From f72afed95099f06fc11b628a73bacf0c69d86c13 Mon Sep 17 00:00:00 2001 From: seveibar Date: Sun, 20 Dec 2020 18:03:12 -0500 Subject: [PATCH 01/21] boilerplate for plugin autoreload, transform page only shows transform plugins --- src/components/PluginProvider/index.js | 44 ++++++++++++++++++++++++-- src/components/TransformPage/index.js | 22 +++++++------ 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/components/PluginProvider/index.js b/src/components/PluginProvider/index.js index 7f58feee..34a82f37 100644 --- a/src/components/PluginProvider/index.js +++ b/src/components/PluginProvider/index.js @@ -17,6 +17,9 @@ export default () => { const { addToast } = useToasts() useEffect(() => { + let pluginChangeWatcherInterval + const lastReloadTime = new Date().toGMTString() + const pluginUrlsToWatch = [] async function loadPlugins() { const pluginUrls = (fromConfig("pluginUrls") || "") .split("\n") @@ -31,21 +34,45 @@ export default () => { importPlugins, interfacePlugins, authenticationPlugins, + tabPlugins, + autoReload, } = (await import(/* webpackIgnore: true */ pluginUrl)).default() plugins.push( - ...transformPlugins.map((p) => ({ ...p, type: "transform" })) + ...transformPlugins.map((p) => ({ + ...p, + type: "transform", + pluginUrl, + })) + ) + plugins.push( + ...importPlugins.map((p) => ({ ...p, type: "import", pluginUrl })) ) - plugins.push(...importPlugins.map((p) => ({ ...p, type: "import" }))) plugins.push( - ...interfacePlugins.map((p) => ({ ...p, type: "interface" })) + ...interfacePlugins.map((p) => ({ + ...p, + type: "interface", + pluginUrl, + })) ) plugins.push( ...authenticationPlugins.map((p) => ({ ...p, type: "authentication", + pluginUrl, + })) + ) + plugins.push( + ...tabPlugins.map((p) => ({ + ...p, + type: "tab", + pluginUrl, })) ) + + if (autoReload) { + pluginUrlsToWatch.push(pluginUrl) + } } catch (e) { // TODO display broken plugin more nicely, using regex extraction of // package and version @@ -56,8 +83,19 @@ export default () => { } } setPlugins(plugins) + + if (pluginUrlsToWatch.length > 0) { + pluginChangeWatcherInterval = setInterval(() => { + // TODO check for 304s against the plugin and reload if necessary + }, 1000) + } } loadPlugins() + + return () => { + clearInterval(pluginChangeWatcherInterval) + } + // eslint-disable-next-line }, [fromConfig("pluginUrls")]) } diff --git a/src/components/TransformPage/index.js b/src/components/TransformPage/index.js index 6e4f5ed6..d4a3aef0 100644 --- a/src/components/TransformPage/index.js +++ b/src/components/TransformPage/index.js @@ -99,16 +99,18 @@ export default () => { > Remove Invalid Samples - {plugins.map((plugin) => ( - - ))} + {plugins + .filter((plugin) => plugin.type === "transform") + .map((plugin) => ( + + ))} {openPlugin && ( Date: Mon, 21 Dec 2020 01:25:48 -0500 Subject: [PATCH 02/21] start review plugin --- src/components/DatasetEditor/index.js | 2 + src/components/HeaderToolbar/index.js | 16 ++++- src/components/ReviewPluginContent/Label.js | 7 +++ src/components/ReviewPluginContent/Review.js | 7 +++ .../ReviewPluginContent/Settings.js | 7 +++ src/components/ReviewPluginContent/index.js | 63 +++++++++++++++++++ .../ReviewPluginContent/index.story.js | 11 ++++ 7 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/components/ReviewPluginContent/Label.js create mode 100644 src/components/ReviewPluginContent/Review.js create mode 100644 src/components/ReviewPluginContent/Settings.js create mode 100644 src/components/ReviewPluginContent/index.js create mode 100644 src/components/ReviewPluginContent/index.story.js diff --git a/src/components/DatasetEditor/index.js b/src/components/DatasetEditor/index.js index 61695ead..14819c34 100644 --- a/src/components/DatasetEditor/index.js +++ b/src/components/DatasetEditor/index.js @@ -19,6 +19,7 @@ import { useHotkeyStorage } from "../HotkeyStorage" import useInterface from "../../hooks/use-interface" import useSummary from "../../hooks/use-summary" import useRemoveSamples from "../../hooks/use-remove-samples" +import ReviewPluginContent from "../ReviewPluginContent" import "brace/mode/javascript" import "brace/theme/github" @@ -166,6 +167,7 @@ export default ({ onClickSetup={() => changeMode("setup")} /> )} + {mode === "review" && } diff --git a/src/components/HeaderToolbar/index.js b/src/components/HeaderToolbar/index.js index d545d30a..e2fb86cb 100644 --- a/src/components/HeaderToolbar/index.js +++ b/src/components/HeaderToolbar/index.js @@ -21,6 +21,7 @@ import { colors, Tooltip } from "@material-ui/core" import ExitToAppIcon from "@material-ui/icons/ExitToApp" import DownloadButton from "../DownloadButton" import PowerIcon from "@material-ui/icons/Power" +import RateReviewIcon from "@material-ui/icons/RateReview" const capitalize = (s) => { return s.charAt(0).toUpperCase() + s.slice(1) @@ -104,6 +105,8 @@ const getIcon = (t, tooltip) => { return case "Samples": return + case "Review": + return default: return
} @@ -161,10 +164,21 @@ const HeaderToolbar = ({ {getIcon(name)} } - // label={isSmall ? "" : t} value={name.toLowerCase()} /> ))} + + {getIcon("Review")} + + } + value={"review".toLowerCase()} + /> )} diff --git a/src/components/ReviewPluginContent/Label.js b/src/components/ReviewPluginContent/Label.js new file mode 100644 index 00000000..4d7ab51a --- /dev/null +++ b/src/components/ReviewPluginContent/Label.js @@ -0,0 +1,7 @@ +import React from "react" + +export const Label = () => { + return null +} + +export default Label diff --git a/src/components/ReviewPluginContent/Review.js b/src/components/ReviewPluginContent/Review.js new file mode 100644 index 00000000..49643a4f --- /dev/null +++ b/src/components/ReviewPluginContent/Review.js @@ -0,0 +1,7 @@ +import React from "react" + +export const Review = () => { + return null +} + +export default Review diff --git a/src/components/ReviewPluginContent/Settings.js b/src/components/ReviewPluginContent/Settings.js new file mode 100644 index 00000000..f765568f --- /dev/null +++ b/src/components/ReviewPluginContent/Settings.js @@ -0,0 +1,7 @@ +import React from "react" + +export const Settings = () => { + return null +} + +export default Settings diff --git a/src/components/ReviewPluginContent/index.js b/src/components/ReviewPluginContent/index.js new file mode 100644 index 00000000..c0f8c253 --- /dev/null +++ b/src/components/ReviewPluginContent/index.js @@ -0,0 +1,63 @@ +import React, { useState } from "react" +import useEventCallback from "use-event-callback" +import { Tabs, Tab, Box, colors, styled } from "@material-ui/core" +import SettingsIcon from "@material-ui/icons/Settings" +import RateReviewIcon from "@material-ui/icons/RateReview" +import CreateIcon from "@material-ui/icons/Create" +import PollIcon from "@material-ui/icons/Poll" +import Settings from "./Settings" +import Review from "./Review" +import Label from "./Label" + +const tabs = [ + { name: "Settings" }, + { name: "Review" }, + { name: "Label" }, + { name: "Analytics" }, +] + +const getIcon = (s) => { + switch (s.toLowerCase()) { + case "settings": { + return + } + case "review": { + return + } + case "label": { + return + } + case "analytics": { + return + } + default: { + return null + } + } +} + +export const ReviewPluginContent = () => { + const [tab, setTab] = useState("settings") + const onChangeTab = useEventCallback((e, newTab) => { + setTab(newTab) + }) + return ( + + + {tabs.map((tab) => ( + + ))} + + {tab === "settings" && } + {tab === "review" && } + {tab === "label" && + ) +} + +export default ReviewPluginContent diff --git a/src/components/ReviewPluginContent/index.story.js b/src/components/ReviewPluginContent/index.story.js new file mode 100644 index 00000000..3fe1942e --- /dev/null +++ b/src/components/ReviewPluginContent/index.story.js @@ -0,0 +1,11 @@ +// @flow + +import React from "react" + +import { storiesOf } from "@storybook/react" + +import ReviewPluginContent from "./" + +storiesOf("ReviewPluginContent", module).add("Basic", () => ( + +)) From 945ffe042e5ea3a76b332855b30d6251c6c7e18a Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 21 Dec 2020 14:56:37 -0500 Subject: [PATCH 03/21] settings page --- .../ReviewPluginContent/QualityContent.js | 39 ++++++++++ .../ReviewPluginContent/Settings.js | 30 ++++++- .../ReviewPluginContent/SidebarItem.js | 15 ++++ .../ReviewPluginContent/SimpleSidebar.js | 36 +++++++++ .../ReviewPluginContent/TeamTable.js | 78 +++++++++++++++++++ src/components/ReviewPluginContent/Title.js | 10 +++ 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 src/components/ReviewPluginContent/QualityContent.js create mode 100644 src/components/ReviewPluginContent/SidebarItem.js create mode 100644 src/components/ReviewPluginContent/SimpleSidebar.js create mode 100644 src/components/ReviewPluginContent/TeamTable.js create mode 100644 src/components/ReviewPluginContent/Title.js diff --git a/src/components/ReviewPluginContent/QualityContent.js b/src/components/ReviewPluginContent/QualityContent.js new file mode 100644 index 00000000..e457d24d --- /dev/null +++ b/src/components/ReviewPluginContent/QualityContent.js @@ -0,0 +1,39 @@ +import React from "react" +import { Box, TextField, InputAdornment } from "@material-ui/core" + +export const QualityContent = () => { + return ( + + + Quality is controlled by assigning multiple labelers each dataset + sample. Each labeler's work is checked against other labelers who have + labeled the same sample. Based on the agreement or disagreement, the + incorrect labeler(s) will be notified and worker statistics relating to + accuracy will be updated. +
+
+ You can configure specific settings to ensure higher overall dataset + quality or increase the speed of the labeling operation. +
+ %, + }} + /> + +
+ ) +} + +export default QualityContent diff --git a/src/components/ReviewPluginContent/Settings.js b/src/components/ReviewPluginContent/Settings.js index f765568f..40ce48a6 100644 --- a/src/components/ReviewPluginContent/Settings.js +++ b/src/components/ReviewPluginContent/Settings.js @@ -1,7 +1,33 @@ -import React from "react" +import React, { useState, styled } from "react" +import { Box, colors } from "@material-ui/core" +import Title from "./Title" +import TeamTable from "./TeamTable" +import QualityContent from "./QualityContent" +import SimpleSidebar from "./SimpleSidebar" + +const sidebarItems = [ + { + name: "Team", + }, + { + name: "Quality", + }, +] export const Settings = () => { - return null + const [selectedItem, setSelectedItem] = useState("Team") + + return ( + + {selectedItem} + {selectedItem === "Team" && } + {selectedItem === "Quality" && } + + ) } export default Settings diff --git a/src/components/ReviewPluginContent/SidebarItem.js b/src/components/ReviewPluginContent/SidebarItem.js new file mode 100644 index 00000000..9f97235f --- /dev/null +++ b/src/components/ReviewPluginContent/SidebarItem.js @@ -0,0 +1,15 @@ +import React from "react" +import { styled, colors } from "@material-ui/core" + +export const SidebarItem = styled("div")(({ selected }) => ({ + padding: 8, + fontWeight: 500, + color: selected ? colors.blue[600] : colors.grey[800], + opacity: !selected ? 0.75 : 1, + cursor: "pointer", + "&:hover": { + opacity: 1, + }, +})) + +export default SidebarItem diff --git a/src/components/ReviewPluginContent/SimpleSidebar.js b/src/components/ReviewPluginContent/SimpleSidebar.js new file mode 100644 index 00000000..ab85e228 --- /dev/null +++ b/src/components/ReviewPluginContent/SimpleSidebar.js @@ -0,0 +1,36 @@ +import React from "react" +import { Box, colors } from "@material-ui/core" +import SidebarItem from "./SidebarItem" + +export const SimpleSidebar = ({ + children, + sidebarItems, + selectedItem, + onSelectItem, +}) => { + return ( + + + {sidebarItems.map((item) => ( + onSelectItem(item.name)} + > + {item.name} + + ))} + + + {children} + + + ) +} + +export default SimpleSidebar diff --git a/src/components/ReviewPluginContent/TeamTable.js b/src/components/ReviewPluginContent/TeamTable.js new file mode 100644 index 00000000..367c26c8 --- /dev/null +++ b/src/components/ReviewPluginContent/TeamTable.js @@ -0,0 +1,78 @@ +import React from "react" +import { + colors, + Box, + Button, + Table, + TableHead, + TableRow, + TableBody, + TableCell, +} from "@material-ui/core" +import EditIcon from "@material-ui/icons/Edit" +import moment from "moment" + +const people = [ + { name: "Billy Acosta", email: "billyaacoasta@rhyta.com", role: "Admin" }, + { + name: "Michael Reynolds", + email: "MichaelCReynolds@rhyta.com", + role: "Reviewer", + }, + { name: "Mary Pack", email: "marypack@rhyta.com", role: "Labeler" }, + { name: "William Pierce", email: "willp@rhyta.com", role: "Labeler" }, + { name: "Micheal Myers", email: "mmyers@rhyta.com", role: "Labeler" }, + { name: "Micheal Lyons", email: "mikelyons@rhyta.com", role: "Labeler" }, +].map((a, i) => ({ + ...a, + lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, +})) + +export const TeamTable = () => { + return ( + <> + + + + Name + Email + Role + Last Activity + + + + {people.map((p, i) => ( + + {p.name} + {p.email} + + + {p.role} + + + + {moment(p.lastActivity).fromNow()} + + ))} + +
+ + + + + + ) +} + +export default TeamTable diff --git a/src/components/ReviewPluginContent/Title.js b/src/components/ReviewPluginContent/Title.js new file mode 100644 index 00000000..b40059a7 --- /dev/null +++ b/src/components/ReviewPluginContent/Title.js @@ -0,0 +1,10 @@ +import React from "react" +import { styled, colors } from "@material-ui/core" + +export const Title = styled("div")({ + fontSize: 24, + color: colors.grey[600], + padding: 32, +}) + +export default Title From 7a51498731f185d2e57ea7f198f1c04ac6f3fb71 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 21 Dec 2020 17:51:37 -0500 Subject: [PATCH 04/21] initial review page --- src/components/ReviewPluginContent/Review.js | 137 ++++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/src/components/ReviewPluginContent/Review.js b/src/components/ReviewPluginContent/Review.js index 49643a4f..4b1a75d9 100644 --- a/src/components/ReviewPluginContent/Review.js +++ b/src/components/ReviewPluginContent/Review.js @@ -1,7 +1,140 @@ -import React from "react" +import React, { useState } from "react" +import Title from "./Title" +import SimpleSidebar from "./SimpleSidebar" +import { + TextField, + InputAdornment, + colors, + Box, + Table, + TableHead, + TableRow, + TableBody, + TableCell, +} from "@material-ui/core" +import CheckIcon from "@material-ui/icons/Check" +import SearchIcon from "@material-ui/icons/Search" +import moment from "moment" + +const sidebarItems = [ + { + name: "All Samples", + }, + { + name: "Needs Review", + }, + { + name: "Complete", + }, +] + +const samples = [ + { + worksSubmitted: 3, + worksNeeded: 3, + confidence: 100, + reviewed: true, + }, + { + worksSubmitted: 2, + worksNeeded: 3, + confidence: 94.4, + reviewed: false, + }, + { + worksSubmitted: 2, + worksNeeded: 3, + confidence: 100, + reviewed: true, + }, + { + worksSubmitted: 1, + worksNeeded: 3, + confidence: 70.3, + reviewed: false, + }, + { + worksSubmitted: 1, + worksNeeded: 1, + confidence: 91.3, + }, + { + worksSubmitted: 0, + worksNeeded: 1, + confidence: 0, + }, + { + worksSubmitted: 0, + worksNeeded: 1, + confidence: 0, + }, + { + worksSubmitted: 0, + worksNeeded: 3, + confidence: 0, + }, + { + worksSubmitted: 0, + worksNeeded: 3, + confidence: 0, + }, +].map((a, i) => ({ + ...a, + index: i, + lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, +})) export const Review = () => { - return null + const [selectedItem, setSelectedItem] = useState("All Samples") + return ( + + Samples + + + + + ), + }} + /> + + + + + SN + Consensus + Confidence + Reviewed + Last Activity + + + + {samples.map((s, i) => ( + + {s.index} + + {s.worksSubmitted} / {s.worksNeeded} + + {s.confidence.toFixed(1)}% + + {s.reviewed ? : null} + + {moment(s.lastActivity).fromNow()} + + ))} + +
+
+ ) } export default Review From fa9913bafa8ec87ee1884b26266d9a6ef0dd92d8 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 21 Dec 2020 18:43:25 -0500 Subject: [PATCH 05/21] refactor for review sample page --- src/components/ReviewPluginContent/Review.js | 105 ++------------ .../ReviewSampleContent.js | 7 + .../ReviewPluginContent/ReviewSamplesTable.js | 135 ++++++++++++++++++ 3 files changed, 152 insertions(+), 95 deletions(-) create mode 100644 src/components/ReviewPluginContent/ReviewSampleContent.js create mode 100644 src/components/ReviewPluginContent/ReviewSamplesTable.js diff --git a/src/components/ReviewPluginContent/Review.js b/src/components/ReviewPluginContent/Review.js index 4b1a75d9..e0b167cc 100644 --- a/src/components/ReviewPluginContent/Review.js +++ b/src/components/ReviewPluginContent/Review.js @@ -11,10 +11,13 @@ import { TableRow, TableBody, TableCell, + Button, } from "@material-ui/core" import CheckIcon from "@material-ui/icons/Check" import SearchIcon from "@material-ui/icons/Search" import moment from "moment" +import ReviewSamplesTable from "./ReviewSamplesTable" +import ReviewSampleContent from "./ReviewSampleContent" const sidebarItems = [ { @@ -28,62 +31,6 @@ const sidebarItems = [ }, ] -const samples = [ - { - worksSubmitted: 3, - worksNeeded: 3, - confidence: 100, - reviewed: true, - }, - { - worksSubmitted: 2, - worksNeeded: 3, - confidence: 94.4, - reviewed: false, - }, - { - worksSubmitted: 2, - worksNeeded: 3, - confidence: 100, - reviewed: true, - }, - { - worksSubmitted: 1, - worksNeeded: 3, - confidence: 70.3, - reviewed: false, - }, - { - worksSubmitted: 1, - worksNeeded: 1, - confidence: 91.3, - }, - { - worksSubmitted: 0, - worksNeeded: 1, - confidence: 0, - }, - { - worksSubmitted: 0, - worksNeeded: 1, - confidence: 0, - }, - { - worksSubmitted: 0, - worksNeeded: 3, - confidence: 0, - }, - { - worksSubmitted: 0, - worksNeeded: 3, - confidence: 0, - }, -].map((a, i) => ({ - ...a, - index: i, - lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, -})) - export const Review = () => { const [selectedItem, setSelectedItem] = useState("All Samples") return ( @@ -92,47 +39,15 @@ export const Review = () => { onSelectItem={setSelectedItem} selectedItem={selectedItem} > - Samples - - - - - ), + {!selectedItem.startsWith("Sample ") && ( + { + setSelectedItem(`Sample ${sampleIndex}`) }} /> - - - - - SN - Consensus - Confidence - Reviewed - Last Activity - - - - {samples.map((s, i) => ( - - {s.index} - - {s.worksSubmitted} / {s.worksNeeded} - - {s.confidence.toFixed(1)}% - - {s.reviewed ? : null} - - {moment(s.lastActivity).fromNow()} - - ))} - -
+ )} + {selectedItem.startsWith("Sample ") && } ) } diff --git a/src/components/ReviewPluginContent/ReviewSampleContent.js b/src/components/ReviewPluginContent/ReviewSampleContent.js new file mode 100644 index 00000000..a7450246 --- /dev/null +++ b/src/components/ReviewPluginContent/ReviewSampleContent.js @@ -0,0 +1,7 @@ +import React from "react" + +export const ReviewSampleContent = () => { + return null +} + +export default ReviewSampleContent diff --git a/src/components/ReviewPluginContent/ReviewSamplesTable.js b/src/components/ReviewPluginContent/ReviewSamplesTable.js new file mode 100644 index 00000000..1c66265d --- /dev/null +++ b/src/components/ReviewPluginContent/ReviewSamplesTable.js @@ -0,0 +1,135 @@ +import React from "react" +import { + TextField, + InputAdornment, + colors, + Box, + Table, + TableHead, + TableRow, + TableBody, + TableCell, + Button, +} from "@material-ui/core" +import Title from "./Title" +import CheckIcon from "@material-ui/icons/Check" +import SearchIcon from "@material-ui/icons/Search" +import moment from "moment" + +const samples = [ + { + worksSubmitted: 3, + worksNeeded: 3, + confidence: 100, + reviewed: true, + }, + { + worksSubmitted: 2, + worksNeeded: 3, + confidence: 94.4, + reviewed: false, + }, + { + worksSubmitted: 2, + worksNeeded: 3, + confidence: 100, + reviewed: true, + }, + { + worksSubmitted: 1, + worksNeeded: 3, + confidence: 70.3, + reviewed: false, + }, + { + worksSubmitted: 1, + worksNeeded: 1, + confidence: 91.3, + }, + { + worksSubmitted: 0, + worksNeeded: 1, + confidence: 0, + }, + { + worksSubmitted: 0, + worksNeeded: 1, + confidence: 0, + }, + { + worksSubmitted: 0, + worksNeeded: 3, + confidence: 0, + }, + { + worksSubmitted: 0, + worksNeeded: 3, + confidence: 0, + }, +].map((a, i) => ({ + ...a, + index: i, + lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, +})) + +export const ReviewSamplesTable = ({ selectedItem }) => { + return ( + <> + Samples + + + + + ), + }} + /> + + + + + SN + Consensus + Confidence + Reviewed + Last Activity + + + + + {samples + .filter((s) => + selectedItem === "Needs Review" + ? !s.reviewed + : selectedItem === "Complete" + ? s.worksSubmitted >= s.worksNeeded + : true + ) + .map((s, i) => ( + + {s.index} + + {s.worksSubmitted} / {s.worksNeeded} + + {s.confidence.toFixed(1)}% + + {s.reviewed ? : null} + + {moment(s.lastActivity).fromNow()} + + + + + ))} + +
+ + ) +} + +export default ReviewSamplesTable From e548eab2e50f4ab3ef3457bbf0a4c4853f31417f Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 21 Dec 2020 22:44:28 -0500 Subject: [PATCH 06/21] progress towards audit trail --- .../ReviewPluginContent/AuditTrail.js | 23 +++++++++++++++++++ src/components/ReviewPluginContent/Review.js | 15 ++++++++---- .../ReviewSampleContent.js | 8 ++++++- .../ReviewPluginContent/ReviewSamplesTable.js | 4 ++-- src/components/ReviewPluginContent/index.js | 2 +- 5 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 src/components/ReviewPluginContent/AuditTrail.js diff --git a/src/components/ReviewPluginContent/AuditTrail.js b/src/components/ReviewPluginContent/AuditTrail.js new file mode 100644 index 00000000..88f1ee58 --- /dev/null +++ b/src/components/ReviewPluginContent/AuditTrail.js @@ -0,0 +1,23 @@ +import React from "react" +import { Box, styled } from "@material-ui/core" + +const ItemContainer = styled("div")({ + padding: 16, + display: "flex", +}) + +const IconContainer = styled("div")({ + flexShrink: 0, +}) + +export const AuditTrail = () => { + return ( + + + + + + ) +} + +export default AuditTrail diff --git a/src/components/ReviewPluginContent/Review.js b/src/components/ReviewPluginContent/Review.js index e0b167cc..a10f27b3 100644 --- a/src/components/ReviewPluginContent/Review.js +++ b/src/components/ReviewPluginContent/Review.js @@ -32,18 +32,25 @@ const sidebarItems = [ ] export const Review = () => { - const [selectedItem, setSelectedItem] = useState("All Samples") + // const [selectedItem, setSelectedItem] = useState("All Samples") + const [selectedItem, setSelectedItem] = useState("Sample 3") return ( {!selectedItem.startsWith("Sample ") && ( { - setSelectedItem(`Sample ${sampleIndex}`) + onClickSample={(sample) => { + setSelectedItem(`Sample ${sample.index}`) }} /> )} diff --git a/src/components/ReviewPluginContent/ReviewSampleContent.js b/src/components/ReviewPluginContent/ReviewSampleContent.js index a7450246..a2354ab8 100644 --- a/src/components/ReviewPluginContent/ReviewSampleContent.js +++ b/src/components/ReviewPluginContent/ReviewSampleContent.js @@ -1,7 +1,13 @@ import React from "react" +import { Box, styled } from "@material-ui/core" +import AuditTrail from "./AuditTrail.js" export const ReviewSampleContent = () => { - return null + return ( + + + + ) } export default ReviewSampleContent diff --git a/src/components/ReviewPluginContent/ReviewSamplesTable.js b/src/components/ReviewPluginContent/ReviewSamplesTable.js index 1c66265d..f901c5b9 100644 --- a/src/components/ReviewPluginContent/ReviewSamplesTable.js +++ b/src/components/ReviewPluginContent/ReviewSamplesTable.js @@ -72,7 +72,7 @@ const samples = [ lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, })) -export const ReviewSamplesTable = ({ selectedItem }) => { +export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { return ( <> Samples @@ -122,7 +122,7 @@ export const ReviewSamplesTable = ({ selectedItem }) => { {moment(s.lastActivity).fromNow()} - + ))} diff --git a/src/components/ReviewPluginContent/index.js b/src/components/ReviewPluginContent/index.js index c0f8c253..86767f1c 100644 --- a/src/components/ReviewPluginContent/index.js +++ b/src/components/ReviewPluginContent/index.js @@ -37,7 +37,7 @@ const getIcon = (s) => { } export const ReviewPluginContent = () => { - const [tab, setTab] = useState("settings") + const [tab, setTab] = useState("review") const onChangeTab = useEventCallback((e, newTab) => { setTab(newTab) }) From 623c95127301b84f9af746e9f07a42960f3df8a1 Mon Sep 17 00:00:00 2001 From: seveibar Date: Tue, 22 Dec 2020 10:40:27 -0500 Subject: [PATCH 07/21] mockup mostly complete --- .../ReviewPluginContent/AuditTrail.js | 77 +++++++++++++++++-- src/components/ReviewPluginContent/Label.js | 14 +++- src/components/ReviewPluginContent/Review.js | 3 +- .../ReviewSampleContent.js | 52 ++++++++++++- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/src/components/ReviewPluginContent/AuditTrail.js b/src/components/ReviewPluginContent/AuditTrail.js index 88f1ee58..4d4dd316 100644 --- a/src/components/ReviewPluginContent/AuditTrail.js +++ b/src/components/ReviewPluginContent/AuditTrail.js @@ -1,21 +1,84 @@ import React from "react" -import { Box, styled } from "@material-ui/core" +import { Box, styled, colors } from "@material-ui/core" +import EditIcon from "@material-ui/icons/Edit" +import ComputerIcon from "@material-ui/icons/Computer" +import SettingsApplicationsIcon from "@material-ui/icons/SettingsApplications" +import SupervisedUserCircleIcon from "@material-ui/icons/SupervisedUserCircle" const ItemContainer = styled("div")({ padding: 16, + margin: 8, + alignItems: "center", + fontSize: 12, display: "flex", + border: "1px solid #ccc", + "& .icon": { + marginRight: 16, + width: 18, + height: 18, + }, + cursor: "pointer", + transition: "border-left 80ms ease,padding-right 80ms ease", + "&.selected": { + borderLeft: `4px solid ${colors.blue[500]}`, + paddingRight: 13, + }, + "&:hover": { + borderLeft: `4px solid ${colors.blue[500]}`, + }, }) -const IconContainer = styled("div")({ - flexShrink: 0, -}) +const ItemText = styled("div")({}) + +const items = [ + { + text: "Billy Acosta labeled this item with 4 bounding boxes.", + type: "label", + }, + { text: "System has selected Billy Acosta's labels", type: "system" }, + { text: "Michael Reynolds labeled this item no labels.", type: "label" }, + { + text: "System has selected Michael Reynolds's labels (91.4% > 87.7%)", + type: "system", + }, + { text: "Mary Pack has confirmed Michael Reynold's labels.", type: "review" }, +] + +const getIcon = (type) => { + switch (type) { + case "label": { + return + } + case "system": { + return ( + + ) + } + case "review": { + return ( + + ) + } + default: { + } + } +} export const AuditTrail = () => { return ( - - - + {items.map((item, i) => ( + + {getIcon(item.type)} + {item.text} + + ))} ) } diff --git a/src/components/ReviewPluginContent/Label.js b/src/components/ReviewPluginContent/Label.js index 4d7ab51a..3ed9441a 100644 --- a/src/components/ReviewPluginContent/Label.js +++ b/src/components/ReviewPluginContent/Label.js @@ -1,7 +1,19 @@ import React from "react" +import UniversalSampleEditor from "../UniversalSampleEditor" export const Label = () => { - return null + return ( + + ) } export default Label diff --git a/src/components/ReviewPluginContent/Review.js b/src/components/ReviewPluginContent/Review.js index a10f27b3..84a1a8ea 100644 --- a/src/components/ReviewPluginContent/Review.js +++ b/src/components/ReviewPluginContent/Review.js @@ -32,8 +32,7 @@ const sidebarItems = [ ] export const Review = () => { - // const [selectedItem, setSelectedItem] = useState("All Samples") - const [selectedItem, setSelectedItem] = useState("Sample 3") + const [selectedItem, setSelectedItem] = useState("All Samples") return ( { return ( - - + + + + + + + + + + + + + + History + + + + + + ) } From 6edac488a02dd55efbe96d929fd51831686e46e8 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 28 Dec 2020 12:06:38 -0500 Subject: [PATCH 08/21] team performance, analytics --- .../ReviewPluginContent/Analytics.js | 16 +++++ .../TeamPerformanceTitle.js | 65 +++++++++++++++++++ src/components/ReviewPluginContent/index.js | 2 + 3 files changed, 83 insertions(+) create mode 100644 src/components/ReviewPluginContent/Analytics.js create mode 100644 src/components/ReviewPluginContent/TeamPerformanceTitle.js diff --git a/src/components/ReviewPluginContent/Analytics.js b/src/components/ReviewPluginContent/Analytics.js new file mode 100644 index 00000000..a56e32ec --- /dev/null +++ b/src/components/ReviewPluginContent/Analytics.js @@ -0,0 +1,16 @@ +import React from "react" +import TeamPerformanceTable from "./TeamPerformanceTitle" +import SimpleSidebar from "./SimpleSidebar" + +export const Analytics = () => { + return ( + + + + ) +} + +export default Analytics diff --git a/src/components/ReviewPluginContent/TeamPerformanceTitle.js b/src/components/ReviewPluginContent/TeamPerformanceTitle.js new file mode 100644 index 00000000..478b6c74 --- /dev/null +++ b/src/components/ReviewPluginContent/TeamPerformanceTitle.js @@ -0,0 +1,65 @@ +import React from "react" +import { + colors, + Box, + Button, + Table, + TableHead, + TableRow, + TableBody, + TableCell, +} from "@material-ui/core" +import EditIcon from "@material-ui/icons/Edit" +import moment from "moment" + +const people = [ + { name: "Billy Acosta", email: "billyaacoasta@rhyta.com", role: "Admin" }, + { + name: "Michael Reynolds", + email: "MichaelCReynolds@rhyta.com", + samplesReviewed: 11, + role: "Reviewer", + }, + { name: "Mary Pack", email: "marypack@rhyta.com", role: "Labeler" }, + { name: "William Pierce", email: "willp@rhyta.com", role: "Labeler" }, + { name: "Micheal Myers", email: "mmyers@rhyta.com", role: "Labeler" }, + { name: "Micheal Lyons", email: "mikelyons@rhyta.com", role: "Labeler" }, +].map((a, i) => ({ + ...a, + samplesLabeled: 2 + ((i * 17) % 7) ** 2, + accuracy: 40 + (((2 + ((i * 17) % 7) ** 2) * 17.7) % 60), + timePerLabel: Math.floor(Math.random() * 600) / 10, +})) + +export const TeamPerformanceTable = () => { + return ( + <> + + + + Name + Email + Samples Labeled + Samples Reviewed + Accuracy + Avg Time/Label + + + + {people.map((p, i) => ( + + {p.name} + {p.email} + {p.samplesLabeled} + {p.samplesReviewed || 0} + {p.accuracy.toFixed(1)}% + {p.timePerLabel.toFixed(1)}s + + ))} + +
+ + ) +} + +export default TeamPerformanceTable diff --git a/src/components/ReviewPluginContent/index.js b/src/components/ReviewPluginContent/index.js index 86767f1c..6d207a98 100644 --- a/src/components/ReviewPluginContent/index.js +++ b/src/components/ReviewPluginContent/index.js @@ -6,6 +6,7 @@ import RateReviewIcon from "@material-ui/icons/RateReview" import CreateIcon from "@material-ui/icons/Create" import PollIcon from "@material-ui/icons/Poll" import Settings from "./Settings" +import Analytics from "./Analytics" import Review from "./Review" import Label from "./Label" @@ -56,6 +57,7 @@ export const ReviewPluginContent = () => { {tab === "settings" && } {tab === "review" && } {tab === "label" &&
) } From 62fbb2a4bc5f7e602380a4ef1e6e4e3f8afc28dd Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 30 Dec 2020 01:37:59 -0500 Subject: [PATCH 09/21] add premium tab to welcome page --- src/components/PremiumStartingPage/index.js | 333 ++++++++++++++++++ .../PremiumStartingPage/index.story.js | 12 + .../PremiumWelcomeSidebarElement/index.js | 52 +++ .../StartingPage/RightSideContent.js | 153 ++++++++ src/components/StartingPage/index.js | 206 +---------- src/components/StartingPage/parts.js | 83 +++++ 6 files changed, 648 insertions(+), 191 deletions(-) create mode 100644 src/components/PremiumStartingPage/index.js create mode 100644 src/components/PremiumStartingPage/index.story.js create mode 100644 src/components/PremiumWelcomeSidebarElement/index.js create mode 100644 src/components/StartingPage/RightSideContent.js create mode 100644 src/components/StartingPage/parts.js diff --git a/src/components/PremiumStartingPage/index.js b/src/components/PremiumStartingPage/index.js new file mode 100644 index 00000000..722a6542 --- /dev/null +++ b/src/components/PremiumStartingPage/index.js @@ -0,0 +1,333 @@ +// @flow + +import React, { useState, useEffect } from "react" +import { makeStyles } from "@material-ui/core/styles" +import Grid from "@material-ui/core/Grid" +import { HeaderWithContainer } from "../Header" +import templates from "../StartingPage/templates" +import * as colors from "@material-ui/core/colors" +import { useDropzone } from "react-dropzone" +import { styled } from "@material-ui/core/styles" +import usePosthog from "../../hooks/use-posthog" +import packageInfo from "../../../package.json" +import useEventCallback from "use-event-callback" +import Box from "@material-ui/core/Box" +import Select from "react-select" +import { useTranslation } from "react-i18next" +import getEmbedYoutubeUrl from "../StartingPage/get-embed-youtube-url.js" +import packageJSON from "../../../package.json" +import Button from "@material-ui/core/Button" +import GetAppIcon from "@material-ui/icons/GetApp" +import useIsDesktop from "../../hooks/use-is-desktop" + +const useStyles = makeStyles((theme) => ({ + container: { + display: "flex", + flexDirection: "column", + backgroundColor: colors.grey[900], + height: "100vh", + }, + headerButton: { + fontSize: 12, + backgroundColor: "#fff", + }, + downloadIcon: { + marginTop: 2, + width: 18, + height: 18, + marginRight: 4, + marginLeft: -6, + color: colors.grey[700], + }, + languageSelectionWrapper: { + display: "flex", + flexDirection: "column", + textAlign: "center", + }, + languageSelectionBox: { + display: "flex", + paddingTop: 24, + [theme.breakpoints.up("sm")]: { + justifyContent: "flex-end", + }, + }, +})) + +const ContentContainer = styled("div")(({ theme }) => ({ + display: "flex", + justifyContent: "center", + flexGrow: 1, + color: "#fff", + overflowY: "scroll", + padding: 100, + [theme.breakpoints.down("sm")]: { + padding: 50, + }, +})) +const Content = styled("div")(({ theme }) => ({ + display: "flex", + flexDirection: "column", + width: "calc(100% - 32px)", + marginLeft: 16, + maxWidth: 1000, +})) + +const Title = styled("div")({ + marginTop: 20, + fontSize: 36, + fontWeight: 600, + color: colors.grey[300], +}) + +const languageSelectionFormStyle = { + control: (base, state) => ({ + ...base, + border: "1px solid #9e9e9e", + background: "transparent", + color: "#e0e0e0", + }), + menuList: (base) => ({ + ...base, + padding: 0, + margin: 0, + color: "black", + }), + singleValue: (base) => ({ + ...base, + color: "white", + }), +} + +const Subtitle = styled("div")({ + fontSize: 18, + // fontWeight: "bold", + marginTop: 8, + color: colors.grey[500], +}) +const ActionList = styled("div")({ marginTop: 48 }) +const Action = styled("a")({ + display: "block", + color: colors.blue[500], + marginTop: 4, + cursor: "pointer", + textDecoration: "none", +}) +const ActionTitle = styled("div")({ + // fontWeight: "bold", + fontSize: 24, + marginBottom: 8, + color: colors.grey[500], +}) +const ActionText = styled("div")({ + color: colors.grey[300], + "& a": { + cursor: "pointer", + color: colors.blue[500], + textDecoration: "none", + }, +}) +const Actionless = styled("div")({ + color: colors.grey[600], + paddingTop: 16, +}) + +const BottomSpacer = styled("div")({ height: 100 }) + +const languageOptions = [ + { label: "English", value: "en" }, + { label: "French", value: "fr" }, + { label: "Chinese", value: "cn" }, + { label: "Portuguese", value: "pt" }, + { label: "Dutch", value: "nl" }, +] + +export default ({ + onFileDrop, + onOpenTemplate, + showDownloadLink = true, + recentItems = [], + onOpenRecentItem, + onClickOpenSession, +}) => { + const c = useStyles() + const posthog = usePosthog() + + // internalization hook + const { t, i18n } = useTranslation() + + const isDesktop = useIsDesktop() + // eslint-disable-next-line + const [newVersionAvailable, changeNewVersionAvailable] = useState(false) + useEffect(() => { + // if (!isDesktop) return + async function checkNewVersion() { + const newPackage = await fetch( + "https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/master/package.json" + ).then((r) => r.json()) + if (newPackage.version !== packageInfo.version) { + changeNewVersionAvailable(newPackage.version) + } + } + checkNewVersion() + }, []) + + const [latestCommunityUpdate, setLatestCommunityUpdate] = useState(null) + useEffect(() => { + async function getLatestREADME() { + const readme = await fetch( + "https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/master/README.md" + ).then((r) => r.text()) + const startCU = readme.search("COMMUNITY-UPDATE:START") + const endCU = readme.search("COMMUNITY-UPDATE:END") + const communityUpdates = readme + .slice(startCU, endCU) + .split("\n") + .slice(1, -1) + .filter((line) => line.trim() !== "") + const latestYtLink = communityUpdates[0].match(/\((.*)\)/)[1] + setLatestCommunityUpdate({ + name: communityUpdates[0].match(/\[(.*)\]/)[1], + ytLink: latestYtLink, + embedYTLink: getEmbedYoutubeUrl(latestYtLink), + }) + } + getLatestREADME() + }, []) + + const [ + createFromTemplateDialogOpen, + changeCreateFromTemplateDialogOpen, + ] = useState(false) + const [addAuthFromDialogOpen, changeAddAuthFromDialogOpen] = useState(false) + const onDrop = useEventCallback((acceptedFiles) => { + onFileDrop(acceptedFiles[0]) + }) + + const changeLanguage = (language) => { + i18n.changeLanguage(language) + } + + let { getRootProps, getInputProps } = useDropzone({ onDrop }) + + return ( +
+ + + + + + Universal Data Tool + {t("universaldatatool-description")} + v{packageJSON.version} + + + + + + {t("open-file")} + + {onClickOpenSession && ( + + {t("open-collaborative-session")} + + )} + changeAddAuthFromDialogOpen(true)}> + {t("add-authentication")} + + { + window.location.href = + "https://universaldatatool.com/courses" + }} + > + {t("create-training-course")} + + {/* Open Folder */} + + + {t("recent")} + {recentItems.length === 0 ? ( + {t("no-recent-files")} + ) : ( + recentItems.map((ri, i) => ( + onOpenRecentItem(ri)}> + {ri.fileName} + + )) + )} + + + {t("help")} + + {t("downloading-and-installing-udt")} + + + {t("labeling-images")} + + {/* Custom Data Entry */} + + {t("github-repository")} + + + {t("youtube-channel")} + + + + + {newVersionAvailable && isDesktop && ( + + )} + + + + + + + + +
+ ) +} diff --git a/src/components/PremiumStartingPage/index.story.js b/src/components/PremiumStartingPage/index.story.js new file mode 100644 index 00000000..860254a2 --- /dev/null +++ b/src/components/PremiumStartingPage/index.story.js @@ -0,0 +1,12 @@ +// @flow + +import React from "react" + +import { storiesOf } from "@storybook/react" +import { action } from "@storybook/addon-actions" + +import PremiumStartingPage from "./" + +storiesOf("PremiumStartingPage", module).add("Basic", () => ( + +)) diff --git a/src/components/PremiumWelcomeSidebarElement/index.js b/src/components/PremiumWelcomeSidebarElement/index.js new file mode 100644 index 00000000..8d0a7134 --- /dev/null +++ b/src/components/PremiumWelcomeSidebarElement/index.js @@ -0,0 +1,52 @@ +import React, { useEffect } from "react" +import { styled, colors, TextField, Button, Box } from "@material-ui/core" +import { useRecoilState, atom } from "recoil" + +const Title = styled("div")({ + marginTop: 24, + color: colors.grey[500], + fontSize: 24, +}) + +const StyledTextField = styled(TextField)({ + "&.MuiTextField-root": { + marginTop: 12, + }, + "& .MuiInputLabel-formControl": { + color: colors.grey[500], + }, + "& .MuiInputBase-input": { + color: colors.grey[100], + }, +}) + +const StyledButton = styled(Button)({ + color: colors.grey[500], +}) + +const loginState = atom({ + key: "loginState", + default: { + loggedIn: false, + }, +}) + +export const PremiumWelcomeSidebarElement = () => { + const [login, setLoginState] = useRecoilState(loginState) + + if (!login.loggedIn) { + return ( + + + + + Login + + + ) + } + + return
{/* asd */}
+} + +export default PremiumWelcomeSidebarElement diff --git a/src/components/StartingPage/RightSideContent.js b/src/components/StartingPage/RightSideContent.js new file mode 100644 index 00000000..5b774cfc --- /dev/null +++ b/src/components/StartingPage/RightSideContent.js @@ -0,0 +1,153 @@ +import React, { useState, useEffect } from "react" +import { Button, styled, Box, colors } from "@material-ui/core" +import packageInfo from "../../../package.json" +import useIsDesktop from "../../hooks/use-is-desktop" +import { + ContentContainer, + Content, + Title, + Subtitle, + ActionList, + Action, + ActionTitle, + ActionText, + Actionless, + BottomSpacer, + useStyles, +} from "./parts" +import { useTranslation } from "react-i18next" +import getEmbedYoutubeUrl from "./get-embed-youtube-url.js" +import GetAppIcon from "@material-ui/icons/GetApp" +import PremiumWelcomeSidebarElement from "../PremiumWelcomeSidebarElement" + +const Tab = styled("div")({ + fontWeight: 600, + color: colors.grey[500], + borderBottom: `1px solid ${colors.grey[700]}`, + marginLeft: 8, + fontSize: 14, + "&:first-child": { + marginLeft: 0, + }, + cursor: "pointer", + "&.active, &:hover": { + color: colors.blue[500], + borderBottom: `1px solid ${colors.blue[700]}`, + }, +}) + +export const RightSideContent = () => { + const { t } = useTranslation() + const c = useStyles() + const isDesktop = useIsDesktop() + const [tab, setTab] = useState("Premium") + const [newVersionAvailable, changeNewVersionAvailable] = useState(false) + + useEffect(() => { + // if (!isDesktop) return + async function checkNewVersion() { + const newPackage = await fetch( + "https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/master/package.json" + ).then((r) => r.json()) + if (newPackage.version !== packageInfo.version) { + changeNewVersionAvailable(newPackage.version) + } + } + checkNewVersion() + }, []) + + const [latestCommunityUpdate, setLatestCommunityUpdate] = useState(null) + useEffect(() => { + async function getLatestREADME() { + const readme = await fetch( + "https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/master/README.md" + ).then((r) => r.text()) + const startCU = readme.search("COMMUNITY-UPDATE:START") + const endCU = readme.search("COMMUNITY-UPDATE:END") + const communityUpdates = readme + .slice(startCU, endCU) + .split("\n") + .slice(1, -1) + .filter((line) => line.trim() !== "") + const latestYtLink = communityUpdates[0].match(/\((.*)\)/)[1] + setLatestCommunityUpdate({ + name: communityUpdates[0].match(/\[(.*)\]/)[1], + ytLink: latestYtLink, + embedYTLink: getEmbedYoutubeUrl(latestYtLink), + }) + } + getLatestREADME() + }, []) + + return ( +
+ + setTab("About")} + className={tab === "About" ? "active" : ""} + > + About + + setTab("Premium")} + className={tab === "Premium" ? "active" : ""} + > + Premium + + + {tab === "About" && ( + <> + {newVersionAvailable && isDesktop && ( + + )} + + {t("about")} + + {t("start-page-about-first-paragraph")} +
+
+ {t("start-page-about-second-paragraph")} +
+
+ {t("the-udt-uses-an")}{" "} + + open-source data format (.udt.json / .udt.csv) + {" "} + {t("start-page-about-third-paragraph")} +
+
+
+
+ + {latestCommunityUpdate && ( + <> + {latestCommunityUpdate.name} + + + )} + + + )} + {tab === "Premium" && } +
+ ) +} + +export default RightSideContent diff --git a/src/components/StartingPage/index.js b/src/components/StartingPage/index.js index 68e690ea..b2619145 100644 --- a/src/components/StartingPage/index.js +++ b/src/components/StartingPage/index.js @@ -16,70 +16,22 @@ import useEventCallback from "use-event-callback" import Box from "@material-ui/core/Box" import Select from "react-select" import { useTranslation } from "react-i18next" -import getEmbedYoutubeUrl from "./get-embed-youtube-url.js" import packageJSON from "../../../package.json" import Button from "@material-ui/core/Button" -import GetAppIcon from "@material-ui/icons/GetApp" -import useIsDesktop from "../../hooks/use-is-desktop" - -const useStyles = makeStyles((theme) => ({ - container: { - display: "flex", - flexDirection: "column", - backgroundColor: colors.grey[900], - height: "100vh", - }, - headerButton: { - fontSize: 12, - backgroundColor: "#fff", - }, - downloadIcon: { - marginTop: 2, - width: 18, - height: 18, - marginRight: 4, - marginLeft: -6, - color: colors.grey[700], - }, - languageSelectionWrapper: { - display: "flex", - flexDirection: "column", - textAlign: "center", - }, - languageSelectionBox: { - display: "flex", - paddingTop: 24, - [theme.breakpoints.up("sm")]: { - justifyContent: "flex-end", - }, - }, -})) - -const ContentContainer = styled("div")(({ theme }) => ({ - display: "flex", - justifyContent: "center", - flexGrow: 1, - color: "#fff", - overflowY: "scroll", - padding: 100, - [theme.breakpoints.down("sm")]: { - padding: 50, - }, -})) -const Content = styled("div")(({ theme }) => ({ - display: "flex", - flexDirection: "column", - width: "calc(100% - 32px)", - marginLeft: 16, - maxWidth: 1000, -})) - -const Title = styled("div")({ - marginTop: 20, - fontSize: 36, - fontWeight: 600, - color: colors.grey[300], -}) +import RightSideContent from "./RightSideContent" +import { + ContentContainer, + Content, + Title, + Subtitle, + ActionList, + Action, + ActionTitle, + ActionText, + Actionless, + BottomSpacer, + useStyles, +} from "./parts" const languageSelectionFormStyle = { control: (base, state) => ({ @@ -100,41 +52,6 @@ const languageSelectionFormStyle = { }), } -const Subtitle = styled("div")({ - fontSize: 18, - // fontWeight: "bold", - marginTop: 8, - color: colors.grey[500], -}) -const ActionList = styled("div")({ marginTop: 48 }) -const Action = styled("a")({ - display: "block", - color: colors.blue[500], - marginTop: 4, - cursor: "pointer", - textDecoration: "none", -}) -const ActionTitle = styled("div")({ - // fontWeight: "bold", - fontSize: 24, - marginBottom: 8, - color: colors.grey[500], -}) -const ActionText = styled("div")({ - color: colors.grey[300], - "& a": { - cursor: "pointer", - color: colors.blue[500], - textDecoration: "none", - }, -}) -const Actionless = styled("div")({ - color: colors.grey[600], - paddingTop: 16, -}) - -const BottomSpacer = styled("div")({ height: 100 }) - const languageOptions = [ { label: "English", value: "en" }, { label: "French", value: "fr" }, @@ -157,45 +74,6 @@ export default ({ // internalization hook const { t, i18n } = useTranslation() - const isDesktop = useIsDesktop() - // eslint-disable-next-line - const [newVersionAvailable, changeNewVersionAvailable] = useState(false) - useEffect(() => { - // if (!isDesktop) return - async function checkNewVersion() { - const newPackage = await fetch( - "https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/master/package.json" - ).then((r) => r.json()) - if (newPackage.version !== packageInfo.version) { - changeNewVersionAvailable(newPackage.version) - } - } - checkNewVersion() - }, []) - - const [latestCommunityUpdate, setLatestCommunityUpdate] = useState(null) - useEffect(() => { - async function getLatestREADME() { - const readme = await fetch( - "https://raw.githubusercontent.com/UniversalDataTool/universal-data-tool/master/README.md" - ).then((r) => r.text()) - const startCU = readme.search("COMMUNITY-UPDATE:START") - const endCU = readme.search("COMMUNITY-UPDATE:END") - const communityUpdates = readme - .slice(startCU, endCU) - .split("\n") - .slice(1, -1) - .filter((line) => line.trim() !== "") - const latestYtLink = communityUpdates[0].match(/\((.*)\)/)[1] - setLatestCommunityUpdate({ - name: communityUpdates[0].match(/\[(.*)\]/)[1], - ytLink: latestYtLink, - embedYTLink: getEmbedYoutubeUrl(latestYtLink), - }) - } - getLatestREADME() - }, []) - const [ createFromTemplateDialogOpen, changeCreateFromTemplateDialogOpen, @@ -315,61 +193,7 @@ export default ({ - {newVersionAvailable && isDesktop && ( - - )} - - {t("about")} - - {t("start-page-about-first-paragraph")} -
-
- {t("start-page-about-second-paragraph")} -
-
- {t("the-udt-uses-an")}{" "} - - open-source data format (.udt.json / .udt.csv) - {" "} - {t("start-page-about-third-paragraph")} -
-
-
-
- - {latestCommunityUpdate && ( - <> - {latestCommunityUpdate.name} - - - )} - {/* - changeCreateFromTemplateDialogOpen(true)} - > - {t("open-a-template")} - {" "} - {t("to-see-how-the-udt-could-work-for-your-data")} - */} - +
diff --git a/src/components/StartingPage/parts.js b/src/components/StartingPage/parts.js new file mode 100644 index 00000000..a7318e10 --- /dev/null +++ b/src/components/StartingPage/parts.js @@ -0,0 +1,83 @@ +import React from "react" +import { styled, colors, makeStyles } from "@material-ui/core" + +export const useStyles = makeStyles((theme) => ({ + container: { + display: "flex", + flexDirection: "column", + backgroundColor: colors.grey[900], + height: "100vh", + }, + languageSelectionWrapper: { + display: "flex", + flexDirection: "column", + textAlign: "center", + }, + languageSelectionBox: { + display: "flex", + paddingTop: 24, + [theme.breakpoints.up("sm")]: { + justifyContent: "flex-end", + }, + }, +})) + +export const ContentContainer = styled("div")(({ theme }) => ({ + display: "flex", + justifyContent: "center", + flexGrow: 1, + color: "#fff", + overflowY: "scroll", + padding: 100, + [theme.breakpoints.down("sm")]: { + padding: 50, + }, +})) +export const Content = styled("div")(({ theme }) => ({ + display: "flex", + flexDirection: "column", + width: "calc(100% - 32px)", + marginLeft: 16, + maxWidth: 1000, +})) + +export const Title = styled("div")({ + marginTop: 20, + fontSize: 36, + fontWeight: 600, + color: colors.grey[300], +}) + +export const Subtitle = styled("div")({ + fontSize: 18, + // fontWeight: "bold", + marginTop: 8, + color: colors.grey[500], +}) +export const ActionList = styled("div")({ marginTop: 48 }) +export const Action = styled("a")({ + display: "block", + color: colors.blue[500], + marginTop: 4, + cursor: "pointer", + textDecoration: "none", +}) +export const ActionTitle = styled("div")({ + // fontWeight: "bold", + fontSize: 24, + marginBottom: 8, + color: colors.grey[500], +}) +export const ActionText = styled("div")({ + color: colors.grey[300], + "& a": { + cursor: "pointer", + color: colors.blue[500], + textDecoration: "none", + }, +}) +export const Actionless = styled("div")({ + color: colors.grey[600], + paddingTop: 16, +}) +export const BottomSpacer = styled("div")({ height: 100 }) From f3d912296fa85286bdd421065b2e951e138e9ab3 Mon Sep 17 00:00:00 2001 From: seveibar Date: Wed, 30 Dec 2020 02:02:17 -0500 Subject: [PATCH 10/21] login and dataset browser mockup --- .../PremiumWelcomeSidebarElement/index.js | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/components/PremiumWelcomeSidebarElement/index.js b/src/components/PremiumWelcomeSidebarElement/index.js index 8d0a7134..988ea7c0 100644 --- a/src/components/PremiumWelcomeSidebarElement/index.js +++ b/src/components/PremiumWelcomeSidebarElement/index.js @@ -31,6 +31,33 @@ const loginState = atom({ }, }) +const DatasetRow = styled("div")({ + display: "flex", + justifyContent: "space-between", + color: colors.grey[600], + "&:hover": { + backgroundColor: "rgba(255,255,255,0.1)", + color: colors.grey[400], + }, + cursor: "pointer", +}) +const DatasetCol = styled("div")({ + display: "flex", + padding: 8, + flexShrink: 0, + flexGrow: 1, + flexBasis: 1, + fontSize: 14, + textAlign: "left", + alignItems: "flex-end", + "&:last-child": { + color: colors.grey[700], + textAlign: "right", + fontSize: 12, + justifyContent: "flex-end", + }, +}) + export const PremiumWelcomeSidebarElement = () => { const [login, setLoginState] = useRecoilState(loginState) @@ -46,7 +73,28 @@ export const PremiumWelcomeSidebarElement = () => { ) } - return
{/* asd */}
+ return ( + + Datasets + + + Dataset 1 + 16,789 Samples + 3 hours ago + + + Dataset 2 + 531 Samples + 12 hours ago + + + Dataset 3 + 6,542 Samples + 5 weeks ago + + + + ) } export default PremiumWelcomeSidebarElement From 4ead54e96595a0ff26f8631606293a3f3839520b Mon Sep 17 00:00:00 2001 From: Mohammed Eldadah Date: Thu, 7 Jan 2021 14:45:29 +0200 Subject: [PATCH 11/21] add user dialog mockup --- .../ReviewPluginContent/AddUserDialog.js | 108 ++++++++++++++++++ .../ReviewPluginContent/TeamTable.js | 14 ++- 2 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/components/ReviewPluginContent/AddUserDialog.js diff --git a/src/components/ReviewPluginContent/AddUserDialog.js b/src/components/ReviewPluginContent/AddUserDialog.js new file mode 100644 index 00000000..2864d515 --- /dev/null +++ b/src/components/ReviewPluginContent/AddUserDialog.js @@ -0,0 +1,108 @@ +import React, { useState } from "react" +import { + Button, + Grid, + TextField, + Dialog, + DialogTitle, + DialogActions, + DialogContent, + MenuItem, + colors, +} from "@material-ui/core" + +export const AddUserDialog = (props) => { + const { onClose, open } = props + const [userData, setUserData] = useState({ + name: "", + email: "", + password: "", + role: "labeler", + }) + + const onSubmit = () => { + // TODO:: submit user data + onClose() + } + + const onInputChange = (event) => { + setUserData({ + ...userData, + [event.target.name]: event.target.value, + }) + } + + return ( + + Add new user + + + + + + + + + + + + + + {["admin", "reviewer", "labeler"].map((role) => ( + + {role} + + ))} + + + + + + + + + + ) +} +export default AddUserDialog diff --git a/src/components/ReviewPluginContent/TeamTable.js b/src/components/ReviewPluginContent/TeamTable.js index 367c26c8..5f195563 100644 --- a/src/components/ReviewPluginContent/TeamTable.js +++ b/src/components/ReviewPluginContent/TeamTable.js @@ -1,4 +1,4 @@ -import React from "react" +import React, { useState } from "react" import { colors, Box, @@ -11,6 +11,7 @@ import { } from "@material-ui/core" import EditIcon from "@material-ui/icons/Edit" import moment from "moment" +import AddUserDialog from "./AddUserDialog" const people = [ { name: "Billy Acosta", email: "billyaacoasta@rhyta.com", role: "Admin" }, @@ -29,6 +30,7 @@ const people = [ })) export const TeamTable = () => { + const [openAddUserDialog, setOpenAddUserDialog] = useState(false) return ( <> @@ -57,7 +59,11 @@ export const TeamTable = () => {
- + setOpenAddUserDialog(false)} + /> ) } From 43729b69779b6c36f79eed529a5c607c3f48be82 Mon Sep 17 00:00:00 2001 From: seveibar Date: Thu, 7 Jan 2021 16:28:31 -0500 Subject: [PATCH 12/21] dataset loading login --- .../PremiumWelcomeSidebarElement/index.js | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/components/PremiumWelcomeSidebarElement/index.js b/src/components/PremiumWelcomeSidebarElement/index.js index 988ea7c0..9e0f5fd7 100644 --- a/src/components/PremiumWelcomeSidebarElement/index.js +++ b/src/components/PremiumWelcomeSidebarElement/index.js @@ -1,6 +1,11 @@ import React, { useEffect } from "react" import { styled, colors, TextField, Button, Box } from "@material-ui/core" -import { useRecoilState, atom } from "recoil" +import { useRecoilState, useSetRecoilState, atom } from "recoil" +import { + useLogin, + useDatasets, + activeDatasetAtom, +} from "udt-premium-api-hook-lib" const Title = styled("div")({ marginTop: 24, @@ -59,15 +64,34 @@ const DatasetCol = styled("div")({ }) export const PremiumWelcomeSidebarElement = () => { - const [login, setLoginState] = useRecoilState(loginState) + const [email, setEmail] = React.useState("") + const [password, setPassword] = React.useState("") + const { login, loginError, isLoggedIn } = useLogin() + const { datasets } = useDatasets() + const setActiveDataset = useSetRecoilState(activeDatasetAtom) - if (!login.loggedIn) { + if (!isLoggedIn) { return ( - - + {loginError && {loginError}} + setEmail(e.target.value)} + variant="filled" + label="Email" + /> + setPassword(e.target.value)} + variant="filled" + type="password" + label="Password" + /> - Login + login({ email, password })} + variant="filled" + > + Login + ) @@ -77,21 +101,13 @@ export const PremiumWelcomeSidebarElement = () => { Datasets - - Dataset 1 - 16,789 Samples - 3 hours ago - - - Dataset 2 - 531 Samples - 12 hours ago - - - Dataset 3 - 6,542 Samples - 5 weeks ago - + {(datasets || []).map((ds) => ( + setActiveDataset(ds)} key={ds.dataset_id}> + {ds.display_name} + {ds.num_samples} Samples + {ds.last_activity} + + ))} ) From 4ecc7ddf88d725218761c43ef12cd493b8495002 Mon Sep 17 00:00:00 2001 From: seveibar Date: Thu, 7 Jan 2021 22:33:44 -0500 Subject: [PATCH 13/21] temp solution to setting active dataset --- .../PremiumWelcomeSidebarElement/index.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/PremiumWelcomeSidebarElement/index.js b/src/components/PremiumWelcomeSidebarElement/index.js index 9e0f5fd7..4de857f9 100644 --- a/src/components/PremiumWelcomeSidebarElement/index.js +++ b/src/components/PremiumWelcomeSidebarElement/index.js @@ -1,6 +1,8 @@ import React, { useEffect } from "react" import { styled, colors, TextField, Button, Box } from "@material-ui/core" import { useRecoilState, useSetRecoilState, atom } from "recoil" +import useActiveDatasetManager from "../../hooks/use-active-dataset-manager" +import LocalStorageDatasetManager from "udt-dataset-managers/dist/LocalStorageDatasetManager" import { useLogin, useDatasets, @@ -69,6 +71,7 @@ export const PremiumWelcomeSidebarElement = () => { const { login, loginError, isLoggedIn } = useLogin() const { datasets } = useDatasets() const setActiveDataset = useSetRecoilState(activeDatasetAtom) + const [, setActiveDatasetManager] = useActiveDatasetManager() if (!isLoggedIn) { return ( @@ -102,7 +105,19 @@ export const PremiumWelcomeSidebarElement = () => { Datasets {(datasets || []).map((ds) => ( - setActiveDataset(ds)} key={ds.dataset_id}> + { + setActiveDataset(ds) + const dm = new LocalStorageDatasetManager() + dm.setDataset({ + interface: {}, + samples: [], + dataset_id: ds.dataset_id, + }) + setActiveDatasetManager(dm) + }} + key={ds.dataset_id} + > {ds.display_name} {ds.num_samples} Samples {ds.last_activity} From bb73fa906aa7e2cd118b40377af886c4dbefa09e Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 9 Jan 2021 00:35:03 -0500 Subject: [PATCH 14/21] progress towards full bindings --- package.json | 1 + src/AppWithContexts.js | 9 +- .../PremiumWelcomeSidebarElement/index.js | 42 ++++- .../ReviewPluginContent/AdminSettings.js | 66 +++++++ .../ReviewPluginContent/ReviewSamplesTable.js | 175 ++++++++++-------- .../ReviewPluginContent/Settings.js | 34 ++-- .../ReviewPluginContent/TeamTable.js | 45 ++--- src/components/ReviewPluginContent/index.js | 7 + yarn.lock | 5 + 9 files changed, 251 insertions(+), 133 deletions(-) create mode 100644 src/components/ReviewPluginContent/AdminSettings.js diff --git a/package.json b/package.json index 65db0fb9..e18b90b3 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "react-select": "^3.0.8", "react-time-series": "^1.0.20", "recoil": "^0.0.13", + "recoil-persist": "^0.8.0", "rfc6902": "^3.0.4", "seamless-immutable": "^7.1.4", "seamless-immutable-patch": "^1.0.4", diff --git a/src/AppWithContexts.js b/src/AppWithContexts.js index 3f3b085a..87b1d37f 100644 --- a/src/AppWithContexts.js +++ b/src/AppWithContexts.js @@ -8,15 +8,16 @@ import { AuthProvider } from "./utils/auth-handlers/use-auth.js" import { LabelHelpProvider } from "./components/LabelHelpView" import { HotkeyStorageProvider } from "./components/HotkeyStorage" import "./App.css" - +import recoilPersist from "recoil-persist" import Loading from "./components/Loading" - -// Importing Internalization file import "./i18n" +const { RecoilPersist, updateState } = recoilPersist() + export const AppWithContexts = () => { return ( - + + diff --git a/src/components/PremiumWelcomeSidebarElement/index.js b/src/components/PremiumWelcomeSidebarElement/index.js index 4de857f9..8791055f 100644 --- a/src/components/PremiumWelcomeSidebarElement/index.js +++ b/src/components/PremiumWelcomeSidebarElement/index.js @@ -1,5 +1,12 @@ import React, { useEffect } from "react" -import { styled, colors, TextField, Button, Box } from "@material-ui/core" +import { + styled, + colors, + TextField, + Button, + Box, + CircularProgress, +} from "@material-ui/core" import { useRecoilState, useSetRecoilState, atom } from "recoil" import useActiveDatasetManager from "../../hooks/use-active-dataset-manager" import LocalStorageDatasetManager from "udt-dataset-managers/dist/LocalStorageDatasetManager" @@ -31,13 +38,6 @@ const StyledButton = styled(Button)({ color: colors.grey[500], }) -const loginState = atom({ - key: "loginState", - default: { - loggedIn: false, - }, -}) - const DatasetRow = styled("div")({ display: "flex", justifyContent: "space-between", @@ -68,11 +68,30 @@ const DatasetCol = styled("div")({ export const PremiumWelcomeSidebarElement = () => { const [email, setEmail] = React.useState("") const [password, setPassword] = React.useState("") - const { login, loginError, isLoggedIn } = useLogin() + const { + login, + logout, + loginError, + isLoggedIn, + loading: loginLoading, + } = useLogin() const { datasets } = useDatasets() const setActiveDataset = useSetRecoilState(activeDatasetAtom) const [, setActiveDatasetManager] = useActiveDatasetManager() + if (loginLoading) + return ( + + + + ) + if (!isLoggedIn) { return ( @@ -124,6 +143,11 @@ export const PremiumWelcomeSidebarElement = () => { ))} + + logout()} variant="filled"> + Sign Out + +
) } diff --git a/src/components/ReviewPluginContent/AdminSettings.js b/src/components/ReviewPluginContent/AdminSettings.js new file mode 100644 index 00000000..47f637ba --- /dev/null +++ b/src/components/ReviewPluginContent/AdminSettings.js @@ -0,0 +1,66 @@ +import React from "react" +import Recoil from "recoil" +import { Box, Button, CircularProgress } from "@material-ui/core" +import { activeDatasetAtom, useAddDataset } from "udt-review-hooks" +import useActiveDatasetManager from "../../hooks/use-active-dataset-manager" + +export const AdminSettings = () => { + const [activeDataset, setActiveDataset] = Recoil.useRecoilState( + activeDatasetAtom + ) + const [dm] = useActiveDatasetManager() + const [loading, setLoading] = React.useState(false) + const addDataset = useAddDataset() + + return ( + + {loading ? ( + + ) : activeDataset ? ( + + + Your dataset is loaded. + + + + + + ) : ( + + + This dataset is being stored locally. + + + + + + )} + + ) +} + +export default AdminSettings diff --git a/src/components/ReviewPluginContent/ReviewSamplesTable.js b/src/components/ReviewPluginContent/ReviewSamplesTable.js index f901c5b9..1e29b8d5 100644 --- a/src/components/ReviewPluginContent/ReviewSamplesTable.js +++ b/src/components/ReviewPluginContent/ReviewSamplesTable.js @@ -10,69 +10,74 @@ import { TableBody, TableCell, Button, + CircularProgress, } from "@material-ui/core" import Title from "./Title" import CheckIcon from "@material-ui/icons/Check" import SearchIcon from "@material-ui/icons/Search" import moment from "moment" +import { useSampleSearch } from "udt-review-hooks" -const samples = [ - { - worksSubmitted: 3, - worksNeeded: 3, - confidence: 100, - reviewed: true, - }, - { - worksSubmitted: 2, - worksNeeded: 3, - confidence: 94.4, - reviewed: false, - }, - { - worksSubmitted: 2, - worksNeeded: 3, - confidence: 100, - reviewed: true, - }, - { - worksSubmitted: 1, - worksNeeded: 3, - confidence: 70.3, - reviewed: false, - }, - { - worksSubmitted: 1, - worksNeeded: 1, - confidence: 91.3, - }, - { - worksSubmitted: 0, - worksNeeded: 1, - confidence: 0, - }, - { - worksSubmitted: 0, - worksNeeded: 1, - confidence: 0, - }, - { - worksSubmitted: 0, - worksNeeded: 3, - confidence: 0, - }, - { - worksSubmitted: 0, - worksNeeded: 3, - confidence: 0, - }, -].map((a, i) => ({ - ...a, - index: i, - lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, -})) +// const samples = [ +// { +// worksSubmitted: 3, +// worksNeeded: 3, +// confidence: 100, +// reviewed: true, +// }, +// { +// worksSubmitted: 2, +// worksNeeded: 3, +// confidence: 94.4, +// reviewed: false, +// }, +// { +// worksSubmitted: 2, +// worksNeeded: 3, +// confidence: 100, +// reviewed: true, +// }, +// { +// worksSubmitted: 1, +// worksNeeded: 3, +// confidence: 70.3, +// reviewed: false, +// }, +// { +// worksSubmitted: 1, +// worksNeeded: 1, +// confidence: 91.3, +// }, +// { +// worksSubmitted: 0, +// worksNeeded: 1, +// confidence: 0, +// }, +// { +// worksSubmitted: 0, +// worksNeeded: 1, +// confidence: 0, +// }, +// { +// worksSubmitted: 0, +// worksNeeded: 3, +// confidence: 0, +// }, +// { +// worksSubmitted: 0, +// worksNeeded: 3, +// confidence: 0, +// }, +// ].map((a, i) => ({ +// ...a, +// index: i, +// lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, +// })) export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { + const { samples } = useSampleSearch() + console.log({ samples }) + return ( <> Samples @@ -93,7 +98,7 @@ export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { - SN + SI Consensus Confidence Reviewed @@ -102,32 +107,42 @@ export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { - {samples - .filter((s) => - selectedItem === "Needs Review" - ? !s.reviewed - : selectedItem === "Complete" - ? s.worksSubmitted >= s.worksNeeded - : true - ) - .map((s, i) => ( - - {s.index} - - {s.worksSubmitted} / {s.worksNeeded} - - {s.confidence.toFixed(1)}% - - {s.reviewed ? : null} - - {moment(s.lastActivity).fromNow()} - - - - - ))} + {samples && + samples + .filter((s) => + selectedItem === "Needs Review" + ? !s.reviewed + : selectedItem === "Complete" + ? s.worksSubmitted >= s.worksNeeded + : true + ) + .map((s, i) => ( + + {s.sample_index} + + {s.number_of_works} / {s.number_of_times_to_be_labeled} + + + {s.confidence.toFixed(1)}% + + + {s.is_reviewed ? : null} + + + {!s.last_activity ? "" : moment(s.last_activity).fromNow()} + + + + + + ))}
+ {!samples && ( + + + + )} ) } diff --git a/src/components/ReviewPluginContent/Settings.js b/src/components/ReviewPluginContent/Settings.js index 40ce48a6..d50b5ba1 100644 --- a/src/components/ReviewPluginContent/Settings.js +++ b/src/components/ReviewPluginContent/Settings.js @@ -1,21 +1,32 @@ -import React, { useState, styled } from "react" +import React, { useState, useMemo, styled } from "react" +import { useRecoilValue } from "recoil" import { Box, colors } from "@material-ui/core" import Title from "./Title" import TeamTable from "./TeamTable" import QualityContent from "./QualityContent" import SimpleSidebar from "./SimpleSidebar" - -const sidebarItems = [ - { - name: "Team", - }, - { - name: "Quality", - }, -] +import AdminSettings from "./AdminSettings" +import { userAtom } from "udt-review-hooks" export const Settings = () => { - const [selectedItem, setSelectedItem] = useState("Team") + const user = useRecoilValue(userAtom) + + const sidebarItems = useMemo( + () => + [ + user.role.toLowerCase() === "admin" && { + name: "Admin", + }, + { + name: "Team", + }, + { + name: "Quality", + }, + ].filter(Boolean), + [] + ) + const [selectedItem, setSelectedItem] = useState(sidebarItems[0].name) return ( { selectedItem={selectedItem} > {selectedItem} + {selectedItem === "Admin" && } {selectedItem === "Team" && } {selectedItem === "Quality" && } diff --git a/src/components/ReviewPluginContent/TeamTable.js b/src/components/ReviewPluginContent/TeamTable.js index 5f195563..6c4007ef 100644 --- a/src/components/ReviewPluginContent/TeamTable.js +++ b/src/components/ReviewPluginContent/TeamTable.js @@ -12,25 +12,11 @@ import { import EditIcon from "@material-ui/icons/Edit" import moment from "moment" import AddUserDialog from "./AddUserDialog" - -const people = [ - { name: "Billy Acosta", email: "billyaacoasta@rhyta.com", role: "Admin" }, - { - name: "Michael Reynolds", - email: "MichaelCReynolds@rhyta.com", - role: "Reviewer", - }, - { name: "Mary Pack", email: "marypack@rhyta.com", role: "Labeler" }, - { name: "William Pierce", email: "willp@rhyta.com", role: "Labeler" }, - { name: "Micheal Myers", email: "mmyers@rhyta.com", role: "Labeler" }, - { name: "Micheal Lyons", email: "mikelyons@rhyta.com", role: "Labeler" }, -].map((a, i) => ({ - ...a, - lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, -})) +import { useTeam } from "udt-premium-api-hook-lib" export const TeamTable = () => { const [openAddUserDialog, setOpenAddUserDialog] = useState(false) + const { team } = useTeam() return ( <> @@ -43,19 +29,20 @@ export const TeamTable = () => { - {people.map((p, i) => ( - - {p.name} - {p.email} - - - {p.role} - - - - {moment(p.lastActivity).fromNow()} - - ))} + {team && + team.map((p, i) => ( + + {p.name} + {p.email} + + + {p.role} + + + + {moment(p.last_activity).fromNow()} + + ))}
diff --git a/src/components/ReviewPluginContent/index.js b/src/components/ReviewPluginContent/index.js index 6d207a98..b92fef86 100644 --- a/src/components/ReviewPluginContent/index.js +++ b/src/components/ReviewPluginContent/index.js @@ -1,4 +1,5 @@ import React, { useState } from "react" +import Recoil from "recoil" import useEventCallback from "use-event-callback" import { Tabs, Tab, Box, colors, styled } from "@material-ui/core" import SettingsIcon from "@material-ui/icons/Settings" @@ -9,6 +10,8 @@ import Settings from "./Settings" import Analytics from "./Analytics" import Review from "./Review" import Label from "./Label" +import AdminSettings from "./AdminSettings" +import { activeDatasetAtom } from "udt-review-hooks" const tabs = [ { name: "Settings" }, @@ -42,6 +45,10 @@ export const ReviewPluginContent = () => { const onChangeTab = useEventCallback((e, newTab) => { setTab(newTab) }) + const activeDataset = Recoil.useRecoilValue(activeDatasetAtom) + + if (!activeDataset) return + return ( diff --git a/yarn.lock b/yarn.lock index 2804db35..47a139a5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18754,6 +18754,11 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +recoil-persist@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/recoil-persist/-/recoil-persist-0.8.0.tgz#132d67ec31412c0289a0a7bf2880f1a0057705b8" + integrity sha512-fqrhvklpsPtqYAf9qRyQ7XbLirLEH3c0s2INGMv80JQS8jAC0gtaCavjVaSBqko+JBvVsNZCSNgtIDDWSP7ZKg== + recoil@^0.0.13: version "0.0.13" resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.0.13.tgz#23e6d63135c07d8defbd91aaa35dca94ec5c15a6" From 90cbbe0b5e4f4ab5f6cd2315c293fccdb5d503e5 Mon Sep 17 00:00:00 2001 From: seveibar Date: Sat, 9 Jan 2021 10:16:40 -0500 Subject: [PATCH 15/21] label mode working --- .../PremiumWelcomeSidebarElement/index.js | 6 +- src/components/ReviewPluginContent/Label.js | 32 ++++++--- .../ReviewPluginContent/ReviewSamplesTable.js | 70 ++++--------------- 3 files changed, 35 insertions(+), 73 deletions(-) diff --git a/src/components/PremiumWelcomeSidebarElement/index.js b/src/components/PremiumWelcomeSidebarElement/index.js index 8791055f..49110913 100644 --- a/src/components/PremiumWelcomeSidebarElement/index.js +++ b/src/components/PremiumWelcomeSidebarElement/index.js @@ -10,11 +10,7 @@ import { import { useRecoilState, useSetRecoilState, atom } from "recoil" import useActiveDatasetManager from "../../hooks/use-active-dataset-manager" import LocalStorageDatasetManager from "udt-dataset-managers/dist/LocalStorageDatasetManager" -import { - useLogin, - useDatasets, - activeDatasetAtom, -} from "udt-premium-api-hook-lib" +import { useLogin, useDatasets, activeDatasetAtom } from "udt-review-hooks" const Title = styled("div")({ marginTop: 24, diff --git a/src/components/ReviewPluginContent/Label.js b/src/components/ReviewPluginContent/Label.js index 3ed9441a..f1d20aeb 100644 --- a/src/components/ReviewPluginContent/Label.js +++ b/src/components/ReviewPluginContent/Label.js @@ -1,18 +1,30 @@ import React from "react" +import Recoil from "recoil" +import { CircularProgress, Box, Button } from "@material-ui/core" import UniversalSampleEditor from "../UniversalSampleEditor" +import { useAssignedSample } from "udt-review-hooks" export const Label = () => { + const { sample, submit, reassign, error, loadingSample } = useAssignedSample() + if (!sample) + return ( + + + + ) return ( - + <> + { + await submit(sample.annotation) + }} + onExit={() => null} + /> + + ) } diff --git a/src/components/ReviewPluginContent/ReviewSamplesTable.js b/src/components/ReviewPluginContent/ReviewSamplesTable.js index 1e29b8d5..6a676883 100644 --- a/src/components/ReviewPluginContent/ReviewSamplesTable.js +++ b/src/components/ReviewPluginContent/ReviewSamplesTable.js @@ -18,65 +18,18 @@ import SearchIcon from "@material-ui/icons/Search" import moment from "moment" import { useSampleSearch } from "udt-review-hooks" -// const samples = [ -// { -// worksSubmitted: 3, -// worksNeeded: 3, -// confidence: 100, -// reviewed: true, -// }, -// { -// worksSubmitted: 2, -// worksNeeded: 3, -// confidence: 94.4, -// reviewed: false, -// }, -// { -// worksSubmitted: 2, -// worksNeeded: 3, -// confidence: 100, -// reviewed: true, -// }, -// { -// worksSubmitted: 1, -// worksNeeded: 3, -// confidence: 70.3, -// reviewed: false, -// }, -// { -// worksSubmitted: 1, -// worksNeeded: 1, -// confidence: 91.3, -// }, -// { -// worksSubmitted: 0, -// worksNeeded: 1, -// confidence: 0, -// }, -// { -// worksSubmitted: 0, -// worksNeeded: 1, -// confidence: 0, -// }, -// { -// worksSubmitted: 0, -// worksNeeded: 3, -// confidence: 0, -// }, -// { -// worksSubmitted: 0, -// worksNeeded: 3, -// confidence: 0, -// }, -// ].map((a, i) => ({ -// ...a, -// index: i, -// lastActivity: Date.now() - (i / 16) ** 2 * 3 * 1000 * 60 * 60 * 48, -// })) - export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { - const { samples } = useSampleSearch() - console.log({ samples }) + const { samples } = useSampleSearch({ + filter: + selectedItem === "Needs Review" + ? "needs-review" + : selectedItem === "asd" + ? "reviewed" + : "", + // order_by: "last_activity" + // sort_order: "desc" + }) + const [searchText, setSearchText] = React.useState("") return ( <> @@ -86,6 +39,7 @@ export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { fullWidth variant="outlined" label="Search by sample number or labeler" + onChange={(e) => setSearchText(e.target.value)} InputProps={{ startAdornment: ( From 58a9a3fd8c4e99fc6bcc07997e4b7e4838889d1f Mon Sep 17 00:00:00 2001 From: seveibar Date: Sun, 10 Jan 2021 23:24:34 -0500 Subject: [PATCH 16/21] most things working --- src/components/DatasetEditor/index.js | 13 ++- src/components/HeaderToolbar/index.js | 43 ++++----- .../PremiumWelcomeSidebarElement/index.js | 5 +- .../ReviewPluginContent/AdminSettings.js | 9 +- .../ReviewPluginContent/Analytics.js | 5 +- .../ReviewPluginContent/AuditTrail.js | 45 +++++---- .../ReviewPluginContent/QualityContent.js | 75 +++++++++++---- src/components/ReviewPluginContent/Review.js | 10 +- .../ReviewSampleContent.js | 92 ++++++++++++++++--- .../ReviewPluginContent/ReviewSamplesTable.js | 29 +++--- 10 files changed, 228 insertions(+), 98 deletions(-) diff --git a/src/components/DatasetEditor/index.js b/src/components/DatasetEditor/index.js index 14819c34..6e2739db 100644 --- a/src/components/DatasetEditor/index.js +++ b/src/components/DatasetEditor/index.js @@ -1,6 +1,7 @@ // @flow import React, { useState, useEffect, useMemo } from "react" +import Recoil from "recoil" import { makeStyles } from "@material-ui/core/styles" import { HeaderWithContainer } from "../Header" @@ -20,6 +21,7 @@ import useInterface from "../../hooks/use-interface" import useSummary from "../../hooks/use-summary" import useRemoveSamples from "../../hooks/use-remove-samples" import ReviewPluginContent from "../ReviewPluginContent" +import { activeDatasetAtom } from "udt-review-hooks" import "brace/mode/javascript" import "brace/theme/github" @@ -58,7 +60,10 @@ export default ({ const labelOnlyMode = useIsLabelOnlyMode() const c = useStyles() const { addToast } = useToasts() - const [mode, changeMode] = useState(labelOnlyMode ? "label" : initialMode) + const isReviewMode = Boolean(Recoil.useRecoilValue(activeDatasetAtom)) + const [mode, changeMode] = useState( + isReviewMode ? "review" : labelOnlyMode ? "label" : initialMode + ) const { ipcRenderer } = useElectron() || {} const posthog = usePosthog() const { iface } = useInterface() @@ -67,7 +72,11 @@ export default ({ const [sampleIndex, setSampleIndex] = useState(null) - const headerTabs = labelOnlyMode ? ["Label"] : ["Setup", "Samples", "Label"] + const headerTabs = isReviewMode + ? ["Review"] + : labelOnlyMode + ? ["Label"] + : ["Setup", "Samples", "Label"] const [ sampleTimeToComplete, diff --git a/src/components/HeaderToolbar/index.js b/src/components/HeaderToolbar/index.js index e2fb86cb..c7bf82c2 100644 --- a/src/components/HeaderToolbar/index.js +++ b/src/components/HeaderToolbar/index.js @@ -22,6 +22,8 @@ import ExitToAppIcon from "@material-ui/icons/ExitToApp" import DownloadButton from "../DownloadButton" import PowerIcon from "@material-ui/icons/Power" import RateReviewIcon from "@material-ui/icons/RateReview" +import Recoil from "recoil" +import { activeDatasetAtom } from "udt-review-hooks" const capitalize = (s) => { return s.charAt(0).toUpperCase() + s.slice(1) @@ -141,6 +143,7 @@ const HeaderToolbar = ({ const c = useStyles() const { authProvider, isLoggedIn, logout } = useAuth() const { t } = useTranslation() + const isReviewMode = Boolean(Recoil.useRecoilValue(activeDatasetAtom)) return ( <> @@ -167,40 +170,30 @@ const HeaderToolbar = ({ value={name.toLowerCase()} /> ))} - - {getIcon("Review")} - - } - value={"review".toLowerCase()} - /> )}
- {fileOpen && } - - {!isDesktop && fileOpen && ( + {!isReviewMode && fileOpen && } + {!isReviewMode && ( + + )} + {!isReviewMode && !isDesktop && fileOpen && ( )} - {fileOpen && ( + {!isReviewMode && fileOpen && ( { key={ds.dataset_id} > {ds.display_name} - {ds.num_samples} Samples - {ds.last_activity} + {ds.number_of_samples} Samples + {moment(ds.last_activity).fromNow()} ))} diff --git a/src/components/ReviewPluginContent/AdminSettings.js b/src/components/ReviewPluginContent/AdminSettings.js index 47f637ba..6c246f37 100644 --- a/src/components/ReviewPluginContent/AdminSettings.js +++ b/src/components/ReviewPluginContent/AdminSettings.js @@ -22,9 +22,16 @@ export const AdminSettings = () => { Your dataset is loaded. - + ) : ( diff --git a/src/components/ReviewPluginContent/Analytics.js b/src/components/ReviewPluginContent/Analytics.js index a56e32ec..3584996c 100644 --- a/src/components/ReviewPluginContent/Analytics.js +++ b/src/components/ReviewPluginContent/Analytics.js @@ -5,7 +5,10 @@ import SimpleSidebar from "./SimpleSidebar" export const Analytics = () => { return ( diff --git a/src/components/ReviewPluginContent/AuditTrail.js b/src/components/ReviewPluginContent/AuditTrail.js index 4d4dd316..5261ed16 100644 --- a/src/components/ReviewPluginContent/AuditTrail.js +++ b/src/components/ReviewPluginContent/AuditTrail.js @@ -1,5 +1,5 @@ import React from "react" -import { Box, styled, colors } from "@material-ui/core" +import { CircularProgress, Box, styled, colors } from "@material-ui/core" import EditIcon from "@material-ui/icons/Edit" import ComputerIcon from "@material-ui/icons/Computer" import SettingsApplicationsIcon from "@material-ui/icons/SettingsApplications" @@ -30,23 +30,10 @@ const ItemContainer = styled("div")({ const ItemText = styled("div")({}) -const items = [ - { - text: "Billy Acosta labeled this item with 4 bounding boxes.", - type: "label", - }, - { text: "System has selected Billy Acosta's labels", type: "system" }, - { text: "Michael Reynolds labeled this item no labels.", type: "label" }, - { - text: "System has selected Michael Reynolds's labels (91.4% > 87.7%)", - type: "system", - }, - { text: "Mary Pack has confirmed Michael Reynold's labels.", type: "review" }, -] - const getIcon = (type) => { switch (type) { - case "label": { + case "label": + case "work": { return } case "system": { @@ -57,6 +44,7 @@ const getIcon = (type) => { /> ) } + case "work_review": case "review": { return ( { } } -export const AuditTrail = () => { +export const AuditTrail = ({ selectedItem, items, onSelectItem }) => { + if (!items) + return ( + + + + ) return ( {items.map((item, i) => ( - + onSelectItem(item)} + key={i} + className={selectedItem === item && "selected"} + > {getIcon(item.type)} - {item.text} + {item.type === "work" && ( + {item.worker_name} submitted labels + )} + {item.type === "work_review" && ( + + {item.reviewer_name} {item.accept_work ? "accepted" : "rejected"}{" "} + labels from {item.worker_name} + + )} + {item.type === "system" && {item.message}} ))} diff --git a/src/components/ReviewPluginContent/QualityContent.js b/src/components/ReviewPluginContent/QualityContent.js index e457d24d..d873ec98 100644 --- a/src/components/ReviewPluginContent/QualityContent.js +++ b/src/components/ReviewPluginContent/QualityContent.js @@ -1,7 +1,21 @@ import React from "react" -import { Box, TextField, InputAdornment } from "@material-ui/core" +import { + Button, + Box, + TextField, + InputAdornment, + CircularProgress, +} from "@material-ui/core" +import { useDatasetSettings } from "udt-review-hooks" export const QualityContent = () => { + const { updateDatasetSettings, dataset, loading } = useDatasetSettings() + const [newSettings, setNewSetting] = React.useReducer( + (state, [key, value]) => + key === "erase" ? {} : { ...state, [key]: value }, + {} + ) + return ( @@ -15,23 +29,48 @@ export const QualityContent = () => { You can configure specific settings to ensure higher overall dataset quality or increase the speed of the labeling operation. - %, - }} - /> - + {loading ? ( + + + + ) : ( + <> + %, + }} + /> + { + setNewSetting(["votes_per_sample", parseInt(e.target.value)]) + }} + variant="outlined" + label="Votes" + helperText="Number of times each sample will be labeled" + /> + + )} + + + ) } diff --git a/src/components/ReviewPluginContent/Review.js b/src/components/ReviewPluginContent/Review.js index 84a1a8ea..cd47f90c 100644 --- a/src/components/ReviewPluginContent/Review.js +++ b/src/components/ReviewPluginContent/Review.js @@ -27,12 +27,13 @@ const sidebarItems = [ name: "Needs Review", }, { - name: "Complete", + name: "Reviewed", }, ] export const Review = () => { const [selectedItem, setSelectedItem] = useState("All Samples") + const [selectedSampleId, setSelectedSampleId] = useState(null) return ( { { - setSelectedItem(`Sample ${sample.index}`) + setSelectedItem(`Sample ${sample.sample_index}`) + setSelectedSampleId(sample.sample_id) }} /> )} - {selectedItem.startsWith("Sample ") && } + {selectedItem.startsWith("Sample ") && ( + + )} ) } diff --git a/src/components/ReviewPluginContent/ReviewSampleContent.js b/src/components/ReviewPluginContent/ReviewSampleContent.js index 4930e813..3d168071 100644 --- a/src/components/ReviewPluginContent/ReviewSampleContent.js +++ b/src/components/ReviewPluginContent/ReviewSampleContent.js @@ -1,9 +1,41 @@ import React from "react" -import { Box, styled, Button, colors } from "@material-ui/core" +import { + CircularProgress, + Box, + styled, + Button, + colors, +} from "@material-ui/core" import AuditTrail from "./AuditTrail.js" import UniversalSampleEditor from "../UniversalSampleEditor" +import { useSample, useReviewWork } from "udt-review-hooks" + +export const ReviewSampleContent = ({ sampleId }) => { + const [mode, setMode] = React.useState("view-best") // correct, view-audit-item + const [selectedAuditItem, setSelectedAuditItem] = React.useState() + const { sample, auditTrail, loading, reloadSample } = useSample({ + sampleId, + withAuditTrail: true, + }) + const { reviewWork } = useReviewWork( + mode === "view-best" ? sample?.best_work_id : selectedAuditItem?.work_id + ) + + const sampleBeingViewed = React.useMemo( + () => ({ + ...sample?.sample_data, + annotation: + mode === "correct" + ? null + : mode === "view-best" + ? sample?.best_annotation + : mode === "view-audit-item" + ? selectedAuditItem?.annotation + : null, + }), + [sample, mode, selectedAuditItem] + ) -export const ReviewSampleContent = () => { return ( @@ -23,6 +55,13 @@ export const ReviewSampleContent = () => { color: colors.green[800], borderColor: colors.green[600], }} + onClick={async () => { + await reviewWork({ + accept: true, + message: window.prompt("Leave a message?"), + }) + await reloadSample() + }} > Approve @@ -30,24 +69,47 @@ export const ReviewSampleContent = () => { Next - + {loading || !sample ? ( + + + + ) : mode === "view-audit-item" && selectedAuditItem.type !== "work" ? ( +
{JSON.stringify(selectedAuditItem, null, "  ")}
+ ) : ( + + )}
History - - - + diff --git a/src/components/ReviewPluginContent/ReviewSamplesTable.js b/src/components/ReviewPluginContent/ReviewSamplesTable.js index 6a676883..ba8dd27f 100644 --- a/src/components/ReviewPluginContent/ReviewSamplesTable.js +++ b/src/components/ReviewPluginContent/ReviewSamplesTable.js @@ -19,16 +19,19 @@ import moment from "moment" import { useSampleSearch } from "udt-review-hooks" export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { - const { samples } = useSampleSearch({ - filter: - selectedItem === "Needs Review" - ? "needs-review" - : selectedItem === "asd" - ? "reviewed" - : "", - // order_by: "last_activity" - // sort_order: "desc" - }) + const searchOptions = React.useMemo( + () => ({ + limit: 1000, + filter: + selectedItem === "Needs Review" + ? "needs-review" + : selectedItem === "Reviewed" + ? "reviewed" + : "", + }), + [selectedItem] + ) + const { samples } = useSampleSearch(searchOptions) const [searchText, setSearchText] = React.useState("") return ( @@ -37,6 +40,8 @@ export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { setSearchText(e.target.value)} @@ -65,9 +70,9 @@ export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { samples .filter((s) => selectedItem === "Needs Review" - ? !s.reviewed + ? !s.is_reviewed : selectedItem === "Complete" - ? s.worksSubmitted >= s.worksNeeded + ? s.number_of_works >= s.number_of_times_to_be_labeled : true ) .map((s, i) => ( From bd61bfa27d98b695860b8a7ba07a9da4317cf8c3 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 11 Jan 2021 00:04:57 -0500 Subject: [PATCH 17/21] UDT <-> cloud transition improvements --- src/components/DatasetEditor/index.js | 2 +- src/components/ReviewPluginContent/AdminSettings.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/DatasetEditor/index.js b/src/components/DatasetEditor/index.js index 6e2739db..bb14f93a 100644 --- a/src/components/DatasetEditor/index.js +++ b/src/components/DatasetEditor/index.js @@ -76,7 +76,7 @@ export default ({ ? ["Review"] : labelOnlyMode ? ["Label"] - : ["Setup", "Samples", "Label"] + : ["Setup", "Samples", "Label", "Review"] const [ sampleTimeToComplete, diff --git a/src/components/ReviewPluginContent/AdminSettings.js b/src/components/ReviewPluginContent/AdminSettings.js index 6c246f37..0324c4f5 100644 --- a/src/components/ReviewPluginContent/AdminSettings.js +++ b/src/components/ReviewPluginContent/AdminSettings.js @@ -55,7 +55,9 @@ export const AdminSettings = () => { udt_dataset: ds, display_name, }) + console.log({ cloudDS }) setActiveDataset(cloudDS) + setLoading(false) }} variant="outlined" From ef04431d304fe238c21dd1d78ec6b0716f9ac6c1 Mon Sep 17 00:00:00 2001 From: Mohammed Eldadah Date: Mon, 11 Jan 2021 16:49:46 +0200 Subject: [PATCH 18/21] apply user roles --- src/components/ReviewPluginContent/TeamTable.js | 2 +- src/components/ReviewPluginContent/index.js | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/ReviewPluginContent/TeamTable.js b/src/components/ReviewPluginContent/TeamTable.js index 6c4007ef..ab467cf6 100644 --- a/src/components/ReviewPluginContent/TeamTable.js +++ b/src/components/ReviewPluginContent/TeamTable.js @@ -12,7 +12,7 @@ import { import EditIcon from "@material-ui/icons/Edit" import moment from "moment" import AddUserDialog from "./AddUserDialog" -import { useTeam } from "udt-premium-api-hook-lib" +import { useTeam } from "udt-review-hooks" export const TeamTable = () => { const [openAddUserDialog, setOpenAddUserDialog] = useState(false) diff --git a/src/components/ReviewPluginContent/index.js b/src/components/ReviewPluginContent/index.js index b92fef86..a758a95a 100644 --- a/src/components/ReviewPluginContent/index.js +++ b/src/components/ReviewPluginContent/index.js @@ -1,5 +1,5 @@ import React, { useState } from "react" -import Recoil from "recoil" +import Recoil, { useRecoilValue } from "recoil" import useEventCallback from "use-event-callback" import { Tabs, Tab, Box, colors, styled } from "@material-ui/core" import SettingsIcon from "@material-ui/icons/Settings" @@ -11,13 +11,13 @@ import Analytics from "./Analytics" import Review from "./Review" import Label from "./Label" import AdminSettings from "./AdminSettings" -import { activeDatasetAtom } from "udt-review-hooks" +import { activeDatasetAtom, userAtom } from "udt-review-hooks" const tabs = [ - { name: "Settings" }, - { name: "Review" }, - { name: "Label" }, - { name: "Analytics" }, + { name: "Settings", roles: ['admin'] }, + { name: "Review", roles: ['admin', 'reviewer'] }, + { name: "Label", roles: ['admin', 'reviewer', 'labeler'] }, + { name: "Analytics", roles: ['admin', 'reviewer', 'labeler'] }, ] const getIcon = (s) => { @@ -41,6 +41,7 @@ const getIcon = (s) => { } export const ReviewPluginContent = () => { + const user = useRecoilValue(userAtom) const [tab, setTab] = useState("review") const onChangeTab = useEventCallback((e, newTab) => { setTab(newTab) @@ -52,7 +53,7 @@ export const ReviewPluginContent = () => { return ( - {tabs.map((tab) => ( + {tabs.filter(tab => tab.roles.includes(user.role.toLowerCase())).map((tab) => ( Date: Mon, 11 Jan 2021 17:27:17 +0200 Subject: [PATCH 19/21] run prettier --- src/components/ReviewPluginContent/index.js | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/ReviewPluginContent/index.js b/src/components/ReviewPluginContent/index.js index a758a95a..d2c48a95 100644 --- a/src/components/ReviewPluginContent/index.js +++ b/src/components/ReviewPluginContent/index.js @@ -14,10 +14,10 @@ import AdminSettings from "./AdminSettings" import { activeDatasetAtom, userAtom } from "udt-review-hooks" const tabs = [ - { name: "Settings", roles: ['admin'] }, - { name: "Review", roles: ['admin', 'reviewer'] }, - { name: "Label", roles: ['admin', 'reviewer', 'labeler'] }, - { name: "Analytics", roles: ['admin', 'reviewer', 'labeler'] }, + { name: "Settings", roles: ["admin"] }, + { name: "Review", roles: ["admin", "reviewer"] }, + { name: "Label", roles: ["admin", "reviewer", "labeler"] }, + { name: "Analytics", roles: ["admin", "reviewer", "labeler"] }, ] const getIcon = (s) => { @@ -53,14 +53,16 @@ export const ReviewPluginContent = () => { return ( - {tabs.filter(tab => tab.roles.includes(user.role.toLowerCase())).map((tab) => ( - - ))} + {tabs + .filter((tab) => tab.roles.includes(user.role.toLowerCase())) + .map((tab) => ( + + ))} {tab === "settings" && } {tab === "review" && } From 4e0b55603b033292abf7fef0184876f535834759 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 11 Jan 2021 12:44:43 -0500 Subject: [PATCH 20/21] various buttons added --- .gitignore | 2 + package.json | 1 + .../ReviewPluginContent/AddUserDialog.js | 117 ++++++++++-------- src/components/ReviewPluginContent/Review.js | 23 +++- .../ReviewSampleContent.js | 43 +++++-- .../ReviewPluginContent/ReviewSamplesTable.js | 10 +- yarn.lock | 26 ++++ 7 files changed, 156 insertions(+), 66 deletions(-) diff --git a/.gitignore b/.gitignore index 6a7a2fd6..8ab1795d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,5 @@ cypress/snapshots .idea *.db + +.vercel diff --git a/package.json b/package.json index e18b90b3..ebe327f3 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,7 @@ "udt-collaboration-server": "^1.0.7", "udt-dataset-managers": "^1.0.15", "udt-format": "0.0.1", + "udt-review-hooks": "^1.0.0", "use-async-effect": "^2.2.2", "use-event-callback": "^0.1.0", "wavesurfer.js": "^3.3.3", diff --git a/src/components/ReviewPluginContent/AddUserDialog.js b/src/components/ReviewPluginContent/AddUserDialog.js index 2864d515..e11fac67 100644 --- a/src/components/ReviewPluginContent/AddUserDialog.js +++ b/src/components/ReviewPluginContent/AddUserDialog.js @@ -1,5 +1,7 @@ import React, { useState } from "react" import { + Box, + CircularProgress, Button, Grid, TextField, @@ -10,8 +12,12 @@ import { MenuItem, colors, } from "@material-ui/core" +import { useAddUser } from "udt-review-hooks" export const AddUserDialog = (props) => { + const addUser = useAddUser() + const [loading, setLoading] = useState(false) + const [error, setError] = useState() const { onClose, open } = props const [userData, setUserData] = useState({ name: "", @@ -20,8 +26,11 @@ export const AddUserDialog = (props) => { role: "labeler", }) - const onSubmit = () => { - // TODO:: submit user data + const onSubmit = async () => { + setLoading(true) + setError(null) + await addUser(userData).catch((err) => setError(err.toString())) + setLoading(false) onClose() } @@ -36,56 +45,62 @@ export const AddUserDialog = (props) => { Add new user - - - + {loading ? ( + + + + ) : ( + + + + + + + + + + + + + {["admin", "reviewer", "labeler"].map((role) => ( + + {role} + + ))} + + - - - - - - - - - {["admin", "reviewer", "labeler"].map((role) => ( - - {role} - - ))} - - - + )} + @@ -56,16 +75,17 @@ export const ReviewSampleContent = ({ sampleId }) => { borderColor: colors.green[600], }} onClick={async () => { - await reviewWork({ - accept: true, - message: window.prompt("Leave a message?"), - }) + if (!work_id) { + window.alert("There is no work to review.") + return + } + await reviewWork({ accept: true }) await reloadSample() }} > Approve - @@ -80,6 +100,9 @@ export const ReviewSampleContent = ({ sampleId }) => { sampleIndex={sample.sample_index} sample={sampleBeingViewed} interface={sample.interface} + onModifySample={async (sample) => { + await submitAnnotation(sample.annotation) + }} /> )} diff --git a/src/components/ReviewPluginContent/ReviewSamplesTable.js b/src/components/ReviewPluginContent/ReviewSamplesTable.js index ba8dd27f..a758c3ea 100644 --- a/src/components/ReviewPluginContent/ReviewSamplesTable.js +++ b/src/components/ReviewPluginContent/ReviewSamplesTable.js @@ -21,7 +21,7 @@ import { useSampleSearch } from "udt-review-hooks" export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { const searchOptions = React.useMemo( () => ({ - limit: 1000, + limit: 20, filter: selectedItem === "Needs Review" ? "needs-review" @@ -91,7 +91,13 @@ export const ReviewSamplesTable = ({ selectedItem, onClickSample }) => { {!s.last_activity ? "" : moment(s.last_activity).fromNow()} - + ))} diff --git a/yarn.lock b/yarn.lock index 47a139a5..1b99793e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17791,6 +17791,15 @@ query-string@^4.1.0: object-assign "^4.1.0" strict-uri-encode "^1.0.0" +query-string@^6.13.8: + version "6.13.8" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.8.tgz#8cf231759c85484da3cf05a851810d8e825c1159" + integrity sha512-jxJzQI2edQPE/NPUOusNjO/ZOGqr1o2OBa/3M00fU76FsLXDVbJDv/p7ng5OdQyorKrkRz1oqfwmbe5MAMePQg== + dependencies: + decode-uri-component "^0.2.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -20060,6 +20069,11 @@ spelling@^2.0.1: resolved "https://registry.yarnpkg.com/spelling/-/spelling-2.0.2.tgz#eaad5facd0a5330e14906b442a7933d16372b69b" integrity sha512-LMcxpEV7clACLN5kTNuZNOElJOTysEFU3dRgwn8RvLt/7nhaiSB4RzrtlxS3FEcquLsOmkn+U+KRLYZnApxCfQ== +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -20257,6 +20271,11 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= + string-length@2.0.0, string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -21243,6 +21262,13 @@ udt-format@0.0.1: is-url "^1.2.4" superstruct "^0.8.3" +udt-review-hooks@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/udt-review-hooks/-/udt-review-hooks-1.0.0.tgz#ebe1bc720888ffd87a76ed3fe441ef6384d34373" + integrity sha512-otjc+LTIEBPt4qJqrfp5xjFYSe6d+5WEpte1Yn8ZmisVt5oH5VIlIeO2zrJt8KYPkxveQFhybAoetxRAhzssJw== + dependencies: + query-string "^6.13.8" + uid-number@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" From afbb75b13104cde8460de49c4f6f176e8bd53093 Mon Sep 17 00:00:00 2001 From: seveibar Date: Mon, 11 Jan 2021 14:52:41 -0500 Subject: [PATCH 21/21] green/red rejections in audit trail, add user fix --- package.json | 2 +- .../ReviewPluginContent/AddUserDialog.js | 15 +++++++++++++-- src/components/ReviewPluginContent/Analytics.js | 9 +++++++++ .../ReviewPluginContent/AuditTrail.js | 8 +++++--- src/components/ReviewPluginContent/TeamTable.js | 17 +++++------------ yarn.lock | 8 ++++---- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index ebe327f3..4f317f7c 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "udt-collaboration-server": "^1.0.7", "udt-dataset-managers": "^1.0.15", "udt-format": "0.0.1", - "udt-review-hooks": "^1.0.0", + "udt-review-hooks": "^1.0.1", "use-async-effect": "^2.2.2", "use-event-callback": "^0.1.0", "wavesurfer.js": "^3.3.3", diff --git a/src/components/ReviewPluginContent/AddUserDialog.js b/src/components/ReviewPluginContent/AddUserDialog.js index e11fac67..8d96eedf 100644 --- a/src/components/ReviewPluginContent/AddUserDialog.js +++ b/src/components/ReviewPluginContent/AddUserDialog.js @@ -29,9 +29,13 @@ export const AddUserDialog = (props) => { const onSubmit = async () => { setLoading(true) setError(null) - await addUser(userData).catch((err) => setError(err.toString())) + try { + await addUser(userData) + onClose() + } catch (e) { + setError(e.toString()) + } setLoading(false) - onClose() } const onInputChange = (event) => { @@ -51,6 +55,13 @@ export const AddUserDialog = (props) => { ) : ( + {error && ( + + + {error} + + + )} { return ( @@ -11,6 +13,13 @@ export const Analytics = () => { ]} selectedItem="Performance Table" > + + This page isn't ready yet +
) diff --git a/src/components/ReviewPluginContent/AuditTrail.js b/src/components/ReviewPluginContent/AuditTrail.js index 5261ed16..a62c85d5 100644 --- a/src/components/ReviewPluginContent/AuditTrail.js +++ b/src/components/ReviewPluginContent/AuditTrail.js @@ -30,7 +30,7 @@ const ItemContainer = styled("div")({ const ItemText = styled("div")({}) -const getIcon = (type) => { +const getIcon = (type, item) => { switch (type) { case "label": case "work": { @@ -49,7 +49,9 @@ const getIcon = (type) => { return ( ) } @@ -73,7 +75,7 @@ export const AuditTrail = ({ selectedItem, items, onSelectItem }) => { key={i} className={selectedItem === item && "selected"} > - {getIcon(item.type)} + {getIcon(item.type, item)} {item.type === "work" && ( {item.worker_name} submitted labels )} diff --git a/src/components/ReviewPluginContent/TeamTable.js b/src/components/ReviewPluginContent/TeamTable.js index ab467cf6..220a66ed 100644 --- a/src/components/ReviewPluginContent/TeamTable.js +++ b/src/components/ReviewPluginContent/TeamTable.js @@ -16,7 +16,7 @@ import { useTeam } from "udt-review-hooks" export const TeamTable = () => { const [openAddUserDialog, setOpenAddUserDialog] = useState(false) - const { team } = useTeam() + const { team, reloadTeam } = useTeam() return ( <> @@ -53,20 +53,13 @@ export const TeamTable = () => { > Add Team Member - setOpenAddUserDialog(false)} + onClose={() => { + setOpenAddUserDialog(false) + reloadTeam() + }} /> ) diff --git a/yarn.lock b/yarn.lock index 1b99793e..df3cee73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21262,10 +21262,10 @@ udt-format@0.0.1: is-url "^1.2.4" superstruct "^0.8.3" -udt-review-hooks@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/udt-review-hooks/-/udt-review-hooks-1.0.0.tgz#ebe1bc720888ffd87a76ed3fe441ef6384d34373" - integrity sha512-otjc+LTIEBPt4qJqrfp5xjFYSe6d+5WEpte1Yn8ZmisVt5oH5VIlIeO2zrJt8KYPkxveQFhybAoetxRAhzssJw== +udt-review-hooks@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/udt-review-hooks/-/udt-review-hooks-1.0.1.tgz#c8a03296ebd9b0859eb4b93632723cd65506b693" + integrity sha512-aGgGPUFSXtQ+g6SIjciEJ8vOEFuSloJaSx7lq1jGIoDsY5XS8jGVHVqAKqQIuQj6NGuquZ1lIAe4UHtI9/uj/Q== dependencies: query-string "^6.13.8"