From dde4f3117a024fac752cdb3a38d8e7e71b6f00d3 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Wed, 10 Feb 2021 18:29:52 +0200 Subject: [PATCH 1/7] TestRun list add BULK operations https://github.com/Visual-Regression-Tracker/Visual-Regression-Tracker/issues/188 --- package-lock.json | 37 ++++ package.json | 1 + src/_helpers/route.helpers.ts | 4 +- src/components/ArrowButtons.tsx | 4 +- src/components/TestRunList.tsx | 178 ------------------ .../TestRunList/BulkDeleteButton.tsx | 56 ++++++ .../TestRunList/DataGridCustomToolbar.tsx | 19 ++ src/components/TestRunList/index.tsx | 109 +++++++++++ src/components/TestStatusChip.tsx | 6 +- src/pages/ProjectPage.tsx | 76 +------- 10 files changed, 238 insertions(+), 252 deletions(-) delete mode 100644 src/components/TestRunList.tsx create mode 100644 src/components/TestRunList/BulkDeleteButton.tsx create mode 100644 src/components/TestRunList/DataGridCustomToolbar.tsx create mode 100644 src/components/TestRunList/index.tsx diff --git a/package-lock.json b/package-lock.json index d1db5011..633ae1e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2433,6 +2433,30 @@ } } }, + "@material-ui/data-grid": { + "version": "4.0.0-alpha.19", + "resolved": "https://registry.npmjs.org/@material-ui/data-grid/-/data-grid-4.0.0-alpha.19.tgz", + "integrity": "sha512-DqBbRZPAREti4XM/AbaUy8hLaNMAvZLUbwQ82UWGkwkv5zF6fhtSEl51W/q+bkvpLwok6SuJyJvoUE5zrlmaUw==", + "requires": { + "@material-ui/utils": "^5.0.0-alpha.14", + "prop-types": "^15.7.2", + "reselect": "^4.0.0" + }, + "dependencies": { + "@material-ui/utils": { + "version": "5.0.0-alpha.24", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-5.0.0-alpha.24.tgz", + "integrity": "sha512-di0zaQKHiRi6NwPAt/4mRNfUYTa5aWVTqfzTYN/OdnQTGtOLPPFo9Om+uYgkunZIOa3lsahveo6ieH/YgFnJfQ==", + "requires": { + "@babel/runtime": "^7.4.4", + "@types/prop-types": "^15.7.3", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0" + } + } + } + }, "@material-ui/icons": { "version": "4.11.2", "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.2.tgz", @@ -2971,6 +2995,14 @@ "@types/react": "*" } }, + "@types/react-is": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.0.tgz", + "integrity": "sha512-A0DQ1YWZ0RG2+PV7neAotNCIh8gZ3z7tQnDJyS2xRPDNtAtSPcJ9YyfMP8be36Ha0kQRzbZCrrTMznA4blqO5g==", + "requires": { + "@types/react": "*" + } + }, "@types/react-material-ui-form-validator": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/react-material-ui-form-validator/-/react-material-ui-form-validator-2.1.0.tgz", @@ -17180,6 +17212,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "reselect": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", + "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" + }, "resolve": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz", diff --git a/package.json b/package.json index c02ca802..848b3926 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@material-ui/core": "^4.11.2", + "@material-ui/data-grid": "^4.0.0-alpha.19", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", "@testing-library/jest-dom": "^5.11.1", diff --git a/src/_helpers/route.helpers.ts b/src/_helpers/route.helpers.ts index 620e45a5..80e25c50 100644 --- a/src/_helpers/route.helpers.ts +++ b/src/_helpers/route.helpers.ts @@ -7,8 +7,8 @@ export const buildTestRunUrl = ( ) => `${routes.HOME}${testVariation.projectId}?buildId=${testRun.buildId}&testId=${testRun.id}`; -export const buildTestRunLocation = (testRun: TestRun) => ({ - search: `buildId=${testRun.buildId}&testId=${testRun.id}`, +export const buildTestRunLocation = (buildId: string, testRunId: string) => ({ + search: `buildId=${buildId}&testId=${testRunId}`, }); export const buildBuildPageUrl = (projectId: string, buildId: string) => diff --git a/src/components/ArrowButtons.tsx b/src/components/ArrowButtons.tsx index 2e59ae3a..baeb3c8c 100644 --- a/src/components/ArrowButtons.tsx +++ b/src/components/ArrowButtons.tsx @@ -33,7 +33,7 @@ export const ArrowButtons: React.FunctionComponent<{ const navigateNext = () => { if (selectedTestRunIndex + 1 < testRuns.length) { const next = testRuns[selectedTestRunIndex + 1]; - history.push(buildTestRunLocation(next)); + history.push(buildTestRunLocation(next.buildId, next.id)); selectTestRun(testRunDispatch, next.id); } }; @@ -42,7 +42,7 @@ export const ArrowButtons: React.FunctionComponent<{ const navigateBefore = () => { if (selectedTestRunIndex > 0) { const prev = testRuns[selectedTestRunIndex - 1]; - history.push(buildTestRunLocation(prev)); + history.push(buildTestRunLocation(prev.buildId, prev.id)); selectTestRun(testRunDispatch, prev.id); } }; diff --git a/src/components/TestRunList.tsx b/src/components/TestRunList.tsx deleted file mode 100644 index 1f80e344..00000000 --- a/src/components/TestRunList.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import React from "react"; -import { - Typography, - TableContainer, - TableHead, - TableCell, - Table, - TableRow, - TableBody, - IconButton, - Menu, - MenuItem, - makeStyles, -} from "@material-ui/core"; -import { MoreVert } from "@material-ui/icons"; -import { useHistory } from "react-router-dom"; -import { TestRun } from "../types"; -import TestStatusChip from "./TestStatusChip"; -import { buildTestRunLocation } from "../_helpers/route.helpers"; -import { - useTestRunState, - useTestRunDispatch, - deleteTestRun, - selectTestRun, -} from "../contexts"; -import { SkeletonList } from "./SkeletonList"; -import { useSnackbar } from "notistack"; -import { BaseModal } from "./BaseModal"; - -const useStyles = makeStyles((theme) => ({ - root: { - height: "100%", - }, -})); - -const TestRunList: React.FunctionComponent<{ - items: TestRun[]; -}> = ({ items }) => { - const classes = useStyles(); - const { enqueueSnackbar } = useSnackbar(); - const { selectedTestRunId, loading } = useTestRunState(); - const testRunDispatch = useTestRunDispatch(); - const history = useHistory(); - const [anchorEl, setAnchorEl] = React.useState(null); - const [selectedTestRun, setSelectedTestRun] = React.useState< - TestRun | undefined - >(undefined); - const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); - - const handleClick = ( - event: React.MouseEvent, - testRun: TestRun - ) => { - event.stopPropagation(); - setAnchorEl(event.currentTarget); - setSelectedTestRun(testRun); - }; - - const handleClose = () => { - setSelectedTestRun(undefined); - }; - - const toggleDeleteDialogOpen = () => { - setDeleteDialogOpen(!deleteDialogOpen); - }; - - return ( - - {loading ? ( - - ) : items.length === 0 ? ( - No test runs - ) : ( - - - - - - Name - OS - Device - Browser - Viewport - Status - - - - - {items.map((test) => ( - - { - history.push(buildTestRunLocation(test)); - selectTestRun(testRunDispatch, test.id); - }} - > - {test.name} - - - {test.os} - - - {test.device} - - - {test.browser} - - - {test.viewport} - - - - - - handleClick(event, test)}> - - - - - ))} - -
-
- - {selectedTestRun && ( - - { - history.push(buildTestRunLocation(selectedTestRun)); - handleClose(); - }} - > - Details - - Delete - - )} - - {selectedTestRun && ( - {`Are you sure you want to delete: ${selectedTestRun.name}?`} - } - onSubmit={() => { - deleteTestRun(testRunDispatch, selectedTestRun.id) - .then((testRun) => { - enqueueSnackbar(`${selectedTestRun.name} deleted`, { - variant: "success", - }); - }) - .catch((err) => - enqueueSnackbar(err, { - variant: "error", - }) - ); - handleClose(); - }} - /> - )} -
- )} -
- ); -}; - -export default TestRunList; diff --git a/src/components/TestRunList/BulkDeleteButton.tsx b/src/components/TestRunList/BulkDeleteButton.tsx new file mode 100644 index 00000000..3e04d8fb --- /dev/null +++ b/src/components/TestRunList/BulkDeleteButton.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { Typography, IconButton } from "@material-ui/core"; +import { BaseComponentProps } from "@material-ui/data-grid"; +import { deleteTestRun, useTestRunDispatch } from "../../contexts"; +import { BaseModal } from "../BaseModal"; +import { useSnackbar } from "notistack"; +import { Delete } from "@material-ui/icons"; + +export const BulkDeleteButton: React.FunctionComponent = ( + props: BaseComponentProps +) => { + const { enqueueSnackbar } = useSnackbar(); + const testRunDispatch = useTestRunDispatch(); + const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); + const rows: Record = props.state.selection; + const count = Object.keys(rows).length; + + const toggleDeleteDialogOpen = () => { + setDeleteDialogOpen(!deleteDialogOpen); + }; + return ( + <> + + + + + {`Are you sure you want to delete ${count} items?`} + } + onSubmit={() => { + Promise.all( + Object.keys(rows).map((id: string) => + deleteTestRun(testRunDispatch, id) + ) + ) + .then(() => { + toggleDeleteDialogOpen(); + enqueueSnackbar(`Deleted`, { + variant: "success", + }); + }) + .catch((err) => + enqueueSnackbar(err, { + variant: "error", + }) + ); + }} + /> + + ); +}; diff --git a/src/components/TestRunList/DataGridCustomToolbar.tsx b/src/components/TestRunList/DataGridCustomToolbar.tsx new file mode 100644 index 00000000..8cfac800 --- /dev/null +++ b/src/components/TestRunList/DataGridCustomToolbar.tsx @@ -0,0 +1,19 @@ +import React from "react"; +import { Toolbar, Box } from "@material-ui/core"; +import { BaseComponentProps, DensitySelector } from "@material-ui/data-grid"; +import { BulkDeleteButton } from "./BulkDeleteButton"; + +export const DataGridCustomToolbar: React.FunctionComponent = ( + props: BaseComponentProps +) => { + return ( + <> + + + + + + + + ); +}; diff --git a/src/components/TestRunList/index.tsx b/src/components/TestRunList/index.tsx new file mode 100644 index 00000000..6dee12d4 --- /dev/null +++ b/src/components/TestRunList/index.tsx @@ -0,0 +1,109 @@ +import React from "react"; +import { Chip } from "@material-ui/core"; +import { useHistory } from "react-router-dom"; +import TestStatusChip from "../TestStatusChip"; +import { buildTestRunLocation } from "../../_helpers/route.helpers"; +import { + useTestRunState, + useTestRunDispatch, + getTestRunList, + useBuildState, +} from "../../contexts"; +import { useSnackbar } from "notistack"; +import { + DataGrid, + ColDef, + RowParams, + CellParams, + PageChangeParams, +} from "@material-ui/data-grid"; +import { DataGridCustomToolbar } from "./DataGridCustomToolbar"; + +const columns: ColDef[] = [ + { field: "id", hide: true }, + { field: "name", headerName: "Name", flex: 1 }, + { + field: "tags", + headerName: "Tags", + flex: 1, + renderCell: (params: CellParams) => { + const tags = [ + params.getValue("os")?.toString(), + params.getValue("device")?.toString(), + params.getValue("browser")?.toString(), + params.getValue("viewport")?.toString(), + ]; + return ( + <> + {tags.map((tag) => tag && )} + + ); + }, + }, + { + field: "status", + headerName: "Status", + renderCell: (params: CellParams) => { + return ; + }, + }, +]; + +const TestRunList: React.FunctionComponent = () => { + const { enqueueSnackbar } = useSnackbar(); + const { testRuns, loading, total, take } = useTestRunState(); + const { selectedBuildId } = useBuildState(); + const testRunDispatch = useTestRunDispatch(); + const history = useHistory(); + + const getTestRunListCalback = React.useCallback( + (page: number) => + selectedBuildId && + getTestRunList(testRunDispatch, selectedBuildId, page).catch( + (err: string) => + enqueueSnackbar(err, { + variant: "error", + }) + ), + [testRunDispatch, enqueueSnackbar, selectedBuildId] + ); + + React.useEffect(() => { + getTestRunListCalback(1); + }, [getTestRunListCalback]); + + console.log("TestRunList"); + return ( + + { + history.push( + buildTestRunLocation( + param.getValue("buildId")?.toString() || "", + param.getValue("id")?.toString() || "" + ) + ); + }} + onPageChange={(param: PageChangeParams) => + getTestRunListCalback(param.page) + } + /> + + ); +}; + +export default TestRunList; diff --git a/src/components/TestStatusChip.tsx b/src/components/TestStatusChip.tsx index 2a75e059..b108666b 100644 --- a/src/components/TestStatusChip.tsx +++ b/src/components/TestStatusChip.tsx @@ -2,9 +2,9 @@ import React from "react"; import { Chip } from "@material-ui/core"; import { TestStatus } from "../types/testStatus"; -const TestStatusChip: React.FunctionComponent<{ status: TestStatus }> = ({ - status, -}) => { +const TestStatusChip: React.FunctionComponent<{ + status: string | undefined; +}> = ({ status }) => { let color: "inherit" | "primary" | "secondary" | "default" | undefined; switch (status) { case TestStatus.new: diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index cccf749f..dc4cb240 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -1,13 +1,11 @@ import React, { useEffect } from "react"; import { Grid, Dialog, Box, makeStyles } from "@material-ui/core"; import { useParams, useLocation, useHistory } from "react-router-dom"; -import { TestRun } from "../types"; import BuildList from "../components/BuildList"; import ProjectSelect from "../components/ProjectSelect"; import qs from "qs"; import TestRunList from "../components/TestRunList"; import TestDetailsModal from "../components/TestDetailsModal"; -import Filters from "../components/Filters"; import { useProjectState, useBuildState, @@ -16,7 +14,6 @@ import { useTestRunState, useTestRunDispatch, selectTestRun, - getTestRunList, useProjectDispatch, selectProject, getBuildList, @@ -53,17 +50,10 @@ const ProjectPage = () => { const { projectId } = useParams<{ projectId: string }>(); const location = useLocation(); const history = useHistory(); - const { enqueueSnackbar } = useSnackbar(); - const { selectedBuild, selectedBuildId, total, take } = useBuildState(); + const { selectedBuild } = useBuildState(); const buildDispatch = useBuildDispatch(); - const { selectedProjectId } = useProjectState(); const projectDispatch = useProjectDispatch(); - const { - testRuns, - selectedTestRunIndex, - total: testRunTotal, - take: testRunTake, - } = useTestRunState(); + const { testRuns, selectedTestRunIndex } = useTestRunState(); const testRunDispatch = useTestRunDispatch(); const queryParams: QueryParams = React.useMemo( @@ -89,38 +79,7 @@ const ProjectPage = () => { } }, [queryParams.testId, testRunDispatch]); - const getTestRunListCalback: any = React.useCallback( - (page: number) => - selectedBuildId && - getTestRunList(testRunDispatch, selectedBuildId, page).catch( - (err: string) => - enqueueSnackbar(err, { - variant: "error", - }) - ), - [testRunDispatch, enqueueSnackbar, selectedBuildId] - ); - - const getBuildListCalback: any = React.useCallback( - (page: number) => - selectedProjectId && - getBuildList(buildDispatch, selectedProjectId, page).catch( - (err: string) => - enqueueSnackbar(err, { - variant: "error", - }) - ), - [buildDispatch, enqueueSnackbar, selectedProjectId] - ); - - React.useEffect(() => { - getTestRunListCalback(1); - }, [getTestRunListCalback]); - - React.useEffect(() => { - getBuildListCalback(1); - }, [getBuildListCalback]); - + console.log("ProjectPage"); return ( @@ -129,34 +88,17 @@ const ProjectPage = () => { - - - getBuildListCalback(page)} - /> - - - {selectedBuild && } - - + + {selectedBuild && } + + + - - - getTestRunListCalback(page)} - /> - - + {selectedTestRunIndex !== undefined && testRuns[selectedTestRunIndex] && ( From 22fcac568f5748cc68e5df1844c967a04bfd4e36 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Wed, 10 Feb 2021 18:33:31 +0200 Subject: [PATCH 2/7] refactoring --- .../{BuildList.tsx => BuildList/index.tsx} | 58 +++++++++++++++---- src/components/TestRunList/index.tsx | 1 - src/pages/ProjectPage.tsx | 5 -- 3 files changed, 47 insertions(+), 17 deletions(-) rename src/components/{BuildList.tsx => BuildList/index.tsx} (80%) diff --git a/src/components/BuildList.tsx b/src/components/BuildList/index.tsx similarity index 80% rename from src/components/BuildList.tsx rename to src/components/BuildList/index.tsx index 39181a7c..8f58c480 100644 --- a/src/components/BuildList.tsx +++ b/src/components/BuildList/index.tsx @@ -24,13 +24,16 @@ import { deleteBuild, selectBuild, stopBuild, -} from "../contexts"; -import { BuildStatusChip } from "./BuildStatusChip"; -import { SkeletonList } from "./SkeletonList"; -import { formatDateTime } from "../_helpers/format.helper"; + getBuildList, + useProjectState, +} from "../../contexts"; +import { BuildStatusChip } from "../BuildStatusChip"; +import { SkeletonList } from "../SkeletonList"; +import { formatDateTime } from "../../_helpers/format.helper"; import { useSnackbar } from "notistack"; -import { Build } from "../types"; -import { BaseModal } from "./BaseModal"; +import { Build } from "../../types"; +import { BaseModal } from "../BaseModal"; +import { Pagination } from "@material-ui/lab"; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -52,10 +55,10 @@ const useStyles = makeStyles((theme: Theme) => const BuildList: FunctionComponent = () => { const classes = useStyles(); const history = useHistory(); - const { buildList, selectedBuild, loading } = useBuildState(); + const { buildList, selectedBuild, loading, total, take } = useBuildState(); const buildDispatch = useBuildDispatch(); const { enqueueSnackbar } = useSnackbar(); - + const { selectedProjectId } = useProjectState(); const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState(null); const [menuBuild, setMenuBuild] = React.useState(); @@ -83,6 +86,22 @@ const BuildList: FunctionComponent = () => { } }, [buildDispatch, selectedBuild, buildList]); + const getBuildListCalback: any = React.useCallback( + (page: number) => + selectedProjectId && + getBuildList(buildDispatch, selectedProjectId, page).catch( + (err: string) => + enqueueSnackbar(err, { + variant: "error", + }) + ), + [buildDispatch, enqueueSnackbar, selectedProjectId] + ); + + React.useEffect(() => { + getBuildListCalback(1); + }, [getBuildListCalback]); + return ( @@ -150,6 +169,16 @@ const BuildList: FunctionComponent = () => { )} + + + getBuildListCalback(page)} + /> + + {menuBuild && ( {menuBuild.isRunning && ( @@ -187,8 +216,12 @@ const BuildList: FunctionComponent = () => { }?`} } onSubmit={() => { - let indexOfBuildDeleted = buildList.findIndex((e) => e.id === menuBuild.id); - let indexOfSelectedBuild = buildList.findIndex((e) => e.id === selectedBuild?.id); + let indexOfBuildDeleted = buildList.findIndex( + (e) => e.id === menuBuild.id + ); + let indexOfSelectedBuild = buildList.findIndex( + (e) => e.id === selectedBuild?.id + ); deleteBuild(buildDispatch, menuBuild.id) .then((b) => { if (indexOfBuildDeleted === indexOfSelectedBuild) { @@ -196,7 +229,10 @@ const BuildList: FunctionComponent = () => { if (indexOfBuildDeleted === 0) { selectBuild(buildDispatch, buildList[1].id); } else { - selectBuild(buildDispatch, buildList[indexOfBuildDeleted - 1].id); + selectBuild( + buildDispatch, + buildList[indexOfBuildDeleted - 1].id + ); } } else { selectBuild(buildDispatch, null); diff --git a/src/components/TestRunList/index.tsx b/src/components/TestRunList/index.tsx index 6dee12d4..cc071bb9 100644 --- a/src/components/TestRunList/index.tsx +++ b/src/components/TestRunList/index.tsx @@ -72,7 +72,6 @@ const TestRunList: React.FunctionComponent = () => { getTestRunListCalback(1); }, [getTestRunListCalback]); - console.log("TestRunList"); return ( { } }, [queryParams.testId, testRunDispatch]); - console.log("ProjectPage"); return ( From 13789e6a270c2e5335451d010985e76e7605fb7d Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Wed, 10 Feb 2021 19:17:13 +0200 Subject: [PATCH 3/7] Update index.tsx --- src/components/TestRunList/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/TestRunList/index.tsx b/src/components/TestRunList/index.tsx index cc071bb9..4b5b60ee 100644 --- a/src/components/TestRunList/index.tsx +++ b/src/components/TestRunList/index.tsx @@ -35,7 +35,9 @@ const columns: ColDef[] = [ ]; return ( <> - {tags.map((tag) => tag && )} + {tags.map( + (tag) => tag && + )} ); }, From f3ebbd910287cae2e2a43470ba033292030393c1 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Wed, 10 Feb 2021 22:19:44 +0200 Subject: [PATCH 4/7] reafactoring --- src/_helpers/route.helpers.ts | 14 ++ src/components/BuildDetails.tsx | 167 +++++++++++---------- src/components/ProjectSelect.tsx | 16 +- src/components/TestDetailsDialog/index.tsx | 49 ++++++ src/components/TestRunList/index.tsx | 8 +- src/pages/ProjectPage.tsx | 75 ++------- src/pages/TestVariationListPage.tsx | 13 +- 7 files changed, 182 insertions(+), 160 deletions(-) create mode 100644 src/components/TestDetailsDialog/index.tsx diff --git a/src/_helpers/route.helpers.ts b/src/_helpers/route.helpers.ts index 80e25c50..01baa579 100644 --- a/src/_helpers/route.helpers.ts +++ b/src/_helpers/route.helpers.ts @@ -1,5 +1,6 @@ import { TestRun, TestVariation } from "../types"; import { routes } from "../constants"; +import qs from "qs"; export const buildTestRunUrl = ( testVariation: TestVariation, @@ -13,3 +14,16 @@ export const buildTestRunLocation = (buildId: string, testRunId: string) => ({ export const buildBuildPageUrl = (projectId: string, buildId: string) => `${routes.HOME}${projectId}?buildId=${buildId}`; + +export interface QueryParams { + buildId?: string; + testId?: string; +} + +export const getQueryParams = (guery: string): QueryParams => { + const queryParams = qs.parse(guery, { ignoreQueryPrefix: true }); + return { + buildId: queryParams.buildId as string, + testId: queryParams.testId as string, + }; +}; diff --git a/src/components/BuildDetails.tsx b/src/components/BuildDetails.tsx index 523e1d04..c07d96ba 100644 --- a/src/components/BuildDetails.tsx +++ b/src/components/BuildDetails.tsx @@ -7,103 +7,104 @@ import { Button, LinearProgress, } from "@material-ui/core"; -import { Build } from "../types"; import { BuildStatusChip } from "./BuildStatusChip"; import { useSnackbar } from "notistack"; import { buildsService } from "../services"; import { formatDateTime } from "../_helpers/format.helper"; +import { useBuildState } from "../contexts"; -interface IProps { - build: Build; -} - -const BuildDetails: React.FunctionComponent = ({ build }) => { +const BuildDetails: React.FunctionComponent = () => { const { enqueueSnackbar } = useSnackbar(); + const { selectedBuild } = useBuildState(); return ( - - - - - - {`#${build.number} ${ - build.ciBuildId || "" - }`} - - - - - - - - {build.unresolvedCount > 0 && ( + selectedBuild && ( + + + + - + {`#${selectedBuild.number} ${ + selectedBuild.ciBuildId || "" + }`} - )} - - - - - - - - - {formatDateTime(build.createdAt)} - - - - - - - - - - {`${ - build.unresolvedCount + build.failedCount + build.passedCount - } total`} - - - {`${build.unresolvedCount} unresolved`} + + + + + + + {selectedBuild.unresolvedCount > 0 && ( + + + + )} - - {`${build.failedCount} failed`} + + + + + + + + {formatDateTime(selectedBuild.createdAt)} + + - - {`${build.passedCount} passed`} + + + + + + + {`${ + selectedBuild.unresolvedCount + + selectedBuild.failedCount + + selectedBuild.passedCount + } total`} + + + {`${selectedBuild.unresolvedCount} unresolved`} + + + {`${selectedBuild.failedCount} failed`} + + + {`${selectedBuild.passedCount} passed`} + - - + + + {selectedBuild.isRunning && } - {build.isRunning && } - + ) ); }; diff --git a/src/components/ProjectSelect.tsx b/src/components/ProjectSelect.tsx index 370e23ef..af0e28df 100644 --- a/src/components/ProjectSelect.tsx +++ b/src/components/ProjectSelect.tsx @@ -8,7 +8,11 @@ import { Select, Theme, } from "@material-ui/core"; -import { useProjectState } from "../contexts"; +import { + useProjectState, + useProjectDispatch, + selectProject, +} from "../contexts"; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -22,10 +26,18 @@ const useStyles = makeStyles((theme: Theme) => ); const ProjectSelect: FunctionComponent<{ + projectId?: string; onProjectSelect: (id: string) => void; -}> = ({ onProjectSelect }) => { +}> = ({ projectId, onProjectSelect }) => { const classes = useStyles(); const { projectList, selectedProjectId } = useProjectState(); + const projectDispatch = useProjectDispatch(); + + React.useEffect(() => { + if (projectId && projectId !== selectedProjectId) { + selectProject(projectDispatch, projectId); + } + }, [projectId, selectedProjectId, projectDispatch]); return ( diff --git a/src/components/TestDetailsDialog/index.tsx b/src/components/TestDetailsDialog/index.tsx new file mode 100644 index 00000000..e18f017a --- /dev/null +++ b/src/components/TestDetailsDialog/index.tsx @@ -0,0 +1,49 @@ +import { Dialog, makeStyles } from "@material-ui/core"; +import React from "react"; +import { useLocation } from "react-router-dom"; +import { + selectTestRun, + useTestRunDispatch, + useTestRunState, +} from "../../contexts"; +import { QueryParams, getQueryParams } from "../../_helpers/route.helpers"; +import { ArrowButtons } from "../ArrowButtons"; +import TestDetailsModal from "../TestDetailsModal"; + +const useStyles = makeStyles((theme) => ({ + modal: { + margin: 40, + }, +})); + +export const TestDetailsDialog: React.FunctionComponent = () => { + const classes = useStyles(); + const location = useLocation(); + const { testRuns, selectedTestRunIndex } = useTestRunState(); + const testRunDispatch = useTestRunDispatch(); + + const queryParams: QueryParams = React.useMemo( + () => getQueryParams(location.search), + [location.search] + ); + + React.useEffect(() => { + if (queryParams.testId) { + selectTestRun(testRunDispatch, queryParams.testId); + } + }, [queryParams.testId, testRunDispatch]); + + if (selectedTestRunIndex === undefined || !testRuns[selectedTestRunIndex]) { + return null; + } + + return ( + + + + + ); +}; diff --git a/src/components/TestRunList/index.tsx b/src/components/TestRunList/index.tsx index 4b5b60ee..aec84995 100644 --- a/src/components/TestRunList/index.tsx +++ b/src/components/TestRunList/index.tsx @@ -28,10 +28,10 @@ const columns: ColDef[] = [ flex: 1, renderCell: (params: CellParams) => { const tags = [ - params.getValue("os")?.toString(), - params.getValue("device")?.toString(), - params.getValue("browser")?.toString(), - params.getValue("viewport")?.toString(), + params.row["os"], + params.row["device"], + params.row["browser"], + params.row["viewport"], ]; return ( <> diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index 45802f8b..6b210f2c 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -1,44 +1,18 @@ import React, { useEffect } from "react"; -import { Grid, Dialog, Box, makeStyles } from "@material-ui/core"; +import { Grid, Box, makeStyles } from "@material-ui/core"; import { useParams, useLocation, useHistory } from "react-router-dom"; import BuildList from "../components/BuildList"; import ProjectSelect from "../components/ProjectSelect"; -import qs from "qs"; import TestRunList from "../components/TestRunList"; -import TestDetailsModal from "../components/TestDetailsModal"; -import { - useBuildState, - useBuildDispatch, - selectBuild, - useTestRunState, - useTestRunDispatch, - selectTestRun, - useProjectDispatch, - selectProject, -} from "../contexts"; -import { ArrowButtons } from "../components/ArrowButtons"; +import { useBuildDispatch, selectBuild } from "../contexts"; import BuildDetails from "../components/BuildDetails"; - -interface QueryParams { - buildId?: string; - testId?: string; -} - -const getQueryParams = (guery: string): QueryParams => { - const queryParams = qs.parse(guery, { ignoreQueryPrefix: true }); - return { - buildId: queryParams.buildId as string, - testId: queryParams.testId as string, - }; -}; +import { TestDetailsDialog } from "../components/TestDetailsDialog"; +import { getQueryParams, QueryParams } from "../_helpers/route.helpers"; const useStyles = makeStyles((theme) => ({ root: { height: "100%", }, - modal: { - margin: 40, - }, })); const ProjectPage = () => { @@ -46,22 +20,11 @@ const ProjectPage = () => { const { projectId } = useParams<{ projectId: string }>(); const location = useLocation(); const history = useHistory(); - const { selectedBuild } = useBuildState(); const buildDispatch = useBuildDispatch(); - const projectDispatch = useProjectDispatch(); - const { testRuns, selectedTestRunIndex } = useTestRunState(); - const testRunDispatch = useTestRunDispatch(); - const queryParams: QueryParams = React.useMemo( - () => getQueryParams(location.search), - [location.search] - ); - - useEffect(() => { - if (projectId) { - selectProject(projectDispatch, projectId); - } - }, [projectId, projectDispatch]); + const queryParams: QueryParams = React.useMemo(() => { + return getQueryParams(location.search); + }, [location.search]); useEffect(() => { if (queryParams.buildId) { @@ -69,40 +32,28 @@ const ProjectPage = () => { } }, [buildDispatch, queryParams.buildId]); - useEffect(() => { - if (queryParams.testId) { - selectTestRun(testRunDispatch, queryParams.testId); - } - }, [queryParams.testId, testRunDispatch]); - return ( - history.push(id)} /> + history.push(id)} + /> - {selectedBuild && } + - - {selectedTestRunIndex !== undefined && testRuns[selectedTestRunIndex] && ( - - - - - )} + ); }; diff --git a/src/pages/TestVariationListPage.tsx b/src/pages/TestVariationListPage.tsx index 43077029..c7633139 100644 --- a/src/pages/TestVariationListPage.tsx +++ b/src/pages/TestVariationListPage.tsx @@ -8,12 +8,10 @@ import ProjectSelect from "../components/ProjectSelect"; import Filters from "../components/Filters"; import { TestVariationMergeForm } from "../components/TestVariationMergeForm"; import { useSnackbar } from "notistack"; -import { selectProject, useProjectDispatch } from "../contexts"; const TestVariationListPage: React.FunctionComponent = () => { const history = useHistory(); const { enqueueSnackbar } = useSnackbar(); - const projectDispatch = useProjectDispatch(); const { projectId = "" } = useParams<{ projectId: string }>(); const [testVariations, setTestVariations] = React.useState( [] @@ -28,12 +26,6 @@ const TestVariationListPage: React.FunctionComponent = () => { const [branchName, setBranchName] = React.useState(""); const [filteredItems, setFilteredItems] = React.useState([]); - React.useEffect(() => { - if (projectId) { - selectProject(projectDispatch, projectId); - } - }, [projectId, projectDispatch]); - React.useEffect(() => { if (projectId) { testVariationService @@ -84,7 +76,10 @@ const TestVariationListPage: React.FunctionComponent = () => { - history.push(id)} /> + history.push(id)} + /> Date: Wed, 10 Feb 2021 22:36:32 +0200 Subject: [PATCH 5/7] refactoring --- src/components/BuildDetails.tsx | 164 +++++++++--------- .../TestRunList/BulkDeleteButton.tsx | 2 +- 2 files changed, 84 insertions(+), 82 deletions(-) diff --git a/src/components/BuildDetails.tsx b/src/components/BuildDetails.tsx index c07d96ba..96f5f03b 100644 --- a/src/components/BuildDetails.tsx +++ b/src/components/BuildDetails.tsx @@ -17,94 +17,96 @@ const BuildDetails: React.FunctionComponent = () => { const { enqueueSnackbar } = useSnackbar(); const { selectedBuild } = useBuildState(); - return ( - selectedBuild && ( - - - - - - {`#${selectedBuild.number} ${ - selectedBuild.ciBuildId || "" - }`} - - - - - - - - {selectedBuild.unresolvedCount > 0 && ( - - - - )} + return ( + + + + + + {`#${selectedBuild.number} ${ + selectedBuild.ciBuildId || "" + }`} - - - - - - - - {formatDateTime(selectedBuild.createdAt)} - - + + - - - - - - - {`${ - selectedBuild.unresolvedCount + - selectedBuild.failedCount + - selectedBuild.passedCount - } total`} - - - {`${selectedBuild.unresolvedCount} unresolved`} - - - {`${selectedBuild.failedCount} failed`} - + + + + {selectedBuild.unresolvedCount > 0 && ( - {`${selectedBuild.passedCount} passed`} + + )} + + + + + + + + + {formatDateTime(selectedBuild.createdAt)} + + + + + + + + + + {`${ + selectedBuild.unresolvedCount + + selectedBuild.failedCount + + selectedBuild.passedCount + } total`} + + + {`${selectedBuild.unresolvedCount} unresolved`} + + + {`${selectedBuild.failedCount} failed`} + + + {`${selectedBuild.passedCount} passed`} - - - {selectedBuild.isRunning && } + + - ) + {selectedBuild.isRunning && } + ); }; diff --git a/src/components/TestRunList/BulkDeleteButton.tsx b/src/components/TestRunList/BulkDeleteButton.tsx index 3e04d8fb..84c916bc 100644 --- a/src/components/TestRunList/BulkDeleteButton.tsx +++ b/src/components/TestRunList/BulkDeleteButton.tsx @@ -40,7 +40,7 @@ export const BulkDeleteButton: React.FunctionComponent = ( ) .then(() => { toggleDeleteDialogOpen(); - enqueueSnackbar(`Deleted`, { + enqueueSnackbar(`${count} items deleted`, { variant: "success", }); }) From e9c391d40455142ef96219729947092af02b9205 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Wed, 10 Feb 2021 23:03:01 +0200 Subject: [PATCH 6/7] refactoring --- src/components/BuildList/index.tsx | 23 +++++++++++++---------- src/pages/ProjectPage.tsx | 12 +++++++----- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/components/BuildList/index.tsx b/src/components/BuildList/index.tsx index 8f58c480..4c6e8627 100644 --- a/src/components/BuildList/index.tsx +++ b/src/components/BuildList/index.tsx @@ -104,7 +104,7 @@ const BuildList: FunctionComponent = () => { return ( - + {loading ? ( @@ -169,16 +169,19 @@ const BuildList: FunctionComponent = () => { )} - - - getBuildListCalback(page)} - /> + + + + getBuildListCalback(page)} + /> + - + + {menuBuild && ( {menuBuild.isRunning && ( diff --git a/src/pages/ProjectPage.tsx b/src/pages/ProjectPage.tsx index 6b210f2c..3c36b4b6 100644 --- a/src/pages/ProjectPage.tsx +++ b/src/pages/ProjectPage.tsx @@ -36,11 +36,13 @@ const ProjectPage = () => { - history.push(id)} - /> - + + history.push(id)} + /> + + From 94df527e3563db2bac03fa61ac58bcbcef70f371 Mon Sep 17 00:00:00 2001 From: Pavel Strunkin Date: Wed, 10 Feb 2021 23:10:51 +0200 Subject: [PATCH 7/7] Update BuildDetails.tsx --- src/components/BuildDetails.tsx | 70 +++++++++++++++++---------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/src/components/BuildDetails.tsx b/src/components/BuildDetails.tsx index 96f5f03b..54a99d53 100644 --- a/src/components/BuildDetails.tsx +++ b/src/components/BuildDetails.tsx @@ -21,6 +21,41 @@ const BuildDetails: React.FunctionComponent = () => { return null; } + const approveAllButton = selectedBuild.unresolvedCount > 0 && ( + + + + ); + + const loadingAnimation = selectedBuild.isRunning && ; + return ( @@ -37,38 +72,7 @@ const BuildDetails: React.FunctionComponent = () => { - {selectedBuild.unresolvedCount > 0 && ( - - - - )} + {approveAllButton} @@ -105,7 +109,7 @@ const BuildDetails: React.FunctionComponent = () => { - {selectedBuild.isRunning && } + {loadingAnimation} ); };