From 63f85013f8687edfab6c5679387a4725914ab715 Mon Sep 17 00:00:00 2001 From: Radoslaw Szwajkowski Date: Thu, 14 Mar 2024 21:00:34 +0100 Subject: [PATCH 1/3] :seedling: Use placeholder styling for 'No results' option (#1775) Resolves: #1773 Signed-off-by: Radoslaw Szwajkowski --- .../app/components/FilterToolbar/MultiselectFilterControl.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx b/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx index bbc0c0fce5..4662b656c1 100644 --- a/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx +++ b/client/src/app/components/FilterToolbar/MultiselectFilterControl.tsx @@ -238,7 +238,8 @@ export const MultiselectFilterControl = ({ newSelectOptions = [ { key: "no-results", - isDisabled: false, + isDisabled: true, + hasCheckbox: false, children: `No results found for "${inputValue}"`, value: "No results", }, From 36ca73894d94594e334385cdd01bd8f428c26ce8 Mon Sep 17 00:00:00 2001 From: Radoslaw Szwajkowski Date: Thu, 14 Mar 2024 21:53:47 +0100 Subject: [PATCH 2/3] :seedling: Use multi select filter for application names (#1771) Resolves: #1754 Signed-off-by: Radoslaw Szwajkowski Co-authored-by: Ian Bolton --- client/src/app/pages/issues/helpers.ts | 16 ++++++++++++++-- client/src/app/utils/utils.ts | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/client/src/app/pages/issues/helpers.ts b/client/src/app/pages/issues/helpers.ts index a78958e5e8..9beb0aad7b 100644 --- a/client/src/app/pages/issues/helpers.ts +++ b/client/src/app/pages/issues/helpers.ts @@ -22,6 +22,9 @@ import { useFetchBusinessServices } from "@app/queries/businessservices"; import { useFetchTagsWithTagItems } from "@app/queries/tags"; import { useTranslation } from "react-i18next"; import { useFetchArchetypes } from "@app/queries/archetypes"; +import { useFetchApplications } from "@app/queries/applications"; +import { localeNumericCompare } from "@app/utils/utils"; +import i18n from "@app/i18n"; // Certain filters are shared between the Issues page and the Affected Applications Page. // We carry these filter values between the two pages when determining the URLs to navigate between them. @@ -41,18 +44,27 @@ export const useSharedAffectedApplicationFilterCategories = < const { businessServices } = useFetchBusinessServices(); const { tagCategories, tags, tagItems } = useFetchTagsWithTagItems(); const { archetypes } = useFetchArchetypes(); + const { data: applications } = useFetchApplications(); return [ { key: "application.name", title: t("terms.applicationName"), filterGroup: IssueFilterGroups.ApplicationInventory, - type: FilterType.search, + type: FilterType.multiselect, placeholderText: t("actions.filterBy", { what: t("terms.applicationName").toLowerCase(), }) + "...", - getServerFilterValue: (value) => (value ? [`*${value[0]}*`] : []), + selectOptions: applications + .map(({ name }) => name) + .sort((a, b) => localeNumericCompare(a, b, i18n.language)) + .map((name) => ({ + key: name, + value: name, + })), + getServerFilterValue: (selectedOptions) => + selectedOptions?.filter(Boolean) ?? [], }, { key: "application.id", diff --git a/client/src/app/utils/utils.ts b/client/src/app/utils/utils.ts index 607dc0d1c3..0e57049095 100644 --- a/client/src/app/utils/utils.ts +++ b/client/src/app/utils/utils.ts @@ -192,3 +192,14 @@ export const collapseSpacesAndCompare = ( export const capitalizeFirstLetter = (str: string) => str.charAt(0).toUpperCase() + str.slice(1); + +/** + * Uses native string localCompare method with numeric option enabled. + * + * @param locale to be used by string compareFn + */ +export const localeNumericCompare = ( + a: string, + b: string, + locale: string +): number => a.localeCompare(b, locale, { numeric: true }); From 161fba8feecc4ce1bb0412dc887de0cd91256ada Mon Sep 17 00:00:00 2001 From: Radoslaw Szwajkowski Date: Fri, 15 Mar 2024 14:09:44 +0100 Subject: [PATCH 3/3] :sparkles: Add assessment/review status to archetype table (#1755) Review states: 1. Completed - a review exists 2. NotStarted Assessment states: 1. Completed - all required assessments done (based on 'assessed' flag) 2. InProgress - some assessments done 3. NotStarted Resolves: https://github.com/konveyor/tackle2-ui/issues/1751 Signed-off-by: Radoslaw Szwajkowski Co-authored-by: Ian Bolton --- .../app/pages/archetypes/archetypes-page.tsx | 125 ++++++++++++------ 1 file changed, 85 insertions(+), 40 deletions(-) diff --git a/client/src/app/pages/archetypes/archetypes-page.tsx b/client/src/app/pages/archetypes/archetypes-page.tsx index 0f44e8f1ca..b11e247497 100644 --- a/client/src/app/pages/archetypes/archetypes-page.tsx +++ b/client/src/app/pages/archetypes/archetypes-page.tsx @@ -43,6 +43,8 @@ import { useLocalTableControls, } from "@app/hooks/table-controls"; import { + ARCHETYPES_QUERY_KEY, + ARCHETYPE_QUERY_KEY, useDeleteArchetypeMutation, useFetchArchetypes, } from "@app/queries/archetypes"; @@ -69,11 +71,14 @@ import { } from "@app/rbac"; import { checkAccess } from "@app/utils/rbac-utils"; import keycloak from "@app/keycloak"; +import { IconedStatus } from "@app/components/IconedStatus"; +import { useQueryClient } from "@tanstack/react-query"; const Archetypes: React.FC = () => { const { t } = useTranslation(); const history = useHistory(); const { pushNotification } = React.useContext(NotificationsContext); + const queryClient = useQueryClient(); const [openCreateArchetype, setOpenCreateArchetype] = useState(false); @@ -115,34 +120,37 @@ const Archetypes: React.FC = () => { onError ); - const { mutate: deleteAssessment } = useDeleteAssessmentMutation(); - - const discardAssessment = async (archetype: Archetype) => { - try { - if (archetype.assessments) { - await Promise.all( - archetype.assessments.map(async (assessment) => { - await deleteAssessment({ - assessmentId: assessment.id, - archetypeId: archetype.id, - }); - }) - ).then(() => { - pushNotification({ - title: t("toastr.success.assessmentDiscarded", { - application: archetype.name, - }), - variant: "success", - }); + const { mutateAsync: deleteAssessment } = useDeleteAssessmentMutation(); + + const discardAssessment = (archetype: Archetype) => { + if (!archetype.assessments) { + return; + } + Promise.all( + archetype.assessments.map((assessment) => + deleteAssessment({ + assessmentId: assessment.id, + archetypeId: archetype.id, + }) + ) + ) + .then(() => { + pushNotification({ + title: t("toastr.success.assessmentDiscarded", { + application: archetype.name, + }), + variant: "success", + }); + queryClient.invalidateQueries([ARCHETYPES_QUERY_KEY]); + queryClient.invalidateQueries([ARCHETYPE_QUERY_KEY, archetype.id]); + }) + .catch((error) => { + console.error("Error while deleting assessments:", error); + pushNotification({ + title: getAxiosErrorMessage(error as AxiosError), + variant: "danger", }); - } - } catch (error) { - console.error("Error while deleting assessments:", error); - pushNotification({ - title: getAxiosErrorMessage(error as AxiosError), - variant: "danger", }); - } }; const onDeleteReviewSuccess = (name: string) => { @@ -154,25 +162,29 @@ const Archetypes: React.FC = () => { }); }; - const { mutate: deleteReview } = useDeleteReviewMutation( + const { mutateAsync: deleteReview } = useDeleteReviewMutation( onDeleteReviewSuccess ); - const discardReview = async (archetype: Archetype) => { - try { - if (archetype.review?.id) { - await deleteReview({ - id: archetype.review.id, - name: archetype.name, + const discardReview = (archetype: Archetype) => { + if (!archetype.review?.id) { + return; + } + deleteReview({ + id: archetype.review.id, + name: archetype.name, + }) + .then(() => { + queryClient.invalidateQueries([ARCHETYPES_QUERY_KEY]); + queryClient.invalidateQueries([ARCHETYPE_QUERY_KEY, archetype.id]); + }) + .catch((error) => { + console.error("Error while deleting review:", error); + pushNotification({ + title: getAxiosErrorMessage(error as AxiosError), + variant: "danger", }); - } - } catch (error) { - console.error("Error while deleting review:", error); - pushNotification({ - title: getAxiosErrorMessage(error as AxiosError), - variant: "danger", }); - } }; const urlParams = new URLSearchParams(window.location.search); const filters = urlParams.get("filters"); @@ -193,6 +205,8 @@ const Archetypes: React.FC = () => { tags: t("terms.tags"), maintainers: t("terms.maintainers"), applications: t("terms.applications"), + assessment: t("terms.assessment"), + review: t("terms.review"), }, isFilterEnabled: true, @@ -380,6 +394,11 @@ const Archetypes: React.FC = () => { + + @@ -427,6 +446,32 @@ const Archetypes: React.FC = () => { + + + + + + {(archetypeWriteAccess || assessmentWriteAccess ||