From 1aca61e9ec35767ea7be12ddd278220756e7005b Mon Sep 17 00:00:00 2001 From: Scott Dickerson Date: Mon, 1 Jul 2024 11:51:58 -0400 Subject: [PATCH] :seedling: refactor application table (#1988) Includes: - Basic refactoring of applications-table.tsx - Add radash as a dependency for future use Pre-work for #1985 --------- Signed-off-by: Scott J Dickerson --- client/package.json | 1 + .../applications-table/applications-table.tsx | 191 +++++++++--------- package-lock.json | 9 + 3 files changed, 103 insertions(+), 98 deletions(-) diff --git a/client/package.json b/client/package.json index 672f22c011..b3faca8b7f 100644 --- a/client/package.json +++ b/client/package.json @@ -43,6 +43,7 @@ "i18next-http-backend": "^1.0.22", "js-yaml": "^4.1.0", "keycloak-js": "^18.0.1", + "radash": "^12.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hook-form": "^7.43.1", diff --git a/client/src/app/pages/applications/applications-table/applications-table.tsx b/client/src/app/pages/applications/applications-table/applications-table.tsx index 457797ddfc..e1592ccdc9 100644 --- a/client/src/app/pages/applications/applications-table/applications-table.tsx +++ b/client/src/app/pages/applications/applications-table/applications-table.tsx @@ -1,8 +1,9 @@ // External libraries -import * as React from "react"; +import React, { useState } from "react"; import { AxiosError } from "axios"; import { useHistory } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; +import dayjs from "dayjs"; // @patternfly import { @@ -72,6 +73,7 @@ import { } from "@app/hooks/table-controls"; // Queries +import { getArchetypeById, getAssessmentsByItemId } from "@app/api/rest"; import { Application, Assessment, Ref, Task } from "@app/api/models"; import { useBulkDeleteApplicationMutation, @@ -85,41 +87,38 @@ import { import { useDeleteReviewMutation } from "@app/queries/reviews"; import { useFetchIdentities } from "@app/queries/identities"; import { useFetchTagsWithTagItems } from "@app/queries/tags"; +import { useFetchArchetypes } from "@app/queries/archetypes"; // Relative components +import { AnalysisWizard } from "../analysis-wizard/analysis-wizard"; +import { ApplicationAnalysisStatus } from "../components/application-analysis-status"; import { ApplicationAssessmentStatus } from "../components/application-assessment-status"; import { ApplicationBusinessService } from "../components/application-business-service"; -import { ImportApplicationsForm } from "../components/import-applications-form"; -import { ConditionalRender } from "@app/components/ConditionalRender"; -import { NoDataEmptyState } from "@app/components/NoDataEmptyState"; -import { ConditionalTooltip } from "@app/components/ConditionalTooltip"; -import { getArchetypeById, getAssessmentsByItemId } from "@app/api/rest"; import { ApplicationDependenciesForm } from "@app/components/ApplicationDependenciesFormContainer/ApplicationDependenciesForm"; -import { useState } from "react"; -import { ApplicationAnalysisStatus } from "../components/application-analysis-status"; import { ApplicationDetailDrawer } from "../components/application-detail-drawer/application-detail-drawer"; -import { AnalysisWizard } from "../analysis-wizard/analysis-wizard"; -import { TaskGroupProvider } from "../analysis-wizard/components/TaskGroupContext"; +import { ApplicationFormModal } from "../components/application-form"; import { ApplicationIdentityForm } from "../components/application-identity-form/application-identity-form"; import { ApplicationReviewStatus } from "../components/application-review-status/application-review-status"; +import { ConditionalRender } from "@app/components/ConditionalRender"; +import { ConditionalTooltip } from "@app/components/ConditionalTooltip"; +import { IconWithLabel } from "@app/components/Icons"; +import { ImportApplicationsForm } from "../components/import-applications-form"; import { KebabDropdown } from "@app/components/KebabDropdown"; -import { useFetchArchetypes } from "@app/queries/archetypes"; -import { ApplicationFormModal } from "../components/application-form"; import { ManageColumnsToolbar } from "./components/manage-columns-toolbar"; -import dayjs from "dayjs"; -import { IconWithLabel } from "@app/components/Icons"; +import { NoDataEmptyState } from "@app/components/NoDataEmptyState"; +import { TaskGroupProvider } from "../analysis-wizard/components/TaskGroupContext"; export const ApplicationsTable: React.FC = () => { const { t } = useTranslation(); - const history = useHistory(); - const token = keycloak.tokenParsed; - const { pushNotification } = React.useContext(NotificationsContext); - const { identities } = useFetchIdentities(); + const history = useHistory(); + const token = keycloak.tokenParsed; - const [saveApplicationModalState, setSaveApplicationModalState] = - React.useState<"create" | Application | null>(null); + // ----- State for the modals + const [saveApplicationModalState, setSaveApplicationModalState] = useState< + "create" | Application | null + >(null); const isCreateUpdateApplicationsModalOpen = saveApplicationModalState !== null; @@ -127,40 +126,71 @@ export const ApplicationsTable: React.FC = () => { const createUpdateApplications = saveApplicationModalState !== "create" ? saveApplicationModalState : null; - const [archetypeRefsToOverride, setArchetypeRefsToOverride] = React.useState< + const [archetypeRefsToOverride, setArchetypeRefsToOverride] = useState< Ref[] | null >(null); const [archetypeRefsToOverrideReview, setArchetypeRefsToOverrideReview] = - React.useState(null); + useState(null); const [applicationToAssess, setApplicationToAssess] = - React.useState(null); + useState(null); const [applicationToReview, setApplicationToReview] = - React.useState(null); - - /*** Analysis */ + useState(null); const [isAnalyzeModalOpen, setAnalyzeModalOpen] = useState(false); - const getTask = (application: Application) => - tasks.find((task: Task) => task.application?.id === application.id); + const [applicationDependenciesToManage, setApplicationDependenciesToManage] = + useState(null); + const isDependenciesModalOpen = applicationDependenciesToManage !== null; + + const [assessmentToEdit, setAssessmentToEdit] = useState( + null + ); + + const [reviewToEdit, setReviewToEdit] = useState(null); + + const [applicationsToDelete, setApplicationsToDelete] = useState< + Application[] + >([]); + + const [assessmentToDiscard, setAssessmentToDiscard] = + useState(null); + + const [reviewToDiscard, setReviewToDiscard] = useState( + null + ); + + const [endOfAppImportPeriod, setEndOfAppImportPeriod] = useState( + dayjs() + ); + + const [ + saveApplicationsCredentialsModalState, + setSaveApplicationsCredentialsModalState, + ] = useState<"create" | Application[] | null>(null); + const isCreateUpdateCredentialsModalOpen = + saveApplicationsCredentialsModalState !== null; + const applicationsCredentialsToUpdate = + saveApplicationsCredentialsModalState !== "create" + ? saveApplicationsCredentialsModalState + : null; + + const [isApplicationImportModalOpen, setIsApplicationImportModalOpen] = + useState(false); + + // ----- Table data fetches and mutations + const { identities } = useFetchIdentities(); + const { tagItems } = useFetchTagsWithTagItems(); const { tasks, hasActiveTasks } = useFetchTasks( { kind: "analyzer", addon: "analyzer" }, isAnalyzeModalOpen ); - const isTaskCancellable = (application: Application) => { - const task = getTask(application); - return task?.state && !["Succeeded", "Failed"].includes(task.state); - }; - - const cancelAnalysis = (row: Application) => { - const task = tasks.find((task) => task.application?.id === row.id); - if (task?.id) cancelTask(task.id); - }; + const getTask = (application: Application) => + tasks.find((task: Task) => task.application?.id === application.id); const completedCancelTask = () => { pushNotification({ @@ -182,32 +212,16 @@ export const ApplicationsTable: React.FC = () => { completedCancelTask, failedCancelTask ); - /*** Analysis */ - - const { tagItems } = useFetchTagsWithTagItems(); - - const [applicationDependenciesToManage, setApplicationDependenciesToManage] = - React.useState(null); - const isDependenciesModalOpen = applicationDependenciesToManage !== null; - - const [assessmentToEdit, setAssessmentToEdit] = - React.useState(null); - - const [reviewToEdit, setReviewToEdit] = React.useState(null); - - const [applicationsToDelete, setApplicationsToDelete] = React.useState< - Application[] - >([]); - const [assessmentToDiscard, setAssessmentToDiscard] = - React.useState(null); - - const [reviewToDiscard, setReviewToDiscard] = - React.useState(null); + const cancelAnalysis = (row: Application) => { + const task = tasks.find((task) => task.application?.id === row.id); + if (task?.id) cancelTask(task.id); + }; - const [endOfAppImportPeriod, setEndOfAppImportPeriod] = useState( - dayjs() - ); + const isTaskCancellable = (application: Application) => { + const task = getTask(application); + return task?.state && !["Succeeded", "Failed"].includes(task.state); + }; const { data: applications, @@ -219,6 +233,7 @@ export const ApplicationsTable: React.FC = () => { const { assessments, isFetching: isFetchingAssessments } = useFetchAssessments(); + const { archetypes, isFetching: isFetchingArchetypes } = useFetchArchetypes(); const onDeleteApplicationSuccess = (appIDCount: number) => { @@ -261,6 +276,15 @@ export const ApplicationsTable: React.FC = () => { } ); + const discardReview = async (application: Application) => { + if (application.review) { + deleteReview({ + id: application.review.id, + name: application.name, + }); + } + }; + const { mutate: deleteAssessment } = useDeleteAssessmentMutation( (name) => { pushNotification({ @@ -288,15 +312,7 @@ export const ApplicationsTable: React.FC = () => { } }; - const discardReview = async (application: Application) => { - if (application.review) { - deleteReview({ - id: application.review.id, - name: application.name, - }); - } - }; - + // ----- Table controls const urlParams = new URLSearchParams(window.location.search); const filters = urlParams.get("filters"); @@ -540,20 +556,6 @@ export const ApplicationsTable: React.FC = () => { filterToolbarProps.setFilterValues({}); }; - const [ - saveApplicationsCredentialsModalState, - setSaveApplicationsCredentialsModalState, - ] = useState<"create" | Application[] | null>(null); - const isCreateUpdateCredentialsModalOpen = - saveApplicationsCredentialsModalState !== null; - const applicationsCredentialsToUpdate = - saveApplicationsCredentialsModalState !== "create" - ? saveApplicationsCredentialsModalState - : null; - - const [isApplicationImportModalOpen, setIsApplicationImportModalOpen] = - useState(false); - const userScopes: string[] = token?.scope.split(" ") || [], importWriteAccess = checkAccess(userScopes, importsWriteScopes), applicationWriteAccess = checkAccess(userScopes, applicationsWriteScopes), @@ -741,8 +743,6 @@ export const ApplicationsTable: React.FC = () => { } }; - const applicationName = assessmentToDiscard?.name || "Application name"; - return ( { isTop={false} paginationProps={paginationProps} /> + { onEditClick={() => setSaveApplicationModalState(activeItem)} task={activeItem ? getTask(activeItem) : null} /> + { isOpen={assessmentToDiscard !== null} message={ - - The assessment(s) for {applicationName} will be - discarded. Do you wish to continue? + + The assessment(s) for{" "} + {assessmentToDiscard?.name} discarded. Do you + wish to continue? } @@ -1261,12 +1261,7 @@ export const ApplicationsTable: React.FC = () => { isOpen={reviewToDiscard !== null} message={ - + The review for {reviewToDiscard?.name} will be discarded, as well as the review result. Do you wish to continue? diff --git a/package-lock.json b/package-lock.json index b121c544e5..796033f47a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,6 +82,7 @@ "i18next-http-backend": "^1.0.22", "js-yaml": "^4.1.0", "keycloak-js": "^18.0.1", + "radash": "^12.1.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hook-form": "^7.43.1", @@ -13541,6 +13542,14 @@ } ] }, + "node_modules/radash": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/radash/-/radash-12.1.0.tgz", + "integrity": "sha512-b0Zcf09AhqKS83btmUeYBS8tFK7XL2e3RvLmZcm0sTdF1/UUlHSsjXdCcWNxe7yfmAlPve5ym0DmKGtTzP6kVQ==", + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",