From b3cb5278089098a62034826fa0ac9a47aaf54931 Mon Sep 17 00:00:00 2001 From: Surat Das Date: Fri, 30 Jul 2021 07:28:30 -0700 Subject: [PATCH 1/9] Issue 280 : Implement frontend help/guide in the UI --- package-lock.json | 99 ++++++++++++++++++++++- package.json | 1 + src/App.jsx | 21 ++++- src/Router.jsx | 36 +++++++-- src/components/BuildDetails.tsx | 2 +- src/components/Filters.tsx | 6 +- src/components/Header.tsx | 61 ++++++++++++-- src/components/TestVariationMergeForm.tsx | 2 +- src/pages/LoginPage.tsx | 10 ++- src/pages/ProjectListPage.tsx | 16 +++- src/pages/ProjectPage.tsx | 29 +++++-- src/pages/TestVariationListPage.tsx | 21 ++++- 12 files changed, 267 insertions(+), 37 deletions(-) diff --git a/package-lock.json b/package-lock.json index ef9077d9..76c311b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7849,8 +7849,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "optional": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "to-regex-range": { "version": "5.0.1", @@ -9116,6 +9115,11 @@ "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" }, + "deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==" + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -10613,6 +10617,11 @@ } } }, + "exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" + }, "exit": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", @@ -12694,6 +12703,11 @@ } } }, + "is-lite": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/is-lite/-/is-lite-0.8.1.tgz", + "integrity": "sha512-ekSwuewzOmwFnzzAOWuA5fRFPqOeTrLIL3GWT7hdVVi+oLuD+Rau8gCmkb94vH5hjXc1Q/CfIW/y/td1RrNQIg==" + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -16192,6 +16206,11 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, + "nested-property": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-4.0.0.tgz", + "integrity": "sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==" + }, "next-tick": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", @@ -18553,6 +18572,40 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" }, + "react-floater": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/react-floater/-/react-floater-0.7.3.tgz", + "integrity": "sha512-d1wAEph+xRxQ0RJ3woMmYLlZHTaCIsja7Bv6JNo2ezsVUgdMan4CxOR4Do4/xgpmRFfsQMdlygexLAZZypWirw==", + "requires": { + "deepmerge": "^4.2.2", + "exenv": "^1.2.2", + "is-lite": "^0.8.1", + "popper.js": "^1.16.0", + "react-proptype-conditional-require": "^1.0.4", + "tree-changes": "^0.5.1" + }, + "dependencies": { + "nested-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nested-property/-/nested-property-1.0.1.tgz", + "integrity": "sha512-BnBBoo/8bBNRdAnJc7+m79oWk7dXwW1+vCesaEQhfDGVwXGLMvmI4NwYgLTW94R/x+R2s/yr2g/hB/4w/YSAvA==" + }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, + "tree-changes": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.5.1.tgz", + "integrity": "sha512-O873xzV2xRZ6N059Mn06QzmGKEE21LlvIPbsk2G+GS9ZX5OCur6PIwuuh0rWpAPvLWQZPj0XObyG27zZyLHUzw==", + "requires": { + "deep-diff": "^1.0.2", + "nested-property": "1.0.1" + } + } + } + }, "react-form-validator-core": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/react-form-validator-core/-/react-form-validator-core-1.0.0.tgz", @@ -18577,6 +18630,23 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-joyride": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.3.0.tgz", + "integrity": "sha512-aY7+dgmBKbgGoMjN828qXnMAqeA6QvwvSWxj/fyvxIIIx0iu3wNVT/A5NZ0wHxiVDav+Df9YZuL412Q6C0l7gw==", + "requires": { + "deep-diff": "^1.0.2", + "deepmerge": "^4.2.2", + "exenv": "^1.2.2", + "is-lite": "^0.8.0", + "nested-property": "^4.0.0", + "react-floater": "^0.7.2", + "react-is": "^16.13.1", + "scroll": "^3.0.1", + "scrollparent": "^2.0.1", + "tree-changes": "^0.6.1" + } + }, "react-konva": { "version": "16.13.0-3", "resolved": "https://registry.npmjs.org/react-konva/-/react-konva-16.13.0-3.tgz", @@ -18600,6 +18670,11 @@ "react-form-validator-core": "1.0.0" } }, + "react-proptype-conditional-require": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-proptype-conditional-require/-/react-proptype-conditional-require-1.0.4.tgz", + "integrity": "sha1-acLVdB5t9eCPIw82u8KUTuEiJVU=" + }, "react-reconciler": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.25.1.tgz", @@ -19819,6 +19894,16 @@ "ajv-keywords": "^3.5.2" } }, + "scroll": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/scroll/-/scroll-3.0.1.tgz", + "integrity": "sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==" + }, + "scrollparent": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.0.1.tgz", + "integrity": "sha1-cV1bnMV3YPsivczDvvtb/gaxoxc=" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -21300,6 +21385,16 @@ "punycode": "^2.1.1" } }, + "tree-changes": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.6.1.tgz", + "integrity": "sha512-J+eb7K2hHq8BexW71U5jPHr8RGtd7270plLBjz2a5ubsMpEhInH14k2kafHoCL+bncYS3ixGidToVwpzwC8kug==", + "requires": { + "fast-deep-equal": "^3.1.3", + "is-lite": "^0.8.0", + "react": "^16.8.0 || ^17.0.0" + } + }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", diff --git a/package.json b/package.json index 443c911e..faeb756a 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "react-debounce-input": "^3.2.2", "react-dom": "^16.13.1", "react-hotkeys-hook": "^2.4.0", + "react-joyride": "^2.3.0", "react-konva": "^16.13.0-3", "react-material-ui-form-validator": "^2.1.1", "react-router-dom": "^5.2.0", diff --git a/src/App.jsx b/src/App.jsx index 8be9c187..9c96bfe1 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -12,6 +12,21 @@ import { import Router from "./Router"; function App() { + let helpSteps = []; + + const getHelpSteps = () => { + const firstStep = helpSteps[0]; + //Below line is to prevent application breaking if element is not present for any reason (e.g. if the user deletes build or if there is no data.) + if (firstStep && document.getElementById(firstStep.target.slice(1))) { + return helpSteps; + } + return []; + }; + + const populateHelpSteps = (steps) => { + helpSteps = steps; + }; + return ( @@ -20,10 +35,12 @@ function App() { -
+
getHelpSteps()} /> - + populateHelpSteps(steps)} + /> diff --git a/src/Router.jsx b/src/Router.jsx index dfcec45d..ed61c93a 100644 --- a/src/Router.jsx +++ b/src/Router.jsx @@ -11,10 +11,18 @@ import TestVariationListPage from "./pages/TestVariationListPage"; import UserListPage from "./pages/UserListPage"; import TestVariationDetailsPage from "./pages/TestVariationDetailsPage"; -function Router() { +function Router(props) { return ( - } /> + ( + props.populateHelpSteps(steps)} + /> + )} + /> } /> } + component={() => ( + props.populateHelpSteps(steps)} + /> + )} /> } + exact + path={routes.USER_LIST_PAGE} + component={() => } /> } + component={() => ( + props.populateHelpSteps(steps)} + /> + )} /> } + component={() => ( + props.populateHelpSteps(steps)} + /> + )} /> { return ( - + {`#${selectedBuild.number} ${ diff --git a/src/components/Filters.tsx b/src/components/Filters.tsx index be9912c1..f122cf9e 100644 --- a/src/components/Filters.tsx +++ b/src/components/Filters.tsx @@ -201,7 +201,9 @@ const Filters: React.FunctionComponent = ({ id="filter_customTags" value={customTags} displayEmpty - onChange={(event) => setCustomTags(event.target.value as string)} + onChange={(event) => + setCustomTags(event.target.value as string) + } > All @@ -243,7 +245,7 @@ const Filters: React.FunctionComponent = ({ )} {branchNameList && branchNameList.length > 0 && ( - + Branch diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 33f293ed..145c59d3 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -12,17 +12,38 @@ import { Link } from "react-router-dom"; import { useAuthState, useAuthDispatch, logout } from "../contexts"; import { routes } from "../constants"; import logo from "../static/logo.png"; -import { Role } from "../types"; +import Joyride, { CallBackProps, STATUS, StoreHelpers } from 'react-joyride'; +import { HelpOutline } from "@material-ui/icons"; -const Header: FunctionComponent = () => { +const Header: FunctionComponent = (props: any) => { const [menuRef, setMenuRef] = React.useState(null); + const [run, setRun] = React.useState(false); const { loggedIn, user } = useAuthState(); const authDispatch = useAuthDispatch(); + let helpers: StoreHelpers; + + const getHelpers = (helper: StoreHelpers) => { + helpers = helper; + }; const handleMenuClose = () => { setMenuRef(null); }; + const handleClickStart = (e: React.MouseEvent) => { + e.preventDefault(); + setRun(true); + }; + + const handleJoyrideCallback = (data: CallBackProps) => { + const { status, type } = data; + const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; + + if (finishedStatuses.includes(status)) { + setRun(false); + } + }; + const renderMenu = ( { - {loggedIn && ( + ) => - setMenuRef(event.currentTarget) - } + onClick={handleClickStart} > - {`${user?.firstName[0]}${user?.lastName[0]}`} + + + + - )} + {loggedIn && ( + ) => + setMenuRef(event.currentTarget) + } + > + {`${user?.firstName[0]}${user?.lastName[0]}`} + + )} + diff --git a/src/components/TestVariationMergeForm.tsx b/src/components/TestVariationMergeForm.tsx index bad7bcb7..b06c6e8f 100644 --- a/src/components/TestVariationMergeForm.tsx +++ b/src/components/TestVariationMergeForm.tsx @@ -64,7 +64,7 @@ export const TestVariationMergeForm: React.FunctionComponent = ({ - diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 7b993e83..667b9a0b 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -5,7 +5,7 @@ import { useHistory, useLocation } from "react-router-dom"; import { useAuthState } from "../contexts"; import { routes } from "../constants"; -const LoginPage = () => { +const LoginPage = (props: any) => { const history = useHistory(); const location = useLocation<{ from: string }>(); const { loggedIn } = useAuthState(); @@ -13,8 +13,14 @@ const LoginPage = () => { from: { pathname: routes.HOME }, }; + const helpSteps = [{ + target: "#loginform-1", + content: "Get default username and password from the docker run log.", + }]; + useEffect(() => { if (loggedIn) history.replace(from); + props.populateHelpSteps(helpSteps); }); return ( @@ -26,7 +32,7 @@ const LoginPage = () => { justify="center" style={{ minHeight: "60vh" }} > - + diff --git a/src/pages/ProjectListPage.tsx b/src/pages/ProjectListPage.tsx index a0b1a29b..8946a65d 100644 --- a/src/pages/ProjectListPage.tsx +++ b/src/pages/ProjectListPage.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Grid, Typography, @@ -26,7 +26,7 @@ import { ProjectForm } from "../components/ProjectForm"; import { useSnackbar } from "notistack"; import { BaseModal } from "../components/BaseModal"; -const ProjectsListPage = () => { +const ProjectsListPage = (props: any) => { const { enqueueSnackbar } = useSnackbar(); const projectState = useProjectState(); const projectDispatch = useProjectDispatch(); @@ -35,6 +35,16 @@ const ProjectsListPage = () => { const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); + const helpSteps = [{ + target: "#projectlist-12", + content: (
By default, a project is created by docker, feel free to edit/add/delete projects.
), + title: 'Project List' + }]; + + useEffect(() => { + props.populateHelpSteps(helpSteps); + }); + const toggleCreateDialogOpen = () => { setCreateDialogOpen(!createDialogOpen); }; @@ -139,7 +149,7 @@ const ProjectsListPage = () => { {projectState.projectList.map((project) => ( - + Id: {project.id} Name: {project.name} diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index 3553675c..087f622e 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Grid, Box, makeStyles } from "@material-ui/core"; import { useParams, useHistory } from "react-router-dom"; import BuildList from "../components/BuildList"; @@ -13,22 +13,41 @@ const useStyles = makeStyles((theme) => ({ }, })); -const ProjectPage = () => { +const ProjectPage = (props: any) => { const classes = useStyles(); const { projectId } = useParams<{ projectId: string }>(); const history = useHistory(); + + const helpsteps = [{ + target: "#select-project", + content: (
Select the project for which you want to view details.
), + }, { + target: "#build-list", + content: (
List of test runs. If you see 'No Builds', please run your image comparison from any client.
), + }, { + target: "#build-details", + content: (
Details of the currently selected build.
), + }, { + target: "#test-run-list", + content: (
List of all comparisons for the selected build.
), + }]; + + useEffect(() => { + props.populateHelpSteps(helpsteps); + }); + return ( - + history.push(id)} /> - + @@ -36,7 +55,7 @@ const ProjectPage = () => { - + diff --git a/src/pages/TestVariationListPage.tsx b/src/pages/TestVariationListPage.tsx index 4a8c08c6..ae63745d 100644 --- a/src/pages/TestVariationListPage.tsx +++ b/src/pages/TestVariationListPage.tsx @@ -9,7 +9,7 @@ import Filters from "../components/Filters"; import { TestVariationMergeForm } from "../components/TestVariationMergeForm"; import { useSnackbar } from "notistack"; -const TestVariationListPage: React.FunctionComponent = () => { +const TestVariationListPage: React.FunctionComponent = (props: any) => { const history = useHistory(); const { enqueueSnackbar } = useSnackbar(); const { projectId = "" } = useParams<{ projectId: string }>(); @@ -27,8 +27,23 @@ const TestVariationListPage: React.FunctionComponent = () => { const [branchName, setBranchName] = React.useState(""); const [filteredItems, setFilteredItems] = React.useState([]); + const helpSteps = [{ + target: "#select-project", + title: 'Shows all the historical record of baselines by Name + Branch + OS + Browser + Viewport + Device', + content: (
Select the project you want to act on.
), + }, { + target: "#select-branch", + content: (
Select the branch to which you want to merge the variations.
), + }, { + target: "#reset-filter", + content: (
Only filters items are merged to the target branch.
), + }]; + + const { populateHelpSteps } = props; + React.useEffect(() => { if (projectId) { + populateHelpSteps(helpSteps); testVariationService .getList(projectId) .then((testVariations) => { @@ -40,7 +55,7 @@ const TestVariationListPage: React.FunctionComponent = () => { }) ); } - }, [projectId, enqueueSnackbar]); + }, [projectId, enqueueSnackbar, populateHelpSteps]); React.useEffect(() => { setFilteredItems( @@ -77,7 +92,7 @@ const TestVariationListPage: React.FunctionComponent = () => { - + history.push(id)} From 50b83c726d51428d19cdf8f03b490ca04b5b2894 Mon Sep 17 00:00:00 2001 From: Surat Das Date: Thu, 5 Aug 2021 12:43:39 -0700 Subject: [PATCH 2/9] Fixed review comments. --- src/App.jsx | 36 +- src/Router.jsx | 24 +- src/components/BuildDetails.tsx | 5 +- src/components/Filters.tsx | 388 +++++++++++----------- src/components/GuidedTour.tsx | 51 +++ src/components/Header.tsx | 50 +-- src/components/LoginForm.tsx | 20 +- src/components/TestVariationMergeForm.tsx | 11 +- src/contexts/build.context.tsx | 37 +-- src/contexts/help.context.tsx | 18 + src/contexts/index.ts | 1 + src/pages/LoginPage.tsx | 10 +- src/pages/ProjectListPage.tsx | 26 +- src/pages/ProjectPage.tsx | 49 ++- src/pages/TestVariationListPage.tsx | 46 ++- 15 files changed, 416 insertions(+), 356 deletions(-) create mode 100644 src/components/GuidedTour.tsx create mode 100644 src/contexts/help.context.tsx diff --git a/src/App.jsx b/src/App.jsx index 9c96bfe1..c68ffff2 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useContext } from "react"; import { SnackbarProvider } from "notistack"; import { Box } from "@material-ui/core"; import Header from "./components/Header"; @@ -10,23 +10,11 @@ import { SocketProvider, } from "./contexts"; import Router from "./Router"; +import { HelpContext } from "./contexts/help.context"; function App() { - let helpSteps = []; - - const getHelpSteps = () => { - const firstStep = helpSteps[0]; - //Below line is to prevent application breaking if element is not present for any reason (e.g. if the user deletes build or if there is no data.) - if (firstStep && document.getElementById(firstStep.target.slice(1))) { - return helpSteps; - } - return []; - }; - - const populateHelpSteps = (steps) => { - helpSteps = steps; - }; - + const { getHelpSteps } = useContext(HelpContext); + const { populateHelpSteps } = useContext(HelpContext); return ( @@ -34,14 +22,14 @@ function App() { - -
getHelpSteps()} /> - - - populateHelpSteps(steps)} - /> - + + +
+ + + + + diff --git a/src/Router.jsx b/src/Router.jsx index ed61c93a..9e35a4de 100644 --- a/src/Router.jsx +++ b/src/Router.jsx @@ -11,16 +11,14 @@ import TestVariationListPage from "./pages/TestVariationListPage"; import UserListPage from "./pages/UserListPage"; import TestVariationDetailsPage from "./pages/TestVariationDetailsPage"; -function Router(props) { +function Router() { return ( ( - props.populateHelpSteps(steps)} - /> + )} /> } /> @@ -34,32 +32,26 @@ function Router(props) { exact path={routes.PROJECT_LIST_PAGE} component={() => ( - props.populateHelpSteps(steps)} - /> + )} /> } + exact + path={routes.USER_LIST_PAGE} + component={() => } /> ( - props.populateHelpSteps(steps)} - /> + )} /> ( - props.populateHelpSteps(steps)} - /> + )} /> { } const loadingAnimation = selectedBuild.isRunning && ; + const locatorBuildDetails = "build-details"; return ( - + - + {`#${selectedBuild.number} ${ diff --git a/src/components/Filters.tsx b/src/components/Filters.tsx index f122cf9e..d5a3a6d0 100644 --- a/src/components/Filters.tsx +++ b/src/components/Filters.tsx @@ -43,10 +43,10 @@ const Filters: React.FunctionComponent = ({ const [customTags, setCustomTags] = customTagsState; const [testStatus, setTestStatus] = testStatusState ? testStatusState - : [null, () => { }]; + : [null, () => {}]; const [branchName, setBranchName] = branchNameState ? branchNameState - : [null, () => { }]; + : [null, () => {}]; const osList = items .map((t) => t.os) @@ -81,212 +81,208 @@ const Filters: React.FunctionComponent = ({ .map((t) => t.branchName) .filter((v, i, array) => v && array.indexOf(v) === i); + const locatorResetFilter = "reset-filter"; + return ( - + + + setQuery(event?.target.value)} + /> + + {osList.length > 0 && ( - setQuery(event?.target.value)} - /> - - {osList.length > 0 && ( - - - - OS - - setOs(event.target.value as string)} + > + + All + + {osList.map((os) => ( + + {os} - {osList.map((os) => ( - - {os} - - ))} - - - - )} - {deviceList.length > 0 && ( - - - - Device - - + + + )} + {deviceList.length > 0 && ( + + + + Device + + - - - )} - {browserList.length > 0 && ( - - - - Browser - - + + + )} + {browserList.length > 0 && ( + + + + Browser + + - - - )} - {viewportList.length > 0 && ( - - - - Viewport - - + + + )} + {viewportList.length > 0 && ( + + + + Viewport + + - - - )} - {customTagsList.length > 0 && ( - - - - Custom Tags - - + + + )} + {customTagsList.length > 0 && ( + + + + Custom Tags + + - - - )} - {testStatusList && testStatusList.length > 0 && ( - - - - Status - - + + + )} + {testStatusList && testStatusList.length > 0 && ( + + + + Status + + - - - )} - {branchNameList && branchNameList.length > 0 && ( - - - - Branch - - + + + )} + {branchNameList && branchNameList.length > 0 && ( + + + + Branch + + - - - )} - - + ))} + + + )} + + + ); }; diff --git a/src/components/GuidedTour.tsx b/src/components/GuidedTour.tsx new file mode 100644 index 00000000..885ed652 --- /dev/null +++ b/src/components/GuidedTour.tsx @@ -0,0 +1,51 @@ +import React, { useContext, FunctionComponent } from "react"; +import Joyride, { CallBackProps, STATUS } from "react-joyride"; +import { IconButton, Avatar, } from "@material-ui/core"; +import { HelpOutline } from "@material-ui/icons"; +import { HelpContext } from "../contexts/help.context"; + +const GuidedTour: FunctionComponent = () => { + const [run, setRun] = React.useState(false); + + const handleJoyrideCallback = (data: CallBackProps) => { + const { status } = data; + const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; + + if (finishedStatuses.includes(status)) { + setRun(false); + } + }; + + const handleClickStart = (e: React.MouseEvent) => { + e.preventDefault(); + setRun(true); + }; + + const { getHelpSteps } = useContext(HelpContext); + + return ( + + + + + + + + + ); +} + +export default GuidedTour; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 145c59d3..315b3c46 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -12,38 +12,19 @@ import { Link } from "react-router-dom"; import { useAuthState, useAuthDispatch, logout } from "../contexts"; import { routes } from "../constants"; import logo from "../static/logo.png"; -import Joyride, { CallBackProps, STATUS, StoreHelpers } from 'react-joyride'; -import { HelpOutline } from "@material-ui/icons"; +import { Role } from "../types"; +import GuidedTour from "./GuidedTour"; -const Header: FunctionComponent = (props: any) => { + +const Header: FunctionComponent = () => { const [menuRef, setMenuRef] = React.useState(null); - const [run, setRun] = React.useState(false); const { loggedIn, user } = useAuthState(); const authDispatch = useAuthDispatch(); - let helpers: StoreHelpers; - - const getHelpers = (helper: StoreHelpers) => { - helpers = helper; - }; const handleMenuClose = () => { setMenuRef(null); }; - const handleClickStart = (e: React.MouseEvent) => { - e.preventDefault(); - setRun(true); - }; - - const handleJoyrideCallback = (data: CallBackProps) => { - const { status, type } = data; - const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; - - if (finishedStatuses.includes(status)) { - setRun(false); - } - }; - const renderMenu = ( { - - - - - - + {loggedIn && ( ) => diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index c99503e1..da5d0566 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -1,4 +1,4 @@ -import React, { useState, FormEvent } from "react"; +import React, { useState, FormEvent, useEffect, useContext } from "react"; import { Link } from "react-router-dom"; import { Button, @@ -8,7 +8,7 @@ import { CardActions, Typography, } from "@material-ui/core"; -import { useAuthDispatch, login } from "../contexts"; +import { useAuthDispatch, login, HelpContext } from "../contexts"; import { routes } from "../constants"; import { useSnackbar } from "notistack"; import { TextValidator, ValidatorForm } from "react-material-ui-form-validator"; @@ -28,12 +28,26 @@ const LoginForm = () => { ); }; + const helpSteps = [ + { + target: "#loginform-1", + content: "Default admin account: visual-regression-tracker@example.com / 123456. Make sure to change it's password.", + }, + ]; + + useEffect(() => { + populateHelpSteps(helpSteps); + }); + + const { populateHelpSteps } = useContext(HelpContext); + const errorForTwoChar = "Enter at least two characters."; + const locatorLoginForm = "loginform-1"; return ( - + = ({ const { enqueueSnackbar } = useSnackbar(); const [branch, setBranch] = React.useState(""); + const locatorSelectBranch = "select-branch"; + const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); testVariationService @@ -46,7 +48,7 @@ export const TestVariationMergeForm: React.FunctionComponent = ({ return (
- + - diff --git a/src/contexts/build.context.tsx b/src/contexts/build.context.tsx index 3adf2f3e..a599e19d 100644 --- a/src/contexts/build.context.tsx +++ b/src/contexts/build.context.tsx @@ -105,28 +105,27 @@ function buildReducer(state: State, action: IAction): State { total, loading: false, }; - case "delete": - { - let buildList = state.buildList; - let indexOfBuildDeleted = buildList.findIndex( - (e) => e.id === action.payload - ); - let indexOfSelectedBuild = buildList.findIndex( - (e) => e.id === state.selectedBuildId - ); - if (indexOfBuildDeleted === indexOfSelectedBuild) { - let buildToSelect = null; - if (buildList.length > 1) { + case "delete": { + let buildList = state.buildList; + let indexOfBuildDeleted = buildList.findIndex( + (e) => e.id === action.payload + ); + let indexOfSelectedBuild = buildList.findIndex( + (e) => e.id === state.selectedBuildId + ); + if (indexOfBuildDeleted === indexOfSelectedBuild) { + let buildToSelect = null; + if (buildList.length > 1) { buildToSelect = (buildList.length === 0) ? buildList[1] : buildList[indexOfSelectedBuild - 1]; - } - state.selectedBuild = buildToSelect; - state.selectedBuildId = buildToSelect?.id ?? null; } - return { - ...state, - buildList: state.buildList.filter((p) => p.id !== action.payload), - }; + state.selectedBuild = buildToSelect; + state.selectedBuildId = buildToSelect?.id ?? null; } + return { + ...state, + buildList: state.buildList.filter((p) => p.id !== action.payload), + }; + } case "add": return { ...state, diff --git a/src/contexts/help.context.tsx b/src/contexts/help.context.tsx new file mode 100644 index 00000000..d75b920e --- /dev/null +++ b/src/contexts/help.context.tsx @@ -0,0 +1,18 @@ +import { createContext } from 'react'; + +let helpSteps: any[] = []; + +let getHelpSteps = () => { + const firstStep = helpSteps[0]; + //Below line is to prevent application breaking if element is not present for any reason (e.g. if the user deletes build or if there is no data.) + if (firstStep && document.getElementById(firstStep.target.slice(1))) { + return helpSteps; + } + return []; +}; + +const populateHelpSteps = (steps: any) => { + helpSteps = steps; +}; + +export const HelpContext = createContext({ getHelpSteps, populateHelpSteps }); diff --git a/src/contexts/index.ts b/src/contexts/index.ts index d05bffcb..1f2adbaa 100644 --- a/src/contexts/index.ts +++ b/src/contexts/index.ts @@ -3,3 +3,4 @@ export * from "./build.context"; export * from "./project.context"; export * from "./testRun.context"; export * from "./socket.context"; +export * from "./help.context"; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 667b9a0b..7b993e83 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -5,7 +5,7 @@ import { useHistory, useLocation } from "react-router-dom"; import { useAuthState } from "../contexts"; import { routes } from "../constants"; -const LoginPage = (props: any) => { +const LoginPage = () => { const history = useHistory(); const location = useLocation<{ from: string }>(); const { loggedIn } = useAuthState(); @@ -13,14 +13,8 @@ const LoginPage = (props: any) => { from: { pathname: routes.HOME }, }; - const helpSteps = [{ - target: "#loginform-1", - content: "Get default username and password from the docker run log.", - }]; - useEffect(() => { if (loggedIn) history.replace(from); - props.populateHelpSteps(helpSteps); }); return ( @@ -32,7 +26,7 @@ const LoginPage = (props: any) => { justify="center" style={{ minHeight: "60vh" }} > - + diff --git a/src/pages/ProjectListPage.tsx b/src/pages/ProjectListPage.tsx index 8946a65d..8e35c6ac 100644 --- a/src/pages/ProjectListPage.tsx +++ b/src/pages/ProjectListPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useContext, useEffect } from "react"; import { Grid, Typography, @@ -17,6 +17,7 @@ import { createProject, updateProject, setProjectEditState, + HelpContext, } from "../contexts"; import { Link } from "react-router-dom"; import { Delete, Add, Edit } from "@material-ui/icons"; @@ -26,7 +27,7 @@ import { ProjectForm } from "../components/ProjectForm"; import { useSnackbar } from "notistack"; import { BaseModal } from "../components/BaseModal"; -const ProjectsListPage = (props: any) => { +const ProjectsListPage = () => { const { enqueueSnackbar } = useSnackbar(); const projectState = useProjectState(); const projectDispatch = useProjectDispatch(); @@ -35,14 +36,23 @@ const ProjectsListPage = (props: any) => { const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); - const helpSteps = [{ - target: "#projectlist-12", - content: (
By default, a project is created by docker, feel free to edit/add/delete projects.
), - title: 'Project List' - }]; + const helpSteps = [ + { + target: "#projectlist-1", + content: ( +
+ By default, a project is created by docker, feel free to + edit/add/delete projects. +
+ ), + title: "Project List", + }, + ]; + + const { populateHelpSteps } = useContext(HelpContext); useEffect(() => { - props.populateHelpSteps(helpSteps); + populateHelpSteps(helpSteps); }); const toggleCreateDialogOpen = () => { diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index 087f622e..96916832 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React, { useEffect, useContext } from "react"; import { Grid, Box, makeStyles } from "@material-ui/core"; import { useParams, useHistory } from "react-router-dom"; import BuildList from "../components/BuildList"; @@ -6,6 +6,7 @@ import ProjectSelect from "../components/ProjectSelect"; import TestRunList from "../components/TestRunList"; import BuildDetails from "../components/BuildDetails"; import { TestDetailsDialog } from "../components/TestDetailsDialog"; +import { HelpContext } from "../contexts/help.context"; const useStyles = makeStyles((theme) => ({ root: { @@ -13,28 +14,42 @@ const useStyles = makeStyles((theme) => ({ }, })); -const ProjectPage = (props: any) => { +const ProjectPage = () => { const classes = useStyles(); const { projectId } = useParams<{ projectId: string }>(); const history = useHistory(); + const helpSteps = [ + { + target: "#select-project", + content: ( +
Select the project for which you want to view details.
+ ), + }, + { + target: "#build-list", + title:"List of test runs", + content: ( +
+ If you see 'No Builds', please run your image + comparison from any client. +
+ ), + }, + { + target: "#build-details", + content:
Details of the currently selected build.
, + }, + { + target: "#test-run-list", + content:
On selecting a build, shows all comparisons for the selected build.
, + }, + ]; - const helpsteps = [{ - target: "#select-project", - content: (
Select the project for which you want to view details.
), - }, { - target: "#build-list", - content: (
List of test runs. If you see 'No Builds', please run your image comparison from any client.
), - }, { - target: "#build-details", - content: (
Details of the currently selected build.
), - }, { - target: "#test-run-list", - content: (
List of all comparisons for the selected build.
), - }]; + const { populateHelpSteps } = useContext(HelpContext); useEffect(() => { - props.populateHelpSteps(helpsteps); + populateHelpSteps(helpSteps); }); return ( @@ -47,7 +62,7 @@ const ProjectPage = (props: any) => { onProjectSelect={(id) => history.push(id)} /> - +
diff --git a/src/pages/TestVariationListPage.tsx b/src/pages/TestVariationListPage.tsx index ae63745d..f2d92716 100644 --- a/src/pages/TestVariationListPage.tsx +++ b/src/pages/TestVariationListPage.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useContext } from "react"; import TestVariationList from "../components/TestVariationList"; import { useHistory, useParams } from "react-router-dom"; import { TestVariation } from "../types"; @@ -8,8 +8,9 @@ import ProjectSelect from "../components/ProjectSelect"; import Filters from "../components/Filters"; import { TestVariationMergeForm } from "../components/TestVariationMergeForm"; import { useSnackbar } from "notistack"; +import { HelpContext } from "../contexts/help.context"; -const TestVariationListPage: React.FunctionComponent = (props: any) => { +const TestVariationListPage: React.FunctionComponent = () => { const history = useHistory(); const { enqueueSnackbar } = useSnackbar(); const { projectId = "" } = useParams<{ projectId: string }>(); @@ -27,23 +28,36 @@ const TestVariationListPage: React.FunctionComponent = (props: any) => { const [branchName, setBranchName] = React.useState(""); const [filteredItems, setFilteredItems] = React.useState([]); - const helpSteps = [{ - target: "#select-project", - title: 'Shows all the historical record of baselines by Name + Branch + OS + Browser + Viewport + Device', - content: (
Select the project you want to act on.
), - }, { - target: "#select-branch", - content: (
Select the branch to which you want to merge the variations.
), - }, { - target: "#reset-filter", - content: (
Only filters items are merged to the target branch.
), - }]; + const locatorSelectProject = "select-project"; - const { populateHelpSteps } = props; + const helpSteps = [ + { + target: "#select-project", + title: + "Shows all the historical record of baselines by Name + Branch + OS + Browser + Viewport + Device", + content:
Select the project you want to act on.
, + }, + { + target: "#select-branch", + title: "Merge from one branch to another", + content: ( +
Select the branch from/to which you want to merge the variations.
+ ), + }, + { + target: "#reset-filter", + content:
Only filtered items are displayed/merged to the target branch.
, + }, + ]; + + const { populateHelpSteps } = useContext(HelpContext); + + React.useEffect(() => { + populateHelpSteps(helpSteps); + }); React.useEffect(() => { if (projectId) { - populateHelpSteps(helpSteps); testVariationService .getList(projectId) .then((testVariations) => { @@ -92,7 +106,7 @@ const TestVariationListPage: React.FunctionComponent = (props: any) => { - + history.push(id)} From f1dd57b03ef1a659c0a53e0ab0566215bb55be1f Mon Sep 17 00:00:00 2001 From: Surat Das Date: Thu, 5 Aug 2021 13:20:13 -0700 Subject: [PATCH 3/9] Fixed Codacy errors. --- src/Router.jsx | 20 +++--------- src/components/GuidedTour.tsx | 2 +- src/components/LoginForm.tsx | 2 +- src/components/TestVariationMergeForm.tsx | 7 +---- src/contexts/build.context.tsx | 37 ++++++++++++----------- 5 files changed, 26 insertions(+), 42 deletions(-) diff --git a/src/Router.jsx b/src/Router.jsx index 9e35a4de..dfcec45d 100644 --- a/src/Router.jsx +++ b/src/Router.jsx @@ -14,13 +14,7 @@ import TestVariationDetailsPage from "./pages/TestVariationDetailsPage"; function Router() { return ( - ( - - )} - /> + } /> } /> ( - - )} + component={() => } /> ( - - )} + component={() => } /> ( - - )} + component={() => } /> { ); -} +}; export default GuidedTour; diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 92976a50..81f3046c 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -8,7 +8,7 @@ import { CardActions, Typography, } from "@material-ui/core"; -import { useAuthDispatch, login, HelpContext } from "../contexts"; +import { useUserDispatch, login, HelpContext } from "../contexts"; import { routes } from "../constants"; import { useSnackbar } from "notistack"; import { TextValidator, ValidatorForm } from "react-material-ui-form-validator"; diff --git a/src/components/TestVariationMergeForm.tsx b/src/components/TestVariationMergeForm.tsx index 3f41b84c..b3e3f8e4 100644 --- a/src/components/TestVariationMergeForm.tsx +++ b/src/components/TestVariationMergeForm.tsx @@ -85,12 +85,7 @@ export const TestVariationMergeForm: React.FunctionComponent = ({ />
- diff --git a/src/contexts/build.context.tsx b/src/contexts/build.context.tsx index a599e19d..3adf2f3e 100644 --- a/src/contexts/build.context.tsx +++ b/src/contexts/build.context.tsx @@ -105,27 +105,28 @@ function buildReducer(state: State, action: IAction): State { total, loading: false, }; - case "delete": { - let buildList = state.buildList; - let indexOfBuildDeleted = buildList.findIndex( - (e) => e.id === action.payload - ); - let indexOfSelectedBuild = buildList.findIndex( - (e) => e.id === state.selectedBuildId - ); - if (indexOfBuildDeleted === indexOfSelectedBuild) { - let buildToSelect = null; - if (buildList.length > 1) { + case "delete": + { + let buildList = state.buildList; + let indexOfBuildDeleted = buildList.findIndex( + (e) => e.id === action.payload + ); + let indexOfSelectedBuild = buildList.findIndex( + (e) => e.id === state.selectedBuildId + ); + if (indexOfBuildDeleted === indexOfSelectedBuild) { + let buildToSelect = null; + if (buildList.length > 1) { buildToSelect = (buildList.length === 0) ? buildList[1] : buildList[indexOfSelectedBuild - 1]; + } + state.selectedBuild = buildToSelect; + state.selectedBuildId = buildToSelect?.id ?? null; } - state.selectedBuild = buildToSelect; - state.selectedBuildId = buildToSelect?.id ?? null; + return { + ...state, + buildList: state.buildList.filter((p) => p.id !== action.payload), + }; } - return { - ...state, - buildList: state.buildList.filter((p) => p.id !== action.payload), - }; - } case "add": return { ...state, From f76f5bd826c1988c7b3492f60df824a357e06e40 Mon Sep 17 00:00:00 2001 From: Surat Das Date: Fri, 6 Aug 2021 13:29:26 -0700 Subject: [PATCH 4/9] Removed extra click on beakon and help button style change. --- package.json | 2 +- src/components/GuidedTour.tsx | 3 +++ src/contexts/help.context.tsx | 11 ++++++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 153589df..e44c7e86 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "react-debounce-input": "^3.2.2", "react-dom": "^16.13.1", "react-hotkeys-hook": "^2.4.0", - "react-joyride": "^2.3.0", + "react-joyride": "^2.3.1", "react-konva": "^16.13.0-3", "react-material-ui-form-validator": "^2.1.1", "react-router-dom": "^5.2.0", diff --git a/src/components/GuidedTour.tsx b/src/components/GuidedTour.tsx index a165b203..460976d7 100644 --- a/src/components/GuidedTour.tsx +++ b/src/components/GuidedTour.tsx @@ -36,10 +36,13 @@ const GuidedTour: FunctionComponent = () => { showProgress={true} showSkipButton={true} steps={getHelpSteps()} + disableCloseOnEsc={true} styles={{ options: { zIndex: 10000, }, + buttonNext: { color: "#3f51b5", backgroundColor: "" }, + buttonBack: { color: "#3f51b5" }, }} /> diff --git a/src/contexts/help.context.tsx b/src/contexts/help.context.tsx index d75b920e..7d14508b 100644 --- a/src/contexts/help.context.tsx +++ b/src/contexts/help.context.tsx @@ -1,11 +1,16 @@ import { createContext } from 'react'; +import { Step } from 'react-joyride'; -let helpSteps: any[] = []; +let helpSteps: Step[] = []; -let getHelpSteps = () => { +let getHelpSteps = (): Step[] => { const firstStep = helpSteps[0]; //Below line is to prevent application breaking if element is not present for any reason (e.g. if the user deletes build or if there is no data.) - if (firstStep && document.getElementById(firstStep.target.slice(1))) { + if (firstStep && document.getElementById(firstStep.target.toString().slice(1))) { + helpSteps.every((e) => { + e.disableBeacon = true; + e.hideCloseButton = true; + }); return helpSteps; } return []; From 341474fbe35ee4f0f376b279ff3dea38f9a272b7 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Sat, 7 Aug 2021 22:29:52 +0300 Subject: [PATCH 5/9] Help context refactor propose --- package-lock.json | 33 +++++----- src/App.jsx | 12 ++-- src/components/GuidedTour.tsx | 99 +++++++++++++++++------------ src/components/LoginForm.tsx | 19 ++++-- src/contexts/help.context.tsx | 84 ++++++++++++++++++------ src/pages/ProjectListPage.tsx | 10 +-- src/pages/ProjectPage.tsx | 21 +++--- src/pages/TestVariationListPage.tsx | 32 +++++++--- 8 files changed, 195 insertions(+), 115 deletions(-) diff --git a/package-lock.json b/package-lock.json index d00e4ffd..1fb7ea5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7849,7 +7849,8 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "optional": true }, "to-regex-range": { "version": "5.0.1", @@ -18631,20 +18632,20 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-joyride": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.3.0.tgz", - "integrity": "sha512-aY7+dgmBKbgGoMjN828qXnMAqeA6QvwvSWxj/fyvxIIIx0iu3wNVT/A5NZ0wHxiVDav+Df9YZuL412Q6C0l7gw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/react-joyride/-/react-joyride-2.3.1.tgz", + "integrity": "sha512-MmyhECU3V+4kZAJrcDPPXcXxaoTpwc7g+E7Cq6QZ5IqJZrWYSVvpVCfudQcdcf6BsNbgawRhvCvbQyeWoPtNig==", "requires": { "deep-diff": "^1.0.2", "deepmerge": "^4.2.2", "exenv": "^1.2.2", - "is-lite": "^0.8.0", + "is-lite": "^0.8.1", "nested-property": "^4.0.0", - "react-floater": "^0.7.2", + "react-floater": "^0.7.3", "react-is": "^16.13.1", "scroll": "^3.0.1", "scrollparent": "^2.0.1", - "tree-changes": "^0.6.1" + "tree-changes": "^0.7.1" } }, "react-konva": { @@ -21007,9 +21008,9 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, "tar": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz", - "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz", + "integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==", "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -21386,13 +21387,12 @@ } }, "tree-changes": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.6.1.tgz", - "integrity": "sha512-J+eb7K2hHq8BexW71U5jPHr8RGtd7270plLBjz2a5ubsMpEhInH14k2kafHoCL+bncYS3ixGidToVwpzwC8kug==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/tree-changes/-/tree-changes-0.7.1.tgz", + "integrity": "sha512-sPIt8PKDi0OQTglr7lsetcB9DU19Ls/ZgFSjFvK6DWJGisAn4sOxtjpmQfuqjexQE4UU9U53LNmataL1kRJ3Uw==", "requires": { "fast-deep-equal": "^3.1.3", - "is-lite": "^0.8.0", - "react": "^16.8.0 || ^17.0.0" + "is-lite": "^0.8.1" } }, "tryer": { @@ -22152,8 +22152,7 @@ }, "ssri": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "resolved": "", "requires": { "figgy-pudding": "^3.5.1" } diff --git a/src/App.jsx b/src/App.jsx index 3059a631..c0ebb34c 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React from "react"; import { SnackbarProvider } from "notistack"; import { Box } from "@material-ui/core"; import Header from "./components/Header"; @@ -8,13 +8,11 @@ import { BuildProvider, TestRunProvider, SocketProvider, + HelpProvider, } from "./contexts"; import Router from "./Router"; -import { HelpContext } from "./contexts/help.context"; function App() { - const { getHelpSteps } = useContext(HelpContext); - const { populateHelpSteps } = useContext(HelpContext); return ( @@ -22,14 +20,14 @@ function App() { - +
- + - + diff --git a/src/components/GuidedTour.tsx b/src/components/GuidedTour.tsx index 460976d7..b4f106e7 100644 --- a/src/components/GuidedTour.tsx +++ b/src/components/GuidedTour.tsx @@ -1,54 +1,69 @@ -import React, { useContext, FunctionComponent } from "react"; +import React, { FunctionComponent } from "react"; import Joyride, { CallBackProps, STATUS } from "react-joyride"; -import { IconButton, Avatar, } from "@material-ui/core"; +import { IconButton, Avatar } from "@material-ui/core"; import { HelpOutline } from "@material-ui/icons"; -import { HelpContext } from "../contexts/help.context"; +import { useHelpState } from "../contexts"; const GuidedTour: FunctionComponent = () => { - const [run, setRun] = React.useState(false); + const [run, setRun] = React.useState(false); + const { helpSteps } = useHelpState(); - const handleJoyrideCallback = (data: CallBackProps) => { - const { status } = data; - const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; + const getHelpSteps = React.useCallback(() => { + const firstStep = helpSteps[0]; + //Below line is to prevent application breaking if element is not present for any reason (e.g. if the user deletes build or if there is no data.) + if ( + firstStep && + document.getElementById(firstStep.target.toString().slice(1)) + ) { + helpSteps.every((e) => { + e.disableBeacon = true; + e.hideCloseButton = true; + }); + return helpSteps; + } + return []; + }, [helpSteps]); - if (finishedStatuses.includes(status)) { - setRun(false); - } - }; + const handleJoyrideCallback = (data: CallBackProps) => { + const { status } = data; + const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; - const handleClickStart = (e: React.MouseEvent) => { - e.preventDefault(); - setRun(true); - }; + if (finishedStatuses.includes(status)) { + setRun(false); + } + }; - const { getHelpSteps } = useContext(HelpContext); + const handleClickStart = (e: React.MouseEvent) => { + e.preventDefault(); + setRun(true); + }; - return ( - - - - - - - - - ); + return ( + + + + + + + + + ); }; export default GuidedTour; diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 81f3046c..dd50f4b0 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -1,4 +1,4 @@ -import React, { useState, FormEvent, useEffect, useContext } from "react"; +import React, { useState, FormEvent, useEffect } from "react"; import { Link } from "react-router-dom"; import { Button, @@ -8,7 +8,12 @@ import { CardActions, Typography, } from "@material-ui/core"; -import { useUserDispatch, login, HelpContext } from "../contexts"; +import { + useUserDispatch, + login, + setHelpSteps, + useHelpDispatch, +} from "../contexts"; import { routes } from "../constants"; import { useSnackbar } from "notistack"; import { TextValidator, ValidatorForm } from "react-material-ui-form-validator"; @@ -18,6 +23,7 @@ const LoginForm = () => { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const dispatch = useUserDispatch(); + const helpDispatch = useHelpDispatch(); const handleSubmit = (event: FormEvent) => { event.preventDefault(); @@ -31,16 +37,15 @@ const LoginForm = () => { const helpSteps = [ { target: "#loginform-1", - content: "Default admin account: visual-regression-tracker@example.com / 123456. Make sure to change it's password.", + content: + "Default admin account: visual-regression-tracker@example.com / 123456. Make sure to change it's password.", }, ]; - + useEffect(() => { - populateHelpSteps(helpSteps); + setHelpSteps(helpDispatch, helpSteps); }); - const { populateHelpSteps } = useContext(HelpContext); - const errorForTwoChar = "Enter at least two characters."; const locatorLoginForm = "loginform-1"; diff --git a/src/contexts/help.context.tsx b/src/contexts/help.context.tsx index 7d14508b..741d1b54 100644 --- a/src/contexts/help.context.tsx +++ b/src/contexts/help.context.tsx @@ -1,23 +1,69 @@ -import { createContext } from 'react'; -import { Step } from 'react-joyride'; - -let helpSteps: Step[] = []; - -let getHelpSteps = (): Step[] => { - const firstStep = helpSteps[0]; - //Below line is to prevent application breaking if element is not present for any reason (e.g. if the user deletes build or if there is no data.) - if (firstStep && document.getElementById(firstStep.target.toString().slice(1))) { - helpSteps.every((e) => { - e.disableBeacon = true; - e.hideCloseButton = true; - }); - return helpSteps; - } - return []; +import React from "react"; +import { Step } from "react-joyride"; + +interface ISetStepAction { + type: "setSteps"; + payload: Array; +} + +type IAction = ISetStepAction; + +type Dispatch = (action: IAction) => void; +type State = { + helpSteps: Array; }; -const populateHelpSteps = (steps: any) => { - helpSteps = steps; +type HelpProviderProps = { children: React.ReactNode }; + +const StateContext = React.createContext(undefined); +const DispatchContext = React.createContext(undefined); + +const initialState: State = { + helpSteps: [], }; -export const HelpContext = createContext({ getHelpSteps, populateHelpSteps }); +function reducer(state: State, action: IAction): State { + switch (action.type) { + case "setSteps": + return { + ...state, + helpSteps: action.payload, + }; + default: + return state; + } +} + +function HelpProvider({ children }: HelpProviderProps) { + const [state, dispatch] = React.useReducer(reducer, initialState); + + return ( + + + {children} + + + ); +} + +function useHelpState() { + const context = React.useContext(StateContext); + if (context === undefined) { + throw new Error("must be used within a HelpContext"); + } + return context; +} + +function useHelpDispatch() { + const context = React.useContext(DispatchContext); + if (context === undefined) { + throw new Error("must be used within a HelpContext"); + } + return context; +} + +function setHelpSteps(dispatch: Dispatch, data: Array) { + dispatch({ type: "setSteps", payload: data }); +} + +export { HelpProvider, useHelpDispatch, useHelpState, setHelpSteps }; diff --git a/src/pages/ProjectListPage.tsx b/src/pages/ProjectListPage.tsx index 8e35c6ac..538ae435 100644 --- a/src/pages/ProjectListPage.tsx +++ b/src/pages/ProjectListPage.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect } from "react"; +import React, { useEffect } from "react"; import { Grid, Typography, @@ -17,7 +17,8 @@ import { createProject, updateProject, setProjectEditState, - HelpContext, + useHelpDispatch, + setHelpSteps, } from "../contexts"; import { Link } from "react-router-dom"; import { Delete, Add, Edit } from "@material-ui/icons"; @@ -31,6 +32,7 @@ const ProjectsListPage = () => { const { enqueueSnackbar } = useSnackbar(); const projectState = useProjectState(); const projectDispatch = useProjectDispatch(); + const helpDispatch = useHelpDispatch(); const [createDialogOpen, setCreateDialogOpen] = React.useState(false); const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false); @@ -49,10 +51,8 @@ const ProjectsListPage = () => { }, ]; - const { populateHelpSteps } = useContext(HelpContext); - useEffect(() => { - populateHelpSteps(helpSteps); + setHelpSteps(helpDispatch, helpSteps); }); const toggleCreateDialogOpen = () => { diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index 96916832..0eae9055 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useContext } from "react"; +import React, { useEffect } from "react"; import { Grid, Box, makeStyles } from "@material-ui/core"; import { useParams, useHistory } from "react-router-dom"; import BuildList from "../components/BuildList"; @@ -6,7 +6,7 @@ import ProjectSelect from "../components/ProjectSelect"; import TestRunList from "../components/TestRunList"; import BuildDetails from "../components/BuildDetails"; import { TestDetailsDialog } from "../components/TestDetailsDialog"; -import { HelpContext } from "../contexts/help.context"; +import { useHelpDispatch, setHelpSteps } from "../contexts"; const useStyles = makeStyles((theme) => ({ root: { @@ -18,6 +18,7 @@ const ProjectPage = () => { const classes = useStyles(); const { projectId } = useParams<{ projectId: string }>(); const history = useHistory(); + const helpDispatch = useHelpDispatch(); const helpSteps = [ { @@ -28,11 +29,11 @@ const ProjectPage = () => { }, { target: "#build-list", - title:"List of test runs", + title: "List of test runs", content: (
- If you see 'No Builds', please run your image - comparison from any client. + If you see 'No Builds', please run your image comparison from any + client.
), }, @@ -42,14 +43,16 @@ const ProjectPage = () => { }, { target: "#test-run-list", - content:
On selecting a build, shows all comparisons for the selected build.
, + content: ( +
+ On selecting a build, shows all comparisons for the selected build. +
+ ), }, ]; - const { populateHelpSteps } = useContext(HelpContext); - useEffect(() => { - populateHelpSteps(helpSteps); + setHelpSteps(helpDispatch, helpSteps); }); return ( diff --git a/src/pages/TestVariationListPage.tsx b/src/pages/TestVariationListPage.tsx index f2d92716..592564dd 100644 --- a/src/pages/TestVariationListPage.tsx +++ b/src/pages/TestVariationListPage.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React from "react"; import TestVariationList from "../components/TestVariationList"; import { useHistory, useParams } from "react-router-dom"; import { TestVariation } from "../types"; @@ -8,11 +8,12 @@ import ProjectSelect from "../components/ProjectSelect"; import Filters from "../components/Filters"; import { TestVariationMergeForm } from "../components/TestVariationMergeForm"; import { useSnackbar } from "notistack"; -import { HelpContext } from "../contexts/help.context"; +import { setHelpSteps, useHelpDispatch } from "../contexts/help.context"; const TestVariationListPage: React.FunctionComponent = () => { const history = useHistory(); const { enqueueSnackbar } = useSnackbar(); + const helpDispatch = useHelpDispatch(); const { projectId = "" } = useParams<{ projectId: string }>(); const [testVariations, setTestVariations] = React.useState( [] @@ -41,19 +42,23 @@ const TestVariationListPage: React.FunctionComponent = () => { target: "#select-branch", title: "Merge from one branch to another", content: ( -
Select the branch from/to which you want to merge the variations.
+
+ Select the branch from/to which you want to merge the variations. +
), }, { target: "#reset-filter", - content:
Only filtered items are displayed/merged to the target branch.
, + content: ( +
+ Only filtered items are displayed/merged to the target branch. +
+ ), }, ]; - const { populateHelpSteps } = useContext(HelpContext); - React.useEffect(() => { - populateHelpSteps(helpSteps); + setHelpSteps(helpDispatch, helpSteps); }); React.useEffect(() => { @@ -69,7 +74,7 @@ const TestVariationListPage: React.FunctionComponent = () => { }) ); } - }, [projectId, enqueueSnackbar, populateHelpSteps]); + }, [projectId, enqueueSnackbar]); React.useEffect(() => { setFilteredItems( @@ -84,7 +89,16 @@ const TestVariationListPage: React.FunctionComponent = () => { (browser ? t.browser === browser : true) // by browser ) ); - }, [query, branchName, os, device, browser, viewport, customTags, testVariations]); + }, [ + query, + branchName, + os, + device, + browser, + viewport, + customTags, + testVariations, + ]); const handleDelete = (id: string) => { testVariationService From 4b5615a57323dc8548d2d62fbcaa9eeca9e61b99 Mon Sep 17 00:00:00 2001 From: Pavlo Strunkin Date: Mon, 9 Aug 2021 16:35:06 +0300 Subject: [PATCH 6/9] Update test.moun.helper.tsx --- src/_test/test.moun.helper.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/_test/test.moun.helper.tsx b/src/_test/test.moun.helper.tsx index a08bcc22..002258c0 100644 --- a/src/_test/test.moun.helper.tsx +++ b/src/_test/test.moun.helper.tsx @@ -5,6 +5,7 @@ import { UserProvider, BuildProvider, TestRunProvider, + HelpProvider, } from "../contexts"; import { MemoryRouter, Route } from "react-router-dom"; import { MemoryRouterProps } from "react-router"; @@ -26,7 +27,9 @@ export const mountVrtComponent = ({ - {component} + + {component} + From 1daf7c571e40e3c70dd6d0a44087b0cac94aa76b Mon Sep 17 00:00:00 2001 From: Surat Das Date: Mon, 9 Aug 2021 23:37:29 -0700 Subject: [PATCH 7/9] Moved all help locator and texts to a separate file. --- src/components/BuildDetails.tsx | 4 +-- src/components/Filters.tsx | 5 ++-- src/components/GuidedTour.tsx | 2 +- src/components/LoginForm.tsx | 9 +++---- src/components/TestVariationMergeForm.tsx | 5 ++-- src/constants/help.ts | 24 ++++++++++++++++++ src/pages/ProjectListPage.tsx | 8 +++--- src/pages/ProjectPage.tsx | 24 +++++++++--------- src/pages/TestVariationListPage.tsx | 30 ++++++++--------------- 9 files changed, 61 insertions(+), 50 deletions(-) create mode 100644 src/constants/help.ts diff --git a/src/components/BuildDetails.tsx b/src/components/BuildDetails.tsx index 849e023b..47901b52 100644 --- a/src/components/BuildDetails.tsx +++ b/src/components/BuildDetails.tsx @@ -9,6 +9,7 @@ import { import { BuildStatusChip } from "./BuildStatusChip"; import { formatDateTime } from "../_helpers/format.helper"; import { useBuildState } from "../contexts"; +import { LOCATOR_BUILD_DETAILS } from '../constants/help'; const BuildDetails: React.FunctionComponent = () => { const { selectedBuild } = useBuildState(); @@ -18,10 +19,9 @@ const BuildDetails: React.FunctionComponent = () => { } const loadingAnimation = selectedBuild.isRunning && ; - const locatorBuildDetails = "build-details"; return ( - + diff --git a/src/components/Filters.tsx b/src/components/Filters.tsx index 3de78d8e..5647aea5 100644 --- a/src/components/Filters.tsx +++ b/src/components/Filters.tsx @@ -10,6 +10,7 @@ import { } from "@material-ui/core"; import { TestRun, TestVariation } from "../types"; import { DebounceInput } from "react-debounce-input"; +import { LOCATOR_RESET_FILTER } from "../constants/help"; interface IProps { items: (TestRun | TestVariation)[]; @@ -80,8 +81,6 @@ const Filters: React.FunctionComponent = ({ .map((t) => t.branchName) .filter((v, i, array) => v && array.indexOf(v) === i); - const locatorResetFilter = "reset-filter"; - return ( @@ -241,7 +240,7 @@ const Filters: React.FunctionComponent = ({ )} {branchNameList && branchNameList.length > 0 && ( - + Branch diff --git a/src/components/GuidedTour.tsx b/src/components/GuidedTour.tsx index b4f106e7..febc74aa 100644 --- a/src/components/GuidedTour.tsx +++ b/src/components/GuidedTour.tsx @@ -15,7 +15,7 @@ const GuidedTour: FunctionComponent = () => { firstStep && document.getElementById(firstStep.target.toString().slice(1)) ) { - helpSteps.every((e) => { + helpSteps.forEach((e) => { e.disableBeacon = true; e.hideCloseButton = true; }); diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index dd50f4b0..18449c11 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -17,6 +17,7 @@ import { import { routes } from "../constants"; import { useSnackbar } from "notistack"; import { TextValidator, ValidatorForm } from "react-material-ui-form-validator"; +import { CONTENT_LOGIN_FORM, LOCATOR_LOGIN_FORM } from "../constants/help"; const LoginForm = () => { const { enqueueSnackbar } = useSnackbar(); @@ -36,9 +37,8 @@ const LoginForm = () => { const helpSteps = [ { - target: "#loginform-1", - content: - "Default admin account: visual-regression-tracker@example.com / 123456. Make sure to change it's password.", + target: "#" + LOCATOR_LOGIN_FORM, + content: CONTENT_LOGIN_FORM, }, ]; @@ -47,12 +47,11 @@ const LoginForm = () => { }); const errorForTwoChar = "Enter at least two characters."; - const locatorLoginForm = "loginform-1"; return ( - + = ({ const [fromBranch, setFromBranch] = React.useState(""); const [toBranch, setToBranch] = React.useState(""); - const locatorSelectBranch = "select-branch"; - const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); testVariationService @@ -85,7 +84,7 @@ export const TestVariationMergeForm: React.FunctionComponent = ({ /> - diff --git a/src/constants/help.ts b/src/constants/help.ts new file mode 100644 index 00000000..bad8889a --- /dev/null +++ b/src/constants/help.ts @@ -0,0 +1,24 @@ +export const LOCATOR_LOGIN_FORM = "loginform-1"; +export const LOCATOR_BUILD_DETAILS = "build-details"; +export const LOCATOR_RESET_FILTER = "reset-filter"; +export const LOCATOR_TEST_VARIATION_SELECT_BRANCH = "select-branch"; +export const LOCATOR_PROJECT_LIST_PAGE_PROJECT_LIST = "projectlist-1"; +export const LOCATOR_PROJECT_PAGE_SELECT_PROJECT = "select-project"; +export const LOCATOR_PROJECT_PAGE_BUILD_LIST = "build-list"; +export const LOCATOR_PROJECT_PAGE_BUILD_DETAILS = "build-details"; +export const LOCATOR_PROJECT_PAGE_TEST_RUN_LIST = "test-run-list"; +export const LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT = "select-project"; +export const LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH = "select-branch"; +export const LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER = "reset-filter"; + +export const CONTENT_LOGIN_FORM = "Default admin account: visual-regression-tracker@example.com / 123456. Make sure to change it's password."; +export const CONTENT_PROJECT_LIST_PAGE_PROJECT_LIST = "By default, a project is created by docker, feel free to edit/add/delete projects."; +export const CONTENT_PROJECT_PAGE_SELECT_PROJECT = "Select the project for which you want to view details."; +export const CONTENT_PROJECT_PAGE_BUILD_LIST = "If you see 'No Builds', please run your image comparison from any client."; +export const CONTENT_PROJECT_PAGE_BUILD_DETAILS = "Details of the currently selected build."; +export const CONTENT_PROJECT_PAGE_TEST_RUN_LIST = "On selecting a build, shows all comparisons for the selected build."; +export const TITLE_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT = "Shows all the historical record of baselines by Name + Branch + OS + Browser + Viewport + Device"; +export const CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT = "Select the project you want to act on."; +export const TITLE_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH = "Merge from one branch to another"; +export const CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH = "Select the branch from/to which you want to merge the variations."; +export const CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER = "Only filtered items are displayed/merged to the target branch."; diff --git a/src/pages/ProjectListPage.tsx b/src/pages/ProjectListPage.tsx index 538ae435..6016bf3c 100644 --- a/src/pages/ProjectListPage.tsx +++ b/src/pages/ProjectListPage.tsx @@ -27,6 +27,7 @@ import { formatDateTime } from "../_helpers/format.helper"; import { ProjectForm } from "../components/ProjectForm"; import { useSnackbar } from "notistack"; import { BaseModal } from "../components/BaseModal"; +import { CONTENT_PROJECT_LIST_PAGE_PROJECT_LIST, LOCATOR_PROJECT_LIST_PAGE_PROJECT_LIST } from "../constants/help"; const ProjectsListPage = () => { const { enqueueSnackbar } = useSnackbar(); @@ -40,11 +41,10 @@ const ProjectsListPage = () => { const helpSteps = [ { - target: "#projectlist-1", + target: "#" + LOCATOR_PROJECT_LIST_PAGE_PROJECT_LIST, content: (
- By default, a project is created by docker, feel free to - edit/add/delete projects. + {CONTENT_PROJECT_LIST_PAGE_PROJECT_LIST}
), title: "Project List", @@ -159,7 +159,7 @@ const ProjectsListPage = () => {
{projectState.projectList.map((project) => ( - + Id: {project.id} Name: {project.name} diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index 0eae9055..f542342c 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -7,6 +7,7 @@ import TestRunList from "../components/TestRunList"; import BuildDetails from "../components/BuildDetails"; import { TestDetailsDialog } from "../components/TestDetailsDialog"; import { useHelpDispatch, setHelpSteps } from "../contexts"; +import { CONTENT_PROJECT_PAGE_BUILD_DETAILS, CONTENT_PROJECT_PAGE_BUILD_LIST, CONTENT_PROJECT_PAGE_SELECT_PROJECT, CONTENT_PROJECT_PAGE_TEST_RUN_LIST, LOCATOR_PROJECT_PAGE_BUILD_DETAILS, LOCATOR_PROJECT_PAGE_BUILD_LIST, LOCATOR_PROJECT_PAGE_SELECT_PROJECT, LOCATOR_PROJECT_PAGE_TEST_RUN_LIST } from "../constants/help"; const useStyles = makeStyles((theme) => ({ root: { @@ -22,30 +23,29 @@ const ProjectPage = () => { const helpSteps = [ { - target: "#select-project", + target: "#" + LOCATOR_PROJECT_PAGE_SELECT_PROJECT, content: ( -
Select the project for which you want to view details.
+
{CONTENT_PROJECT_PAGE_SELECT_PROJECT}
), }, { - target: "#build-list", + target: "#" + LOCATOR_PROJECT_PAGE_BUILD_LIST, title: "List of test runs", content: (
- If you see 'No Builds', please run your image comparison from any - client. + {CONTENT_PROJECT_PAGE_BUILD_LIST}
), }, { - target: "#build-details", - content:
Details of the currently selected build.
, + target: "#" + LOCATOR_PROJECT_PAGE_BUILD_DETAILS, + content:
{CONTENT_PROJECT_PAGE_BUILD_DETAILS}
, }, { - target: "#test-run-list", + target: "#" + LOCATOR_PROJECT_PAGE_TEST_RUN_LIST, content: (
- On selecting a build, shows all comparisons for the selected build. + {CONTENT_PROJECT_PAGE_TEST_RUN_LIST}
), }, @@ -59,13 +59,13 @@ const ProjectPage = () => { - + history.push(id)} /> - + @@ -73,7 +73,7 @@ const ProjectPage = () => { - + diff --git a/src/pages/TestVariationListPage.tsx b/src/pages/TestVariationListPage.tsx index 592564dd..2f1d8e08 100644 --- a/src/pages/TestVariationListPage.tsx +++ b/src/pages/TestVariationListPage.tsx @@ -9,6 +9,7 @@ import Filters from "../components/Filters"; import { TestVariationMergeForm } from "../components/TestVariationMergeForm"; import { useSnackbar } from "notistack"; import { setHelpSteps, useHelpDispatch } from "../contexts/help.context"; +import { CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH, CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER, LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER, LOCATOR_TEST_VARIATION_SELECT_BRANCH, TITLE_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH, TITLE_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT } from "../constants/help"; const TestVariationListPage: React.FunctionComponent = () => { const history = useHistory(); @@ -29,31 +30,20 @@ const TestVariationListPage: React.FunctionComponent = () => { const [branchName, setBranchName] = React.useState(""); const [filteredItems, setFilteredItems] = React.useState([]); - const locatorSelectProject = "select-project"; - const helpSteps = [ { - target: "#select-project", - title: - "Shows all the historical record of baselines by Name + Branch + OS + Browser + Viewport + Device", - content:
Select the project you want to act on.
, + target: "#" + LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, + title: TITLE_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, + content:
{CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT}
, }, { - target: "#select-branch", - title: "Merge from one branch to another", - content: ( -
- Select the branch from/to which you want to merge the variations. -
- ), + target: "#" + LOCATOR_TEST_VARIATION_SELECT_BRANCH, + title: TITLE_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH, + content:
{CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH}
, }, { - target: "#reset-filter", - content: ( -
- Only filtered items are displayed/merged to the target branch. -
- ), + target: "#" + LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER, + content:
{CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER}
, }, ]; @@ -120,7 +110,7 @@ const TestVariationListPage: React.FunctionComponent = () => { - + history.push(id)} From a6b588b59cbd2ef130898e5c853b0e268e8132eb Mon Sep 17 00:00:00 2001 From: Pavlo Strunkin Date: Tue, 10 Aug 2021 17:43:42 +0300 Subject: [PATCH 8/9] steps are moved to constants --- src/components/LoginForm.tsx | 24 ++------- src/constants/help.ts | 79 ++++++++++++++++++++++++----- src/constants/index.ts | 1 + src/pages/LoginPage.tsx | 10 +++- src/pages/ProjectListPage.tsx | 21 +++----- src/pages/ProjectPage.tsx | 39 +++----------- src/pages/TestVariationListPage.tsx | 24 ++------- 7 files changed, 97 insertions(+), 101 deletions(-) diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 18449c11..3b7c7429 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -1,4 +1,4 @@ -import React, { useState, FormEvent, useEffect } from "react"; +import React, { useState, FormEvent } from "react"; import { Link } from "react-router-dom"; import { Button, @@ -8,23 +8,16 @@ import { CardActions, Typography, } from "@material-ui/core"; -import { - useUserDispatch, - login, - setHelpSteps, - useHelpDispatch, -} from "../contexts"; -import { routes } from "../constants"; +import { useUserDispatch, login } from "../contexts"; +import { LOCATOR_LOGIN_FORM, routes } from "../constants"; import { useSnackbar } from "notistack"; import { TextValidator, ValidatorForm } from "react-material-ui-form-validator"; -import { CONTENT_LOGIN_FORM, LOCATOR_LOGIN_FORM } from "../constants/help"; const LoginForm = () => { const { enqueueSnackbar } = useSnackbar(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const dispatch = useUserDispatch(); - const helpDispatch = useHelpDispatch(); const handleSubmit = (event: FormEvent) => { event.preventDefault(); @@ -35,17 +28,6 @@ const LoginForm = () => { ); }; - const helpSteps = [ - { - target: "#" + LOCATOR_LOGIN_FORM, - content: CONTENT_LOGIN_FORM, - }, - ]; - - useEffect(() => { - setHelpSteps(helpDispatch, helpSteps); - }); - const errorForTwoChar = "Enter at least two characters."; return ( diff --git a/src/constants/help.ts b/src/constants/help.ts index bad8889a..4b1306ff 100644 --- a/src/constants/help.ts +++ b/src/constants/help.ts @@ -1,3 +1,5 @@ +import { Step } from "react-joyride"; + export const LOCATOR_LOGIN_FORM = "loginform-1"; export const LOCATOR_BUILD_DETAILS = "build-details"; export const LOCATOR_RESET_FILTER = "reset-filter"; @@ -9,16 +11,69 @@ export const LOCATOR_PROJECT_PAGE_BUILD_DETAILS = "build-details"; export const LOCATOR_PROJECT_PAGE_TEST_RUN_LIST = "test-run-list"; export const LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT = "select-project"; export const LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH = "select-branch"; -export const LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER = "reset-filter"; +export const LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER = + "reset-filter"; + +export const LOGIN_PAGE_STEPS: Step[] = [ + { + target: `#${LOCATOR_LOGIN_FORM}`, + content: + "Default admin account: visual-regression-tracker@example.com / 123456. Make sure to change default password.", + }, + { + target: `#${LOCATOR_LOGIN_FORM}`, + content: "Create new account without restrictions.", + }, +]; + +export const PROJECT_LIST_PAGE_STEPS: Step[] = [ + { + target: `#${LOCATOR_PROJECT_LIST_PAGE_PROJECT_LIST}`, + content: + "Default project is created after first start, feel free to edit/add/delete projects.", + title: "Project List", + }, +]; + +export const PROJECT_PAGE_STEPS: Step[] = [ + { + target: "#" + LOCATOR_PROJECT_PAGE_SELECT_PROJECT, + content: "Select the project for which you want to view details.", + }, + { + target: "#" + LOCATOR_PROJECT_PAGE_BUILD_LIST, + content: "List of Builds", + }, + { + target: "#" + LOCATOR_PROJECT_PAGE_BUILD_LIST, + content: + "If you see 'No Builds', please run your image comparison from any client.", + }, + { + target: "#" + LOCATOR_PROJECT_PAGE_BUILD_DETAILS, + content: "Breif details for selected build.", + }, + { + target: "#" + LOCATOR_PROJECT_PAGE_TEST_RUN_LIST, + content: "TestRuns for selected build.", + }, +]; -export const CONTENT_LOGIN_FORM = "Default admin account: visual-regression-tracker@example.com / 123456. Make sure to change it's password."; -export const CONTENT_PROJECT_LIST_PAGE_PROJECT_LIST = "By default, a project is created by docker, feel free to edit/add/delete projects."; -export const CONTENT_PROJECT_PAGE_SELECT_PROJECT = "Select the project for which you want to view details."; -export const CONTENT_PROJECT_PAGE_BUILD_LIST = "If you see 'No Builds', please run your image comparison from any client."; -export const CONTENT_PROJECT_PAGE_BUILD_DETAILS = "Details of the currently selected build."; -export const CONTENT_PROJECT_PAGE_TEST_RUN_LIST = "On selecting a build, shows all comparisons for the selected build."; -export const TITLE_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT = "Shows all the historical record of baselines by Name + Branch + OS + Browser + Viewport + Device"; -export const CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT = "Select the project you want to act on."; -export const TITLE_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH = "Merge from one branch to another"; -export const CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH = "Select the branch from/to which you want to merge the variations."; -export const CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER = "Only filtered items are displayed/merged to the target branch."; +export const TEST_VARIATION_LIST_PAGE = [ + { + target: "#" + LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, + title: + "Shows all the historical record of baselines by Name + Branch + OS + Browser + Viewport + Device", + content: "Select the project you want to act on.", + }, + { + target: "#" + LOCATOR_TEST_VARIATION_SELECT_BRANCH, + title: "Merge from one branch to another", + content: + "Select the branch from/to which you want to merge the variations.", + }, + { + target: "#" + LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER, + content: "Only filtered items are displayed/merged to the target branch.", + }, +]; diff --git a/src/constants/index.ts b/src/constants/index.ts index 3e2e87b6..1d59c57e 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,2 +1,3 @@ export * from "./routes"; export * from "./project"; +export * from "./help"; diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index c1295cae..0322aa30 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -2,13 +2,15 @@ import React, { useEffect } from "react"; import { Grid } from "@material-ui/core"; import LoginForm from "../components/LoginForm"; import { useHistory, useLocation } from "react-router-dom"; -import { useUserState } from "../contexts"; -import { routes } from "../constants"; +import { setHelpSteps, useHelpDispatch, useUserState } from "../contexts"; +import { LOGIN_PAGE_STEPS, routes } from "../constants"; const LoginPage = () => { const history = useHistory(); const location = useLocation<{ from: string }>(); const { loggedIn } = useUserState(); + const helpDispatch = useHelpDispatch(); + const { from } = location.state || { from: { pathname: routes.HOME }, }; @@ -17,6 +19,10 @@ const LoginPage = () => { if (loggedIn) history.replace(from); }); + useEffect(() => { + setHelpSteps(helpDispatch, LOGIN_PAGE_STEPS); + }); + return ( { const { enqueueSnackbar } = useSnackbar(); @@ -39,20 +42,8 @@ const ProjectsListPage = () => { const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); - const helpSteps = [ - { - target: "#" + LOCATOR_PROJECT_LIST_PAGE_PROJECT_LIST, - content: ( -
- {CONTENT_PROJECT_LIST_PAGE_PROJECT_LIST} -
- ), - title: "Project List", - }, - ]; - useEffect(() => { - setHelpSteps(helpDispatch, helpSteps); + setHelpSteps(helpDispatch, PROJECT_LIST_PAGE_STEPS); }); const toggleCreateDialogOpen = () => { diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index f542342c..311530ee 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -7,7 +7,12 @@ import TestRunList from "../components/TestRunList"; import BuildDetails from "../components/BuildDetails"; import { TestDetailsDialog } from "../components/TestDetailsDialog"; import { useHelpDispatch, setHelpSteps } from "../contexts"; -import { CONTENT_PROJECT_PAGE_BUILD_DETAILS, CONTENT_PROJECT_PAGE_BUILD_LIST, CONTENT_PROJECT_PAGE_SELECT_PROJECT, CONTENT_PROJECT_PAGE_TEST_RUN_LIST, LOCATOR_PROJECT_PAGE_BUILD_DETAILS, LOCATOR_PROJECT_PAGE_BUILD_LIST, LOCATOR_PROJECT_PAGE_SELECT_PROJECT, LOCATOR_PROJECT_PAGE_TEST_RUN_LIST } from "../constants/help"; +import { + PROJECT_PAGE_STEPS, + LOCATOR_PROJECT_PAGE_BUILD_LIST, + LOCATOR_PROJECT_PAGE_SELECT_PROJECT, + LOCATOR_PROJECT_PAGE_TEST_RUN_LIST, +} from "../constants"; const useStyles = makeStyles((theme) => ({ root: { @@ -21,38 +26,8 @@ const ProjectPage = () => { const history = useHistory(); const helpDispatch = useHelpDispatch(); - const helpSteps = [ - { - target: "#" + LOCATOR_PROJECT_PAGE_SELECT_PROJECT, - content: ( -
{CONTENT_PROJECT_PAGE_SELECT_PROJECT}
- ), - }, - { - target: "#" + LOCATOR_PROJECT_PAGE_BUILD_LIST, - title: "List of test runs", - content: ( -
- {CONTENT_PROJECT_PAGE_BUILD_LIST} -
- ), - }, - { - target: "#" + LOCATOR_PROJECT_PAGE_BUILD_DETAILS, - content:
{CONTENT_PROJECT_PAGE_BUILD_DETAILS}
, - }, - { - target: "#" + LOCATOR_PROJECT_PAGE_TEST_RUN_LIST, - content: ( -
- {CONTENT_PROJECT_PAGE_TEST_RUN_LIST} -
- ), - }, - ]; - useEffect(() => { - setHelpSteps(helpDispatch, helpSteps); + setHelpSteps(helpDispatch, PROJECT_PAGE_STEPS); }); return ( diff --git a/src/pages/TestVariationListPage.tsx b/src/pages/TestVariationListPage.tsx index 2f1d8e08..66c03044 100644 --- a/src/pages/TestVariationListPage.tsx +++ b/src/pages/TestVariationListPage.tsx @@ -9,7 +9,10 @@ import Filters from "../components/Filters"; import { TestVariationMergeForm } from "../components/TestVariationMergeForm"; import { useSnackbar } from "notistack"; import { setHelpSteps, useHelpDispatch } from "../contexts/help.context"; -import { CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH, CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER, LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER, LOCATOR_TEST_VARIATION_SELECT_BRANCH, TITLE_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH, TITLE_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT } from "../constants/help"; +import { + LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, + TEST_VARIATION_LIST_PAGE, +} from "../constants"; const TestVariationListPage: React.FunctionComponent = () => { const history = useHistory(); @@ -30,25 +33,8 @@ const TestVariationListPage: React.FunctionComponent = () => { const [branchName, setBranchName] = React.useState(""); const [filteredItems, setFilteredItems] = React.useState([]); - const helpSteps = [ - { - target: "#" + LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, - title: TITLE_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT, - content:
{CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_PROJECT}
, - }, - { - target: "#" + LOCATOR_TEST_VARIATION_SELECT_BRANCH, - title: TITLE_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH, - content:
{CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_BRANCH}
, - }, - { - target: "#" + LOCATOR_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER, - content:
{CONTENT_TEST_VARIATION_LIST_PAGE_SELECT_RESET_FILTER}
, - }, - ]; - React.useEffect(() => { - setHelpSteps(helpDispatch, helpSteps); + setHelpSteps(helpDispatch, TEST_VARIATION_LIST_PAGE); }); React.useEffect(() => { From a7d9a97ad9ca2b50b918d4cbb2cdbd24fe9f5eae Mon Sep 17 00:00:00 2001 From: Pavlo Strunkin Date: Tue, 10 Aug 2021 18:13:25 +0300 Subject: [PATCH 9/9] Update Header.spec.tsx --- src/components/Header.spec.tsx | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/components/Header.spec.tsx b/src/components/Header.spec.tsx index efceff16..7dbda17a 100644 --- a/src/components/Header.spec.tsx +++ b/src/components/Header.spec.tsx @@ -1,36 +1,25 @@ /* global cy */ import React from "react"; -import { mount } from "@cypress/react"; import Header from "./Header"; -import { UserProvider } from "../contexts"; -import { BrowserRouter } from "react-router-dom"; import { haveUserLogged } from "../_test/precondition.helper"; -import { TEST_USER } from "../_test/test.data.helper"; +import { TEST_PROJECT, TEST_USER } from "../_test/test.data.helper"; +import { mountVrtComponent } from "../_test/test.moun.helper"; +import { projectStub } from "../_test/stub.helper"; describe("Header", () => { describe("image", () => { it("Guest", () => { localStorage.clear(); - mount( - - -
- - - ); + mountVrtComponent({ component:
}); cy.get("#__cy_root").vrtTrack("Header. Guest"); }); it("Logged", () => { haveUserLogged(TEST_USER); - mount( - - -
- - - ); + projectStub.getAll([TEST_PROJECT]); + + mountVrtComponent({ component:
}); cy.get("#__cy_root").vrtTrack("Header. Logged"); });