diff --git a/client/src/app/Constants.ts b/client/src/app/Constants.ts index 682ba2dc10..422963935c 100644 --- a/client/src/app/Constants.ts +++ b/client/src/app/Constants.ts @@ -94,25 +94,25 @@ type RiskListType = { // t('risks.unknown') export const RISK_LIST: RiskListType = { - GREEN: { + green: { i18Key: "risks.low", hexColor: "#68b240", labelColor: "green", sortFactor: 1, }, - AMBER: { + yellow: { i18Key: "risks.medium", hexColor: "#f0ab0b", labelColor: "orange", sortFactor: 2, }, - RED: { + red: { i18Key: "risks.high", hexColor: "#cb440d", labelColor: "red", sortFactor: 3, }, - UNKNOWN: { + unknown: { i18Key: "risks.unknown", hexColor: black.value, labelColor: "grey", diff --git a/client/src/app/Paths.ts b/client/src/app/Paths.ts index 2354a34369..01875abfb4 100644 --- a/client/src/app/Paths.ts +++ b/client/src/app/Paths.ts @@ -8,10 +8,12 @@ export enum Paths { applicationsAssessmentTab = "/applications/assessment-tab", applicationsImports = "/applications/application-imports", applicationsImportsDetails = "/applications/application-imports/:importId", + archetypesAssessment = "/archetypes/assessment/:assessmentId", applicationsAssessment = "/applications/assessment/:assessmentId", - assessmentActions = "/applications/assessment-actions/:applicationId", + applicationAssessmentActions = "/applications/assessment-actions/:applicationId", + archetypeAssessmentActions = "/archetypes/assessment-actions/:archetypeId", assessmentSummary = "/applications/assessment-summary/:assessmentId", - applicationsReview = "/applications/application/:applicationId/review", + applicationsReview = "/applications/:applicationId/review", applicationsAnalysis = "/applications/analysis", archetypes = "/archetypes", controls = "/controls", @@ -50,11 +52,13 @@ export interface AssessmentRoute { } export interface AssessmentActionsRoute { - applicationId: string; + applicationId?: string; + archetypeId?: string; } export interface ReviewRoute { - applicationId: string; + applicationId?: string; + archetypeId?: string; } export interface ImportSummaryRoute { diff --git a/client/src/app/Routes.tsx b/client/src/app/Routes.tsx index 8eae84dbea..e98bcb483a 100644 --- a/client/src/app/Routes.tsx +++ b/client/src/app/Routes.tsx @@ -6,20 +6,26 @@ import { RepositoriesGit } from "./pages/repositories/Git"; import { RepositoriesMvn } from "./pages/repositories/Mvn"; import { RepositoriesSvn } from "./pages/repositories/Svn"; import { Paths } from "@app/Paths"; -import { ApplicationAssessment } from "./pages/applications/application-assessment/application-assessment"; import { RouteWrapper } from "./components/RouteWrapper"; import { adminRoles, devRoles } from "./rbac"; import { ErrorBoundary } from "react-error-boundary"; import { ErrorFallback } from "@app/components/ErrorFallback"; import { FEATURES_ENABLED } from "./FeatureFlags"; +const Assessment = lazy(() => import("./pages/assessment/assessment-page")); +const Review = lazy(() => import("./pages/review/review-page")); +const AssessmentSettings = lazy( + () => + import( + "./pages/assessment-management/assessment-settings/assessment-settings-page" + ) +); const Applications = lazy(() => import("./pages/applications")); const ManageImports = lazy(() => import("./pages/applications/manage-imports")); const ImportDetails = lazy( () => import("./pages/applications/manage-imports-details") ); -const Reviews = lazy(() => import("./pages/applications/application-review")); const Reports = lazy(() => import("./pages/reports")); const Controls = lazy(() => import("./pages/controls")); const Identities = lazy(() => import("./pages/identities")); @@ -34,27 +40,22 @@ const AffectedApplications = lazy( ); const Dependencies = lazy(() => import("./pages/dependencies")); -const AssessmentSettings = lazy( - () => - import( - "./pages/assessment-management/assessment-settings/assessment-settings-page" - ) -); - const Questionnaire = lazy( () => import("./pages/assessment-management/questionnaire/questionnaire-page") ); const AssessmentActions = lazy( () => - import("./pages/applications/assessment-actions/assessment-actions-page") + import( + "./pages/assessment/components/assessment-actions/assessment-actions-page" + ) ); const Archetypes = lazy(() => import("./pages/archetypes/archetypes-page")); const AssessmentSummary = lazy( () => import( - "./pages/applications/application-assessment/components/assessment-summary/assessment-summary-page" + "./pages/assessment/components/assessment-summary/assessment-summary-page" ) ); export interface IRoute { @@ -75,24 +76,34 @@ export const devRoutes: IRoute[] = [ comp: ManageImports, exact: false, }, + { + path: Paths.archetypesAssessment, + comp: Assessment, + exact: false, + }, { path: Paths.applicationsAssessment, - comp: ApplicationAssessment, + comp: Assessment, + exact: false, + }, + { + path: Paths.applicationsReview, + comp: Review, exact: false, }, { - path: Paths.assessmentActions, + path: Paths.applicationAssessmentActions, comp: AssessmentActions, exact: false, }, { - path: Paths.assessmentSummary, - comp: AssessmentSummary, + path: Paths.archetypeAssessmentActions, + comp: AssessmentActions, exact: false, }, { - path: Paths.applicationsReview, - comp: Reviews, + path: Paths.assessmentSummary, + comp: AssessmentSummary, exact: false, }, { diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts index cae1c15967..b9524cc588 100644 --- a/client/src/app/api/models.ts +++ b/client/src/app/api/models.ts @@ -131,13 +131,13 @@ export interface Application { } export interface Review { - id?: number; + id: number; proposedAction: ProposedAction; effortEstimate: EffortEstimate; businessCriticality: number; workPriority: number; comments?: string; - application?: Application; + application?: Ref; } export interface ApplicationDependency { @@ -172,13 +172,6 @@ export interface ApplicationImport { isValid: boolean; } -export interface BulkCopyReview { - id?: number; - sourceReview: number; - targetApplications: number[]; - completed?: boolean; -} - export type IdentityKind = | "source" | "maven" @@ -214,13 +207,6 @@ export interface Proxy { enabled: boolean; } -export interface BulkCopyAssessment { - bulkId?: number; - fromAssessmentId: number; - applications: { applicationId: number }[]; - completed?: boolean; -} - // Pagination export interface BusinessServicePage { @@ -703,7 +689,7 @@ export interface Thresholds { yellow: number; } export type AssessmentStatus = "empty" | "started" | "complete"; -export type Risk = "GREEN" | "AMBER" | "RED" | "UNKNOWN"; +export type Risk = "green" | "yellow" | "red" | "unknown"; export interface InitialAssessment { application?: Ref; @@ -720,6 +706,8 @@ export interface Assessment description: string; status: AssessmentStatus; risk: Risk; + stakeholders: Ref[]; + stakeholderGroups: Ref[]; } export interface CategorizedTag { category: TagCategory; @@ -757,9 +745,10 @@ export interface Archetype { description: string; comments: string; criteriaTags: Tag[]; - archetypeTags: Tag[]; + tags: Tag[]; assessmentTags?: Tag[]; - stakeholders?: Stakeholder[]; - stakeholderGroups?: StakeholderGroup[]; + stakeholders?: Ref[]; + stakeholderGroups?: Ref[]; applications?: Application[]; + assessments?: Ref[]; } diff --git a/client/src/app/api/rest.ts b/client/src/app/api/rest.ts index 8e9bbb178b..18e803352f 100644 --- a/client/src/app/api/rest.ts +++ b/client/src/app/api/rest.ts @@ -14,8 +14,6 @@ import { ApplicationImport, ApplicationImportSummary, Assessment, - BulkCopyAssessment, - BulkCopyReview, BusinessService, Cache, HubPaginatedResult, @@ -50,7 +48,6 @@ import { InitialAssessment, MimeType, } from "./models"; -import { QueryKey } from "@tanstack/react-query"; import { serializeRequestParamsForHub } from "@app/hooks/table-controls"; // TACKLE_HUB @@ -170,24 +167,24 @@ export const deleteApplicationDependency = (id: number): AxiosPromise => { // Reviews -export const getReviews = (): AxiosPromise => { - return APIClient.get(`${REVIEWS}`); +export const getReviews = (): Promise => { + return axios.get(`${REVIEWS}`); }; -export const getReviewId = (id: number | string): AxiosPromise => { - return APIClient.get(`${REVIEWS}/${id}`); +export const getReviewById = (id: number | string): Promise => { + return axios.get(`${REVIEWS}/${id}`).then((response) => response.data); }; -export const createReview = (obj: Review): AxiosPromise => { - return APIClient.post(`${REVIEWS}`, obj); +export const createReview = (obj: New): Promise => { + return axios.post(`${REVIEWS}`, obj); }; -export const updateReview = (obj: Review): AxiosPromise => { - return APIClient.put(`${REVIEWS}/${obj.id}`, obj); +export const updateReview = (obj: Review): Promise => { + return axios.put(`${REVIEWS}/${obj.id}`, obj); }; -export const deleteReview = (id: number): AxiosPromise => { - return APIClient.delete(`${REVIEWS}/${id}`); +export const deleteReview = (id: number): Promise => { + return axios.delete(`${REVIEWS}/${id}`); }; export const getApplicationAdoptionPlan = ( @@ -208,50 +205,35 @@ export const getApplicationSummaryCSV = (id: string): AxiosPromise => { }); }; -//TODO: Remove this -export const getApplicationByIdPromise = ( - id: number | string -): Promise => axios.get(`${APPLICATIONS}/${id}`); - -//TODO: Remove this -export const getAssessmentsPromise = (filters: { - applicationId?: number | string; -}): Promise => { - const params = { - applicationId: filters.applicationId, - }; - - const query: string[] = buildQuery(params); - return axios.get(`${ASSESSMENTS}?${query.join("&")}`); -}; - -export const getAssessments = (filters: { - applicationId?: number | string; -}): Promise => { - const params = { - applicationId: filters.applicationId, - }; - - const query: string[] = buildQuery(params); - return axios - .get(`${ASSESSMENTS}?${query.join("&")}`) - .then((response) => response.data); -}; - -export const getAssessmentsByAppId = ( - applicationId?: number | string +export const getAssessmentsByItemId = ( + isArchetype: boolean, + itemId?: number | string ): Promise => { - return axios - .get(`${APPLICATIONS}/${applicationId}/assessments`) - .then((response) => response.data); + if (!itemId) return Promise.resolve([]); + if (isArchetype) { + return axios + .get(`${ARCHETYPES}/${itemId}/assessments`) + .then((response) => response.data); + } else { + return axios + .get(`${APPLICATIONS}/${itemId}/assessments`) + .then((response) => response.data); + } }; export const createAssessment = ( - obj: InitialAssessment + obj: InitialAssessment, + isArchetype: boolean ): Promise => { - return axios - .post(`${APPLICATIONS}/${obj?.application?.id}/assessments`, obj) - .then((response) => response.data); + if (isArchetype) { + return axios + .post(`${ARCHETYPES}/${obj?.archetype?.id}/assessments`, obj) + .then((response) => response.data); + } else { + return axios + .post(`${APPLICATIONS}/${obj?.application?.id}/assessments`, obj) + .then((response) => response.data); + } }; export const updateAssessment = (obj: Assessment): Promise => { @@ -268,27 +250,6 @@ export const deleteAssessment = (id: number): AxiosPromise => { return APIClient.delete(`${ASSESSMENTS}/${id}`); }; -export const createBulkCopyAssessment = ( - bulk: BulkCopyAssessment -): AxiosPromise => { - return APIClient.post(`${ASSESSMENTS}/bulk`, bulk); -}; - -export const getBulkCopyAssessment = ({ - queryKey, -}: { - queryKey: QueryKey; -}): AxiosPromise => { - const [_, id] = queryKey; - return APIClient.get(`${ASSESSMENTS}/bulk/${id}`); -}; - -export const createBulkCopyReview = ( - bulk: BulkCopyReview -): AxiosPromise => { - return APIClient.post(`${REVIEWS}/copy`, bulk); -}; - export const getIdentities = (): AxiosPromise> => { return APIClient.get(`${IDENTITIES}`, jsonHeaders); }; @@ -796,12 +757,13 @@ export const deleteQuestionnaire = (id: number): Promise => export const getArchetypes = (): Promise => axios.get(ARCHETYPES).then(({ data }) => data); -export const getArchetypeById = (id: number): Promise => +export const getArchetypeById = (id: number | string): Promise => axios.get(`${ARCHETYPES}/${id}`).then(({ data }) => data); // success with code 201 and created entity as response data -export const createArchetype = (archetype: Archetype): Promise => - axios.post(ARCHETYPES, archetype); +export const createArchetype = ( + archetype: New +): Promise => axios.post(ARCHETYPES, archetype); // success with code 204 and therefore no response content export const updateArchetype = (archetype: Archetype): Promise => diff --git a/client/src/app/components/answer-table/answer-table.tsx b/client/src/app/components/answer-table/answer-table.tsx index a2b3799ff3..1b43df13e3 100644 --- a/client/src/app/components/answer-table/answer-table.tsx +++ b/client/src/app/components/answer-table/answer-table.tsx @@ -109,7 +109,7 @@ const AnswerTable: React.FC = ({ {answer?.autoAnswerFor?.map((tag, index) => { return (
- +
); })} diff --git a/client/src/app/components/items-select/items-select.tsx b/client/src/app/components/items-select/items-select.tsx index 83f9c967cb..83372fbadd 100644 --- a/client/src/app/components/items-select/items-select.tsx +++ b/client/src/app/components/items-select/items-select.tsx @@ -53,8 +53,8 @@ const ItemsSelect = < searchInputAriaLabel={searchInputAriaLabel} options={itemsToName()} selections={normalizeSelections(value)} - onChange={() => { - onChange(value); + onChange={(selection) => { + onChange(selection as any); }} /> )} diff --git a/client/src/app/components/questionnaire-summary/questionnaire-summary.tsx b/client/src/app/components/questionnaire-summary/questionnaire-summary.tsx index 4e58c5b9b1..186687f97f 100644 --- a/client/src/app/components/questionnaire-summary/questionnaire-summary.tsx +++ b/client/src/app/components/questionnaire-summary/questionnaire-summary.tsx @@ -32,17 +32,19 @@ export enum SummaryType { } interface QuestionnaireSummaryProps { - isFetching: boolean; - fetchError: AxiosError | null; + isFetching?: boolean; + fetchError?: AxiosError | null; summaryData: Assessment | Questionnaire | undefined; summaryType: SummaryType; + isArchetype?: boolean; } const QuestionnaireSummary: React.FC = ({ summaryData, summaryType, - isFetching, - fetchError, + isFetching = false, + fetchError = null, + isArchetype, }) => { const { t } = useTranslation(); @@ -64,7 +66,7 @@ const QuestionnaireSummary: React.FC = ({ return { ...summaryData, - sections: summaryData?.sections.map((section) => ({ + sections: summaryData?.sections?.map((section) => ({ ...section, questions: section.questions.filter(({ text, explanation }) => [text, explanation].some( @@ -76,9 +78,10 @@ const QuestionnaireSummary: React.FC = ({ }, [summaryData, searchValue]); const allQuestions = - summaryData?.sections.flatMap((section) => section.questions) || []; + summaryData?.sections?.flatMap((section) => section.questions) || []; const allMatchingQuestions = - filteredSummaryData?.sections.flatMap((section) => section.questions) || []; + filteredSummaryData?.sections?.flatMap((section) => section.questions) || + []; if (!summaryData) { return
No data available.
; @@ -88,7 +91,7 @@ const QuestionnaireSummary: React.FC = ({ @@ -143,10 +146,14 @@ const QuestionnaireSummary: React.FC = ({ @@ -184,7 +191,7 @@ const QuestionnaireSummary: React.FC = ({ hideAnswerKey={summaryType === SummaryType.Assessment} /> , - ...(summaryData?.sections.map((section, index) => { + ...(summaryData?.sections?.map((section, index) => { const filteredQuestions = filteredSummaryData?.sections[index]?.questions || []; return ( diff --git a/client/src/app/components/tests/RiskLabel.test.tsx b/client/src/app/components/tests/RiskLabel.test.tsx index 7d58ef9bd3..e42949478c 100644 --- a/client/src/app/components/tests/RiskLabel.test.tsx +++ b/client/src/app/components/tests/RiskLabel.test.tsx @@ -4,22 +4,22 @@ import { RiskLabel } from "../RiskLabel"; describe("RiskLabel", () => { it("Green", () => { - const { container } = render(); + const { container } = render(); expect(container.firstChild).toHaveClass("pf-v5-c-label pf-m-green"); expect(screen.getByText("risks.low")).toBeInTheDocument(); }); it("Amber", () => { - const { container } = render(); + const { container } = render(); expect(container.firstChild).toHaveClass("pf-v5-c-label pf-m-orange"); expect(screen.getByText("risks.medium")).toBeInTheDocument(); }); it("Red", () => { - const { container } = render(); + const { container } = render(); expect(container.firstChild).toHaveClass("pf-v5-c-label pf-m-red"); expect(screen.getByText("risks.high")).toBeInTheDocument(); }); it("Unknown", () => { - const { container } = render(); + const { container } = render(); expect(container.firstChild).toHaveClass("pf-v5-c-label"); expect(screen.getByText("risks.unknown")).toBeInTheDocument(); }); diff --git a/client/src/app/hooks/index.ts b/client/src/app/hooks/index.ts deleted file mode 100644 index 452d14cb0c..0000000000 --- a/client/src/app/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useAssessApplication } from "./useAssessApplication"; diff --git a/client/src/app/hooks/useAssessApplication/index.ts b/client/src/app/hooks/useAssessApplication/index.ts deleted file mode 100644 index 452d14cb0c..0000000000 --- a/client/src/app/hooks/useAssessApplication/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useAssessApplication } from "./useAssessApplication"; diff --git a/client/src/app/hooks/useAssessApplication/useAssessApplication.test.tsx b/client/src/app/hooks/useAssessApplication/useAssessApplication.test.tsx deleted file mode 100644 index 0eefce3e77..0000000000 --- a/client/src/app/hooks/useAssessApplication/useAssessApplication.test.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { renderHook, act } from "@testing-library/react-hooks"; - -import { Application } from "@app/api/models"; -import { ASSESSMENTS } from "@app/api/rest"; - -import { useAssessApplication } from "./useAssessApplication"; - -describe("useAssessApplication", () => { - it("Initial status", () => { - const { result } = renderHook(() => useAssessApplication()); - const { inProgress } = result.current; - expect(inProgress).toBe(false); - }); - - it("getCurrentAssessment: endpoint fails", async () => { - const application: Application = { - id: 1, - name: "some", - migrationWave: null, - }; - - new MockAdapter(axios) - .onGet(`${ASSESSMENTS}?applicationId=${application.id}}`) - .networkError(); - - // Use hook - const { result, waitForNextUpdate } = renderHook(() => - useAssessApplication() - ); - - // Start call - const { getCurrentAssessment } = result.current; - - const onSuccessSpy = jest.fn(); - const onErrorSpy = jest.fn(); - - act(() => getCurrentAssessment(application, onSuccessSpy, onErrorSpy)); - expect(result.current.inProgress).toBe(true); - - // Verify next status - await waitForNextUpdate(); - expect(result.current.inProgress).toBe(false); - expect(onSuccessSpy).toHaveBeenCalledTimes(0); - expect(onErrorSpy).toHaveBeenCalledTimes(1); - }); - - it("getCurrentAssessment: endpoints works with empty array response", async () => { - const application: Application = { - id: 1, - name: "some", - migrationWave: null, - }; - - // Mock REST API - new MockAdapter(axios) - .onGet(`${ASSESSMENTS}?applicationId=${application.id}`) - .reply(200, []); - - // Use hook - const { result, waitForNextUpdate } = renderHook(() => - useAssessApplication() - ); - - // Start call - const { getCurrentAssessment } = result.current; - - const onSuccessSpy = jest.fn(); - const onErrorSpy = jest.fn(); - - act(() => getCurrentAssessment(application, onSuccessSpy, onErrorSpy)); - expect(result.current.inProgress).toBe(true); - - // Verify next status - await waitForNextUpdate(); - expect(result.current.inProgress).toBe(false); - expect(onSuccessSpy).toHaveBeenCalledTimes(1); - expect(onSuccessSpy).toHaveBeenCalledWith(undefined); - expect(onErrorSpy).toHaveBeenCalledTimes(0); - }); - - it("getCurrentAssessment: endpoints works with filled array response", async () => { - const application: Application = { - id: 1, - name: "some", - migrationWave: null, - }; - - const response = { id: 123 }; - - new MockAdapter(axios) - .onGet(`${ASSESSMENTS}?applicationId=${application.id}`) - .reply(200, [response]); - - // Use hook - const { result, waitForNextUpdate } = renderHook(() => - useAssessApplication() - ); - - // Start call - const { getCurrentAssessment } = result.current; - - const onSuccessSpy = jest.fn(); - const onErrorSpy = jest.fn(); - - act(() => getCurrentAssessment(application, onSuccessSpy, onErrorSpy)); - expect(result.current.inProgress).toBe(true); - - // Verify next status - await waitForNextUpdate(); - expect(result.current.inProgress).toBe(false); - expect(onSuccessSpy).toHaveBeenCalledTimes(1); - expect(onSuccessSpy).toHaveBeenCalledWith(response); - expect(onErrorSpy).toHaveBeenCalledTimes(0); - }); - - it("assessApplication: fetchAssessment fails", async () => { - const application: Application = { - id: 1, - name: "some", - migrationWave: null, - }; - - new MockAdapter(axios) - .onGet(`${ASSESSMENTS}?applicationId=${application.id}}`) - .networkError(); - - // Use hook - const { result, waitForNextUpdate } = renderHook(() => - useAssessApplication() - ); - - // Start call - const { assessApplication } = result.current; - - const onSuccessSpy = jest.fn(); - const onErrorSpy = jest.fn(); - - act(() => assessApplication(application, onSuccessSpy, onErrorSpy)); - expect(result.current.inProgress).toBe(true); - - // Verify next status - await waitForNextUpdate(); - expect(result.current.inProgress).toBe(false); - expect(onSuccessSpy).toHaveBeenCalledTimes(0); - expect(onErrorSpy).toHaveBeenCalledTimes(1); - }); - - it("assessApplication: createAssessment fails", async () => { - const application: Application = { - id: 1, - name: "some", - migrationWave: null, - }; - - new MockAdapter(axios) - .onGet(`${ASSESSMENTS}?applicationId=${application.id}}`) - .reply(200, []); - - // Use hook - const { result, waitForNextUpdate } = renderHook(() => - useAssessApplication() - ); - - // Start call - const { assessApplication } = result.current; - - const onSuccessSpy = jest.fn(); - const onErrorSpy = jest.fn(); - - act(() => assessApplication(application, onSuccessSpy, onErrorSpy)); - expect(result.current.inProgress).toBe(true); - - // Verify next status - await waitForNextUpdate(); - expect(result.current.inProgress).toBe(false); - expect(onSuccessSpy).toHaveBeenCalledTimes(0); - expect(onErrorSpy).toHaveBeenCalledTimes(1); - }); - - it("assessApplication: if assessment exists already => don't create a new assessment", async () => { - const application: Application = { - id: 1, - name: "some", - migrationWave: null, - }; - - // Mock REST API - const assessmentResponse = { id: 123 }; - new MockAdapter(axios) - .onGet(`${ASSESSMENTS}?applicationId=${application.id}`) - .reply(200, [assessmentResponse]); - - // Use hook - const { result, waitForNextUpdate } = renderHook(() => - useAssessApplication() - ); - - // Start call - const { assessApplication } = result.current; - - const onSuccessSpy = jest.fn(); - const onErrorSpy = jest.fn(); - - act(() => assessApplication(application, onSuccessSpy, onErrorSpy)); - expect(result.current.inProgress).toBe(true); - - // Verify next status - await waitForNextUpdate(); - expect(result.current.inProgress).toBe(false); - expect(onSuccessSpy).toHaveBeenCalledTimes(1); - expect(onSuccessSpy).toHaveBeenCalledWith(assessmentResponse); - expect(onErrorSpy).toHaveBeenCalledTimes(0); - }); - - it("assessApplication: if assessment doesn't exists => create a new assessment", async () => { - const application: Application = { - id: 1, - name: "some", - migrationWave: null, - }; - - // Mock REST API - const assessmentResponse = { id: 123 }; - new MockAdapter(axios) - .onGet(`${ASSESSMENTS}?applicationId=${application.id}`) - .reply(200, []) - - .onPost(ASSESSMENTS) - .reply(200, assessmentResponse); - - // Use hook - const { result, waitForNextUpdate } = renderHook(() => - useAssessApplication() - ); - - // Start call - const { assessApplication } = result.current; - - const onSuccessSpy = jest.fn(); - const onErrorSpy = jest.fn(); - - act(() => assessApplication(application, onSuccessSpy, onErrorSpy)); - expect(result.current.inProgress).toBe(true); - - // Verify next status - // await waitForNextUpdate(); - // expect(result.current.inProgress).toBe(false); - // expect(onSuccessSpy).toHaveBeenCalledTimes(1); - // expect(onSuccessSpy).toHaveBeenCalledWith(assessmentResponse); - // expect(onErrorSpy).toHaveBeenCalledTimes(0); - //TODO: Update tests after api is finished - }); -}); diff --git a/client/src/app/hooks/useAssessApplication/useAssessApplication.ts b/client/src/app/hooks/useAssessApplication/useAssessApplication.ts deleted file mode 100644 index ca267bf461..0000000000 --- a/client/src/app/hooks/useAssessApplication/useAssessApplication.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { useCallback, useState } from "react"; -import { AxiosError } from "axios"; - -import { createAssessment, getAssessments } from "@app/api/rest"; -import { Application, Assessment, InitialAssessment } from "@app/api/models"; - -export interface IState { - inProgress: boolean; - getCurrentAssessment: ( - application: Application, - onSuccess: (assessment?: Assessment) => void, - onError: (error: AxiosError) => void - ) => void; - assessApplication: ( - application: Application, - onSuccess: (assessment: Assessment) => void, - onError: (error: AxiosError) => void - ) => void; -} - -export const useAssessApplication = (): IState => { - const [inProgress, setInProgress] = useState(false); - - const getCurrentAssessmentHandler = useCallback( - ( - application: Application, - onSuccess: (assessment?: Assessment) => void, - onError: (error: AxiosError) => void - ) => { - if (!application.id) { - console.log("Entity must have 'id' to execute this operationn"); - return; - } - - setInProgress(true); - getAssessments({ applicationId: application.id }) - .then((data) => { - const currentAssessment: Assessment | undefined = data[0] - ? data[0] - : undefined; - - setInProgress(false); - onSuccess(currentAssessment); - }) - .catch((error: AxiosError) => { - setInProgress(false); - onError(error); - }); - }, - [] - ); - - const assessApplicationHandler = useCallback( - ( - application: Application, - onSuccess: (assessment: Assessment) => void, - onError: (error: AxiosError) => void - ) => { - if (!application.id) { - console.log("Entity must have 'id' to execute this operation"); - return; - } - - setInProgress(true); - getAssessments({ applicationId: application.id }) - .then((data) => { - const currentAssessment: Assessment | undefined = data[0]; - - const newAssessment: InitialAssessment = { - application: { id: application.id, name: application.name }, - questionnaire: { id: 1, name: "Sample Questionnaire" }, - }; - - return Promise.all([ - currentAssessment, - !currentAssessment ? createAssessment(newAssessment) : undefined, - ]); - }) - .then(([currentAssessment, newAssessment]) => { - setInProgress(false); - onSuccess(currentAssessment || newAssessment!); - }) - .catch((error: AxiosError) => { - setInProgress(false); - onError(error); - }); - }, - [] - ); - - return { - inProgress: inProgress, - getCurrentAssessment: getCurrentAssessmentHandler, - assessApplication: assessApplicationHandler, - }; -}; - -export default useAssessApplication; diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/__snapshots__/application-assessment-page-header.test.tsx.snap b/client/src/app/pages/applications/application-assessment/components/application-assessment-page/__snapshots__/application-assessment-page-header.test.tsx.snap deleted file mode 100644 index 63d6d8365b..0000000000 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/__snapshots__/application-assessment-page-header.test.tsx.snap +++ /dev/null @@ -1,249 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ApplicationAssessmentPageHeader Renders without crashing 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
-
-
- -
-
-
-
-
-

- composed.applicationAssessment -

-

-

-
-
-
-
-
-
-
- , - "container":
-
-
- -
-
-
-
-
-

- composed.applicationAssessment -

-

-

-
-
-
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/__snapshots__/application-assessment-page.test.tsx.snap b/client/src/app/pages/applications/application-assessment/components/application-assessment-page/__snapshots__/application-assessment-page.test.tsx.snap deleted file mode 100644 index 1c2f1d5e05..0000000000 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/__snapshots__/application-assessment-page.test.tsx.snap +++ /dev/null @@ -1,267 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ApplicationAssessmentPage Renders without crashing 1`] = ` -{ - "asFragment": [Function], - "baseElement": -
-
-
-
- -
-
-
-
-
-

- composed.applicationAssessment -

-

-

-
-
-
-
-
-
-
- Body of page -
-
-
- , - "container":
-
-
-
- -
-
-
-
-
-

- composed.applicationAssessment -

-

-

-
-
-
-
-
-
-
- Body of page -
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page-header.test.tsx b/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page-header.test.tsx deleted file mode 100644 index ff8d4ea9d4..0000000000 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page-header.test.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import { ApplicationAssessmentPageHeader } from "./application-assessment-page-header"; -import { Assessment } from "@app/api/models"; -import { render } from "@app/test-config/test-utils"; - -describe("ApplicationAssessmentPageHeader", () => { - // const assessment: Assessment = { - // status: "STARTED", - // application: { name: "test", id: 1 }, - // sections: [], - // questionnaire: { name: "test", id: 1 }, - // }; - - it.skip("Renders without crashing", () => { - // const wrapper = - // render(); - // - // Body of page - // - // expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page.test.tsx b/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page.test.tsx deleted file mode 100644 index d684aacc64..0000000000 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; -import { ApplicationAssessmentPage } from "./application-assessment-page"; -import { Assessment } from "@app/api/models"; -import { render } from "@app/test-config/test-utils"; - -describe("ApplicationAssessmentPage", () => { - // const assessment: Assessment = { - // status: "STARTED", - // applicationId: 1, - // // questionnaire: { - // // categories: [], - // // }, - // }; - - it.skip("Renders without crashing", () => { - // const wrapper = render( - // - // Body of page - // - // ); - // expect(wrapper).toMatchSnapshot(); - // }); - }); -}); diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page.tsx b/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page.tsx deleted file mode 100644 index 2f7d9868d7..0000000000 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; -import { PageSection, PageSectionTypes } from "@patternfly/react-core"; -import { Assessment } from "@app/api/models"; - -import { ApplicationAssessmentPageHeader } from "./application-assessment-page-header"; - -export interface IApplicationAssessmentPageProps { - assessment?: Assessment; - children: any; -} - -export const ApplicationAssessmentPage: React.FC< - IApplicationAssessmentPageProps -> = ({ assessment, children }) => { - return ( - <> - - - - - {children} - - - ); -}; diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/index.ts b/client/src/app/pages/applications/application-assessment/components/application-assessment-page/index.ts deleted file mode 100644 index fcfe409a1f..0000000000 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ApplicationAssessmentPage } from "./application-assessment-page"; diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-wizard/index.ts b/client/src/app/pages/applications/application-assessment/components/application-assessment-wizard/index.ts deleted file mode 100644 index 40880119d5..0000000000 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-wizard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ApplicationAssessmentWizard } from "./application-assessment-wizard"; diff --git a/client/src/app/pages/applications/application-assessment/components/assessment-stakeholders-form/index.ts b/client/src/app/pages/applications/application-assessment/components/assessment-stakeholders-form/index.ts deleted file mode 100644 index db0abfc773..0000000000 --- a/client/src/app/pages/applications/application-assessment/components/assessment-stakeholders-form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AssessmentStakeholdersForm } from "./assessment-stakeholders-form"; diff --git a/client/src/app/pages/applications/application-assessment/index.ts b/client/src/app/pages/applications/application-assessment/index.ts deleted file mode 100644 index f25ba376c7..0000000000 --- a/client/src/app/pages/applications/application-assessment/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ApplicationAssessment as default } from "./application-assessment"; diff --git a/client/src/app/pages/applications/application-review/application-review.tsx b/client/src/app/pages/applications/application-review/application-review.tsx deleted file mode 100644 index 4d528e7859..0000000000 --- a/client/src/app/pages/applications/application-review/application-review.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { useHistory, useParams } from "react-router-dom"; -import { useTranslation } from "react-i18next"; -import { AxiosError } from "axios"; -import { - Bullseye, - Button, - Card, - CardHeader, - FormSection, - Grid, - GridItem, - PageSection, - Text, - TextContent, -} from "@patternfly/react-core"; -import BanIcon from "@patternfly/react-icons/dist/esm/icons/ban-icon"; -import InfoCircleIcon from "@patternfly/react-icons/dist/esm/icons/info-circle-icon"; - -import { useAssessApplication } from "@app/hooks"; -import { Paths, ReviewRoute } from "@app/Paths"; -import { - getApplicationByIdPromise, - getAssessmentById, - getAssessmentsPromise, - getReviewId, -} from "@app/api/rest"; -import { Application, Assessment, Review } from "@app/api/models"; -import { formatPath, getAxiosErrorMessage } from "@app/utils/utils"; -import { ApplicationReviewPage } from "./components/application-review-page"; -import { ApplicationDetails } from "./components/application-details"; -import { ReviewForm } from "./components/review-form"; -import { NotificationsContext } from "@app/components/NotificationsContext"; -import { useSetting } from "@app/queries/settings"; -import { SimpleEmptyState } from "@app/components/SimpleEmptyState"; -import { ConditionalRender } from "@app/components/ConditionalRender"; -import { AppPlaceholder } from "@app/components/AppPlaceholder"; -import { ApplicationAssessmentDonutChart } from "./components/application-assessment-donut-chart/application-assessment-donut-chart"; - -export const ApplicationReview: React.FC = () => { - const { t } = useTranslation(); - - const { pushNotification } = React.useContext(NotificationsContext); - - const history = useHistory(); - const { applicationId } = useParams(); - - const { assessApplication, inProgress: isApplicationAssessInProgress } = - useAssessApplication(); - - const { data: reviewAssessmentSetting } = useSetting( - "review.assessment.required" - ); - - // Application and review - - const [isFetching, setIsFetching] = useState(true); - const [fetchError, setFetchError] = useState(); - - const [application, setApplication] = useState(); - const [review, setReview] = useState(); - const [assessment, setAssessment] = useState(); - - // Start fetch - - useEffect(() => { - if (applicationId) { - setIsFetching(true); - - Promise.all([ - getAssessmentsPromise({ applicationId: applicationId }), - getApplicationByIdPromise(applicationId), - ]) - .then(([assessmentData, applicationData]) => { - setApplication(applicationData); - - const assessment = assessmentData[0] - ? getAssessmentById(assessmentData[0].id!) - : undefined; - const review = applicationData.review - ? getReviewId(applicationData.review.id!) - : undefined; - - return Promise.all([assessment, review]); - }) - .then(([assessmentResponse, reviewResponse]) => { - if (assessmentResponse) { - setAssessment(assessmentResponse); - } - if (reviewResponse) { - setReview(reviewResponse.data); - } - - setIsFetching(false); - setFetchError(undefined); - }) - .catch((error) => { - setIsFetching(false); - setFetchError(error); - }); - } - }, [applicationId]); - - const redirectToApplications = () => { - history.push(Paths.applications); - }; - - const startApplicationAssessment = () => { - if (!application) { - console.log("Can not assess without an application"); - return; - } - - assessApplication( - application, - (assessment: Assessment) => { - history.push( - formatPath(Paths.applicationsAssessment, { - assessmentId: assessment.id, - }) - ); - }, - (error) => { - pushNotification({ - title: getAxiosErrorMessage(error), - variant: "danger", - }); - } - ); - }; - - if (fetchError) { - return ( - - - - - - ); - } - - if ( - !isFetching && - (!assessment || (assessment && assessment.status !== "complete")) && - !reviewAssessmentSetting - ) { - return ( - - - - {application && ( - - )} - - } - /> - - - ); - } - - return ( - <> - - }> - - {application && ( - -
- - - - - - -
-
- )} - {assessment && ( - - - - )} -
-
-
- {assessment && ( - - - - - {t("terms.assessmentSummary")} - - - - - )} - - ); -}; diff --git a/client/src/app/pages/applications/application-review/components/application-review-page/application-review-page.tsx b/client/src/app/pages/applications/application-review/components/application-review-page/application-review-page.tsx deleted file mode 100644 index 7c29defb77..0000000000 --- a/client/src/app/pages/applications/application-review/components/application-review-page/application-review-page.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -import { useTranslation } from "react-i18next"; -import { PageSection, Text } from "@patternfly/react-core"; - -import { PageHeader } from "@app/components/PageHeader"; -import { Paths } from "@app/Paths"; - -export const ApplicationReviewPage: React.FC = ({ children }) => { - const { t } = useTranslation(); - - return ( - <> - - {t("message.reviewInstructions")} - } - breadcrumbs={[ - { - title: t("terms.applications"), - path: Paths.applications, - }, - { - title: t("terms.review"), - path: Paths.applicationsReview, - }, - ]} - menuActions={[]} - /> - - {children} - - ); -}; diff --git a/client/src/app/pages/applications/application-review/components/application-review-page/index.ts b/client/src/app/pages/applications/application-review/components/application-review-page/index.ts deleted file mode 100644 index f3f3e65c4c..0000000000 --- a/client/src/app/pages/applications/application-review/components/application-review-page/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ApplicationReviewPage } from "./application-review-page"; diff --git a/client/src/app/pages/applications/application-review/index.ts b/client/src/app/pages/applications/application-review/index.ts deleted file mode 100644 index 83dbaa42b7..0000000000 --- a/client/src/app/pages/applications/application-review/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ApplicationReview as default } from "./application-review"; diff --git a/client/src/app/pages/applications/applications-table-analyze/applications-table-analyze.tsx b/client/src/app/pages/applications/applications-table-analyze/applications-table-analyze.tsx index 49047fbcb4..a359ea0fc5 100644 --- a/client/src/app/pages/applications/applications-table-analyze/applications-table-analyze.tsx +++ b/client/src/app/pages/applications/applications-table-analyze/applications-table-analyze.tsx @@ -75,7 +75,6 @@ import { useFetchApplications, } from "@app/queries/applications"; import { useCancelTaskMutation, useFetchTasks } from "@app/queries/tasks"; -import { useFetchApplicationAssessments } from "@app/queries/assessments"; import { useFetchReviews } from "@app/queries/reviews"; import { useFetchIdentities } from "@app/queries/identities"; import { useFetchTagCategories } from "@app/queries/tags"; @@ -190,9 +189,6 @@ export const ApplicationsTableAnalyze: React.FC = () => { failedCancelTask ); - const { getApplicationAssessment } = - useFetchApplicationAssessments(applications); - const tableControls = useLocalTableControls({ idProperty: "id", items: applications || [], @@ -701,11 +697,6 @@ export const ApplicationsTableAnalyze: React.FC = () => { diff --git a/client/src/app/pages/applications/applications-table-assessment/applications-table-assessment.tsx b/client/src/app/pages/applications/applications-table-assessment/applications-table-assessment.tsx index 378cba3b8a..4106511b51 100644 --- a/client/src/app/pages/applications/applications-table-assessment/applications-table-assessment.tsx +++ b/client/src/app/pages/applications/applications-table-assessment/applications-table-assessment.tsx @@ -1,6 +1,5 @@ // External libraries import * as React from "react"; -import { useState } from "react"; import { AxiosError } from "axios"; import { useHistory } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; @@ -64,7 +63,6 @@ import { checkAccess } from "@app/utils/rbac-utils"; // Hooks import { useQueryClient } from "@tanstack/react-query"; import { useLocalTableControls } from "@app/hooks/table-controls"; -import { useAssessApplication } from "@app/hooks"; // Queries import { Application, Assessment, Task } from "@app/api/models"; @@ -74,21 +72,16 @@ import { useFetchApplications, } from "@app/queries/applications"; import { useFetchTasks } from "@app/queries/tasks"; -import { - useDeleteAssessmentMutation, - useFetchApplicationAssessments, -} from "@app/queries/assessments"; +import { useDeleteAssessmentMutation } from "@app/queries/assessments"; import { useDeleteReviewMutation, useFetchReviews } from "@app/queries/reviews"; import { useFetchIdentities } from "@app/queries/identities"; import { useFetchTagCategories } from "@app/queries/tags"; -import { useCreateBulkCopyMutation } from "@app/queries/bulkcopy"; // Relative components import { ApplicationAssessmentStatus } from "../components/application-assessment-status"; import { ApplicationBusinessService } from "../components/application-business-service"; import { ApplicationDetailDrawerAssessment } from "../components/application-detail-drawer"; import { ApplicationForm } from "../components/application-form"; -import { BulkCopyAssessmentReviewForm } from "../components/bulk-copy-assessment-review-form"; import { ImportApplicationsForm } from "../components/import-applications-form"; import { ConditionalRender } from "@app/components/ConditionalRender"; import { NoDataEmptyState } from "@app/components/NoDataEmptyState"; @@ -147,8 +140,6 @@ export const ApplicationsTable: React.FC = () => { const [assessmentOrReviewToDiscard, setAssessmentOrReviewToDiscard] = React.useState(null); - const [isSubmittingBulkCopy, setIsSubmittingBulkCopy] = useState(false); - const getTask = (application: Application) => tasks.find((task: Task) => task.application?.id === application.id); @@ -191,28 +182,6 @@ export const ApplicationsTable: React.FC = () => { onDeleteApplicationSuccess, onDeleteApplicationError ); - const onHandleCopySuccess = (hasSuccessfulReviewCopy: boolean) => { - setIsSubmittingBulkCopy(false); - pushNotification({ - title: hasSuccessfulReviewCopy - ? t("toastr.success.assessmentAndReviewCopied") - : t("toastr.success.assessmentCopied"), - variant: "success", - }); - fetchApplications(); - }; - const onHandleCopyError = (error: AxiosError) => { - setIsSubmittingBulkCopy(false); - pushNotification({ - title: getAxiosErrorMessage(error), - variant: "danger", - }); - fetchApplications(); - }; - const { mutate: createCopy, isCopying } = useCreateBulkCopyMutation({ - onSuccess: onHandleCopySuccess, - onError: onHandleCopyError, - }); const onDeleteReviewSuccess = (name: string) => { pushNotification({ @@ -251,25 +220,30 @@ export const ApplicationsTable: React.FC = () => { onDeleteError ); - const discardAssessmentAndReview = (application: Application) => { - if (application.review?.id) - deleteReview({ id: application.review.id, name: application.name }); - - const assessment = getApplicationAssessment(application.id!); - if (assessment && assessment.id) { - deleteAssessment({ id: assessment.id, name: application.name }); + const discardAssessmentAndReview = async (application: Application) => { + try { + if (application.review?.id) { + await deleteReview({ + id: application.review.id, + name: application.name, + }); + } + + if (application.assessments) { + await Promise.all( + application.assessments.map(async (assessment) => { + await deleteAssessment({ + id: assessment.id, + name: application.name, + }); + }) + ); + } + } catch (error) { + console.error("Error while deleting assessments and/or reviews:", error); } }; - const { - getApplicationAssessment, - isLoadingApplicationAssessment, - fetchErrorApplicationAssessment, - } = useFetchApplicationAssessments(applications); - - const { assessApplication, inProgress: isApplicationAssessInProgress } = - useAssessApplication(); - const tableControls = useLocalTableControls({ idProperty: "id", items: applications || [], @@ -433,11 +407,6 @@ export const ApplicationsTable: React.FC = () => { fetchError: fetchErrorReviews, } = useFetchReviews(); - const appReview = reviews?.find( - (review) => - review.id === applicationToCopyAssessmentAndReviewFrom?.review?.id - ); - const [isApplicationImportModalOpen, setIsApplicationImportModalOpen] = React.useState(false); @@ -499,7 +468,7 @@ export const ApplicationsTable: React.FC = () => { } else { application?.id && history.push( - formatPath(Paths.assessmentActions, { + formatPath(Paths.applicationAssessmentActions, { applicationId: application?.id, }) ); @@ -661,12 +630,6 @@ export const ApplicationsTable: React.FC = () => { > { { onClose={() => setSaveApplicationModalState(null)} /> - { - setApplicationToCopyAssessmentFrom(null); - fetchApplications(); - }} - > - {applicationToCopyAssessmentFrom && ( - { - setApplicationToCopyAssessmentFrom(null); - fetchApplications(); - }} - /> - )} - - { - setCopyAssessmentAndReviewModalState(null); - fetchApplications(); - }} - > - {applicationToCopyAssessmentAndReviewFrom && ( - { - setCopyAssessmentAndReviewModalState(null); - fetchApplications(); - }} - /> - )} - { onConfirm={() => { applicationToAssess && history.push( - formatPath(Paths.assessmentActions, { + formatPath(Paths.applicationAssessmentActions, { applicationId: applicationToAssess?.id, }) ); diff --git a/client/src/app/pages/applications/assessment-actions/assessment-actions-page.tsx b/client/src/app/pages/applications/assessment-actions/assessment-actions-page.tsx deleted file mode 100644 index 3c921c0a7f..0000000000 --- a/client/src/app/pages/applications/assessment-actions/assessment-actions-page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from "react"; -import { - Text, - TextContent, - PageSection, - PageSectionVariants, - Breadcrumb, - BreadcrumbItem, -} from "@patternfly/react-core"; -import { Link, useParams } from "react-router-dom"; -import { AssessmentActionsRoute, Paths } from "@app/Paths"; -import { ConditionalRender } from "@app/components/ConditionalRender"; -import { AppPlaceholder } from "@app/components/AppPlaceholder"; -import { useFetchApplicationByID } from "@app/queries/applications"; -import AssessmentActionsTable from "./components/assessment-actions-table"; - -const AssessmentActions: React.FC = () => { - const { applicationId } = useParams(); - const { application } = useFetchApplicationByID(applicationId || ""); - - return ( - <> - - - Assessment Actions - - - - Applications - - - Assessment - - - - - }> - - {application ? ( - - ) : null} - - - - - ); -}; - -export default AssessmentActions; diff --git a/client/src/app/pages/applications/assessment-actions/components/dynamic-assessment-button.tsx b/client/src/app/pages/applications/assessment-actions/components/dynamic-assessment-button.tsx deleted file mode 100644 index 8a5deed44e..0000000000 --- a/client/src/app/pages/applications/assessment-actions/components/dynamic-assessment-button.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import { Paths } from "@app/Paths"; -import { - Application, - Assessment, - InitialAssessment, - Questionnaire, -} from "@app/api/models"; -import { - useCreateAssessmentMutation, - useDeleteAssessmentMutation, -} from "@app/queries/assessments"; -import { Button } from "@patternfly/react-core"; -import React, { FunctionComponent } from "react"; -import { useHistory } from "react-router-dom"; -import "./dynamic-assessment-button.css"; -import { AxiosError } from "axios"; -import { formatPath } from "@app/utils/utils"; - -enum AssessmentAction { - Take = "Take", - Retake = "Retake", - Continue = "Continue", -} - -interface DynamicAssessmentButtonProps { - questionnaire: Questionnaire; - application: Application; - assessments?: Assessment[]; -} - -const DynamicAssessmentButton: FunctionComponent< - DynamicAssessmentButtonProps -> = ({ questionnaire, assessments, application }) => { - const history = useHistory(); - console.log("assessments", assessments); - const matchingAssessment = assessments?.find( - (assessment) => assessment.questionnaire.id === questionnaire.id - ); - console.log("matchingAssessment", matchingAssessment?.status); - - const onSuccessHandler = () => {}; - const onErrorHandler = () => {}; - - const { mutateAsync: createAssessmentAsync } = useCreateAssessmentMutation( - onSuccessHandler, - onErrorHandler - ); - - const onDeleteAssessmentSuccess = (name: string) => {}; - - const onDeleteError = (error: AxiosError) => {}; - - const { mutateAsync: deleteAssessmentAsync } = useDeleteAssessmentMutation( - onDeleteAssessmentSuccess, - onDeleteError - ); - console.log("matchingAssessment", matchingAssessment); - const determineAction = () => { - if (!matchingAssessment || matchingAssessment.status === "empty") { - return AssessmentAction.Take; - } else if (matchingAssessment.status === "started") { - return AssessmentAction.Continue; - } else { - return AssessmentAction.Retake; - } - }; - - const determineButtonClassName = () => { - const action = determineAction(); - if (action === AssessmentAction.Continue) { - return "continue-button"; - } else if (action === AssessmentAction.Retake) { - return "retake-button"; - } - }; - const createAssessment = async () => { - const newAssessment: InitialAssessment = { - questionnaire: { name: questionnaire.name, id: questionnaire.id }, - application: { name: application?.name, id: application?.id }, - // TODO handle archetypes here too - }; - - try { - const result = await createAssessmentAsync(newAssessment); - history.push( - formatPath(Paths.applicationsAssessment, { - assessmentId: result.id, - }) - ); - } catch (error) { - console.error("Error while creating assessment:", error); - } - }; - const onHandleAssessmentAction = async () => { - const action = determineAction(); - - if (action === AssessmentAction.Take) { - createAssessment(); - } else if (action === AssessmentAction.Continue) { - history.push( - formatPath(Paths.applicationsAssessment, { - assessmentId: matchingAssessment?.id, - }) - ); - } else if (action === AssessmentAction.Retake) { - if (matchingAssessment) { - try { - await deleteAssessmentAsync({ - name: matchingAssessment.name, - id: matchingAssessment.id, - }).then(() => { - createAssessment(); - }); - history.push( - formatPath(Paths.applicationsAssessment, { - assessmentId: matchingAssessment?.id, - }) - ); - } catch (error) { - console.error("Error while deleting assessment:", error); - } - } - } - }; - - const viewButtonLabel = "View"; - - return ( -
- - {matchingAssessment?.status === "complete" && ( - - )} -
- ); -}; - -export default DynamicAssessmentButton; diff --git a/client/src/app/pages/applications/components/application-assessment-status/application-assessment-status.tsx b/client/src/app/pages/applications/components/application-assessment-status/application-assessment-status.tsx index 35626e0ce3..0bc7b28536 100644 --- a/client/src/app/pages/applications/components/application-assessment-status/application-assessment-status.tsx +++ b/client/src/app/pages/applications/components/application-assessment-status/application-assessment-status.tsx @@ -10,7 +10,7 @@ import { useFetchAssessmentById } from "@app/queries/assessments"; export interface ApplicationAssessmentStatusProps { assessments?: Ref[]; - isLoading: boolean; + isLoading?: boolean; fetchError?: AxiosError; } @@ -29,7 +29,7 @@ const getStatusIconFrom = (assessment: Assessment): IconedStatusPreset => { export const ApplicationAssessmentStatus: React.FC< ApplicationAssessmentStatusProps -> = ({ assessments, isLoading, fetchError }) => { +> = ({ assessments, isLoading = false, fetchError = null }) => { const { t } = useTranslation(); //TODO: remove this once we have a proper assessment status const { assessment } = useFetchAssessmentById(assessments?.[0]?.id || 0); diff --git a/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer-assessment.tsx b/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer-assessment.tsx index 6d758d72ee..9889a331fa 100644 --- a/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer-assessment.tsx +++ b/client/src/app/pages/applications/components/application-detail-drawer/application-detail-drawer-assessment.tsx @@ -14,29 +14,25 @@ import spacing from "@patternfly/react-styles/css/utilities/Spacing/spacing"; import { EmptyTextMessage } from "@app/components/EmptyTextMessage"; import { EFFORT_ESTIMATE_LIST, PROPOSED_ACTION_LIST } from "@app/Constants"; -import { Assessment, Review, Task } from "@app/api/models"; +import { Task } from "@app/api/models"; import { ApplicationRisk } from "./application-risk"; import { ApplicationDetailDrawer, IApplicationDetailDrawerProps, } from "./application-detail-drawer"; +import { useGetReviewByAppId } from "@app/queries/reviews"; export interface IApplicationDetailDrawerAssessmentProps extends Pick { - reviews: Review[]; - assessment: Assessment | null; task: Task | undefined | null; } export const ApplicationDetailDrawerAssessment: React.FC< IApplicationDetailDrawerAssessmentProps -> = ({ application, onCloseClick, reviews, assessment, task }) => { +> = ({ application, onCloseClick, task }) => { const { t } = useTranslation(); - const appReview = reviews?.find( - (review) => review.id === application?.review?.id - ); - + const { review: appReview } = useGetReviewByAppId(application?.id || ""); const notYetReviewed = ( ); @@ -103,12 +99,7 @@ export const ApplicationDetailDrawerAssessment: React.FC< {t("terms.risk")} - {application && assessment && ( - - )} + {application && } diff --git a/client/src/app/pages/applications/components/application-detail-drawer/application-risk.tsx b/client/src/app/pages/applications/components/application-detail-drawer/application-risk.tsx index 4542a4f740..3a24bfe28c 100644 --- a/client/src/app/pages/applications/components/application-detail-drawer/application-risk.tsx +++ b/client/src/app/pages/applications/components/application-detail-drawer/application-risk.tsx @@ -1,17 +1,40 @@ -import React, { useEffect } from "react"; +import React from "react"; import { RiskLabel } from "@app/components/RiskLabel"; -import { Application, Assessment } from "@app/api/models"; +import { Application } from "@app/api/models"; +import { useFetchAssessmentsByItemId } from "@app/queries/assessments"; +import { Alert, Spinner } from "@patternfly/react-core"; export interface IApplicationRiskProps { application: Application; - assessment?: Assessment; } export const ApplicationRisk: React.FC = ({ application, - assessment, }) => { - //TODO calculate risk from assessment response - return ; + const { + assessments, + isFetching: isFetchingAssessmentsById, + fetchError, + } = useFetchAssessmentsByItemId(false, application.id); + + if (isFetchingAssessmentsById || fetchError) { + return ( + <> + {isFetchingAssessmentsById && }{" "} + {fetchError && } + + ); + } + + if (!assessments || assessments.length === 0) { + return ( + <> + ; + {isFetchingAssessmentsById && } + + ); + } + + return ; }; diff --git a/client/src/app/pages/applications/components/application-form/application-form.tsx b/client/src/app/pages/applications/components/application-form/application-form.tsx index 0196ec02d3..cf0f91d67a 100644 --- a/client/src/app/pages/applications/components/application-form/application-form.tsx +++ b/client/src/app/pages/applications/components/application-form/application-form.tsx @@ -360,7 +360,6 @@ export const ApplicationForm: React.FC = ({ id: formValues.id, migrationWave: application ? application.migrationWave : null, identities: application?.identities ? application.identities : undefined, - assessments: application?.assessments ? application.assessments : [], }; if (application) { diff --git a/client/src/app/pages/applications/components/bulk-copy-assessment-review-form/bulk-copy-assessment-review-form.tsx b/client/src/app/pages/applications/components/bulk-copy-assessment-review-form/bulk-copy-assessment-review-form.tsx deleted file mode 100644 index f1b43ee3c4..0000000000 --- a/client/src/app/pages/applications/components/bulk-copy-assessment-review-form/bulk-copy-assessment-review-form.tsx +++ /dev/null @@ -1,388 +0,0 @@ -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; - -import { - cellWidth, - ICell, - IExtraData, - IRow, - IRowData, - sortable, - truncate, -} from "@patternfly/react-table"; -import { - ActionGroup, - Button, - ButtonVariant, - Card, - CardBody, - Checkbox, - FormGroup, -} from "@patternfly/react-core"; -import ExclamationTriangleIcon from "@patternfly/react-icons/dist/esm/icons/exclamation-triangle-icon"; -import { global_palette_gold_400 as gold } from "@patternfly/react-tokens"; -import { useSelectionState } from "@migtools/lib-ui"; - -import { Application, Assessment, Review } from "@app/api/models"; -import { dedupeFunction } from "@app/utils/utils"; -import { ApplicationBusinessService } from "../application-business-service"; -import { useLegacyPaginationState } from "@app/hooks/useLegacyPaginationState"; -import { ApplicationAssessmentStatus } from "../application-assessment-status"; -import { - FilterCategory, - FilterToolbar, - FilterType, -} from "@app/components/FilterToolbar"; -import { useLegacyFilterState } from "@app/hooks/useLegacyFilterState"; -import { useLegacySortState } from "@app/hooks/useLegacySortState"; -import { useFetchApplicationAssessments } from "@app/queries/assessments"; -import { useFetchApplications } from "@app/queries/applications"; -import { useFetchTagCategories } from "@app/queries/tags"; -import { IconedStatus } from "@app/components/IconedStatus"; -import { AppTableWithControls } from "@app/components/AppTableWithControls"; -import { ToolbarBulkSelector } from "@app/components/ToolbarBulkSelector"; - -const ENTITY_FIELD = "entity"; - -const getAppFromRow = (rowData: IRowData): Application => { - return rowData[ENTITY_FIELD]; -}; - -interface BulkCopyAssessmentReviewFormProps { - application: Application; - assessment: Assessment; - review?: Review; - isSubmittingBulkCopy: boolean; - setIsSubmittingBulkCopy: (val: boolean) => void; - isCopying: boolean; - createCopy: (copyParam: any) => void; - onSaved: () => void; -} - -export const BulkCopyAssessmentReviewForm: React.FC< - BulkCopyAssessmentReviewFormProps -> = ({ - application, - assessment, - review, - isCopying, - isSubmittingBulkCopy, - setIsSubmittingBulkCopy, - createCopy, - onSaved, -}) => { - // i18 - const { t } = useTranslation(); - - const [confirmationAccepted, setConfirmationAccepted] = useState(false); - - const { - data: applications, - isFetching, - error: fetchError, - } = useFetchApplications(); - - const { tagCategories: tagCategories } = useFetchTagCategories(); - - const filterCategories: FilterCategory< - Application, - "name" | "businessService" | "description" | "tags" - >[] = [ - { - key: "name", - title: "Name", - type: FilterType.search, - placeholderText: "Filter by name...", - getItemValue: (item) => { - return item.name || ""; - }, - }, - { - key: "businessService", - title: t("terms.businessService"), - type: FilterType.multiselect, - selectOptions: dedupeFunction( - applications - ? applications - .filter((app) => !!app.businessService?.name) - .map((app) => app.businessService?.name) - .map((name) => ({ key: name, value: name })) - : [] - ), - placeholderText: - t("actions.filterBy", { - what: t("terms.businessService").toLowerCase(), - }) + "...", - getItemValue: (item) => { - return item.businessService?.name || ""; - }, - }, - { - key: "description", - title: t("terms.description"), - type: FilterType.search, - placeholderText: - t("actions.filterBy", { - what: t("terms.description").toLowerCase(), - }) + "...", - getItemValue: (item) => { - return item.description || ""; - }, - }, - { - key: "tags", - title: t("terms.tags"), - type: FilterType.multiselect, - placeholderText: - t("actions.filterBy", { - what: t("terms.tagName").toLowerCase(), - }) + "...", - getItemValue: (item) => { - let tagNames = item?.tags?.map((tag) => tag.name).join(""); - return tagNames || ""; - }, - selectOptions: dedupeFunction( - tagCategories - ?.map((tagCategory) => tagCategory?.tags) - .flat() - .filter((tag) => tag && tag.name) - .map((tag) => ({ key: tag?.name, value: tag?.name })) - ), - }, - ]; - - const { filterValues, setFilterValues, filteredItems } = useLegacyFilterState( - applications || [], - filterCategories - ); - - const getSortValues = (item: Application) => [ - "", - item?.name || "", - item.businessService?.name || "", - ]; - - const { sortBy, onSort, sortedItems } = useLegacySortState( - filteredItems, - getSortValues - ); - - const { currentPageItems, setPageNumber, paginationProps } = - useLegacyPaginationState(sortedItems, 10); - - //Bulk selection - const { - isItemSelected: isAppSelected, - isItemSelectable: isAppSelectable, - toggleItemSelected: toggleAppSelected, - selectAll, - selectMultiple, - areAllSelected, - selectedItems: selectedApps, - } = useSelectionState({ - items: filteredItems || [], - isEqual: (a, b) => a.id === b.id, - // Don't allow selecting source application as a copy target - isItemSelectable: (a) => a.id !== application.id, - }); - - // Table's assessments - const { - getApplicationAssessment, - isLoadingApplicationAssessment, - fetchErrorApplicationAssessment, - } = useFetchApplicationAssessments(applications); - - // Table - const columns: ICell[] = [ - { - title: t("terms.name"), - transforms: [sortable, cellWidth(40)], - cellTransforms: [truncate], - }, - { - title: t("terms.businessService"), - transforms: [cellWidth(30)], - cellTransforms: [truncate], - }, - { - title: t("terms.assessment"), - transforms: [cellWidth(15)], - cellTransforms: [truncate], - }, - { - title: t("terms.review"), - transforms: [cellWidth(15)], - cellTransforms: [truncate], - }, - ]; - - const rows: IRow[] = []; - currentPageItems?.forEach((app) => { - rows.push({ - [ENTITY_FIELD]: app, - selected: isAppSelected(app), - disableSelection: !isAppSelectable(app), - cells: [ - { - title: app.name, - }, - { - title: ( - <> - {app.businessService && ( - - )} - - ), - }, - { - title: ( - - ), - }, - { - title: app.review ? ( - - ) : ( - - ), - }, - ], - }); - }); - - // Row actions - const selectRow = ( - event: React.FormEvent, - isSelected: boolean, - rowIndex: number, - rowData: IRowData, - extraData: IExtraData - ) => { - const app = getAppFromRow(rowData); - toggleAppSelected(app); - }; - - const requestConfirmation = () => { - let selectedAnyAppWithAssessment = selectedApps.some((f) => { - const assessment = getApplicationAssessment(f.id!); - if ( - assessment && - (assessment.status === "complete" || assessment.status === "started") - ) - return true; - return false; - }); - - if (review) { - const selectedAnyAppWithReview = selectedApps.some((f) => f.review); - selectedAnyAppWithAssessment = - selectedAnyAppWithAssessment || selectedAnyAppWithReview; - } - return selectedAnyAppWithAssessment; - }; - - const onSubmit = () => { - setIsSubmittingBulkCopy(true); - onSaved(); - createCopy({ assessment, selectedApps, review }); - }; - const handleOnClearAllFilters = () => { - setFilterValues({}); - }; - - return ( -
- - - - } - toolbarBulkSelector={ - - } - /> - - - {requestConfirmation() && ( - - - - -    - {review - ? t("message.copyAssessmentAndReviewQuestion") - : t("message.copyAssessmentQuestion")} - - } - isStack - > - {review - ? t("message.copyAssessmentAndReviewBody") - : t("message.copyAssessmentBody")} - setConfirmationAccepted(isChecked)} - /> - - )} - - - -
- ); -}; diff --git a/client/src/app/pages/applications/components/bulk-copy-assessment-review-form/index.tsx b/client/src/app/pages/applications/components/bulk-copy-assessment-review-form/index.tsx deleted file mode 100644 index 93a35862fb..0000000000 --- a/client/src/app/pages/applications/components/bulk-copy-assessment-review-form/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { BulkCopyAssessmentReviewForm } from "./bulk-copy-assessment-review-form"; diff --git a/client/src/app/pages/archetypes/archetypes-page.tsx b/client/src/app/pages/archetypes/archetypes-page.tsx index 58ec34debf..1ae503fec9 100644 --- a/client/src/app/pages/archetypes/archetypes-page.tsx +++ b/client/src/app/pages/archetypes/archetypes-page.tsx @@ -52,8 +52,9 @@ import ArchetypeMaintainersColumn from "./components/archetype-maintainers-colum import ArchetypeTagsColumn from "./components/archetype-tags-column"; import { Archetype } from "@app/api/models"; import { ConfirmDialog } from "@app/components/ConfirmDialog"; -import { getAxiosErrorMessage } from "@app/utils/utils"; +import { formatPath, getAxiosErrorMessage } from "@app/utils/utils"; import { AxiosError } from "axios"; +import { Paths } from "@app/Paths"; const Archetypes: React.FC = () => { const { t } = useTranslation(); @@ -157,6 +158,38 @@ const Archetypes: React.FC = () => { {t("dialog.title.newArchetype")} ); + const [isOverrideModalOpen, setOverrideModalOpen] = React.useState(false); + const [archetypeToAssess, setArchetypeToAssess] = + React.useState(null); + + const assessSelectedArchetype = (archetype: Archetype) => { + // if application/archetype has an assessment, ask if user wants to override it + const matchingAssessment = false; + if (matchingAssessment) { + setOverrideModalOpen(true); + setArchetypeToAssess(archetype); + } else { + archetype?.id && + history.push( + formatPath(Paths.archetypeAssessmentActions, { + archetypeId: archetype?.id, + }) + ); + setArchetypeToAssess(null); + } + }; + const reviewSelectedArchetype = (archetype: Archetype) => { + //TODO: Review archetype + // if (application.review) { + // setReviewToEdit(application.id); + // } else { + // history.push( + // formatPath(Paths.applicationsReview, { + // applicationId: application.id, + // }) + // ); + // } + }; return ( <> @@ -250,9 +283,14 @@ const Archetypes: React.FC = () => { alert("TODO"), + // }, { - title: t("actions.duplicate"), - onClick: () => alert("TODO"), + title: t("actions.assess"), + onClick: () => + assessSelectedArchetype(archetype), }, { title: t("actions.edit"), @@ -298,7 +336,7 @@ const Archetypes: React.FC = () => { > setArchetypeToEdit(null)} />
@@ -326,6 +364,26 @@ const Archetypes: React.FC = () => { } }} /> + setArchetypeToAssess(null)} + onClose={() => setArchetypeToAssess(null)} + onConfirm={() => { + archetypeToAssess && + history.push( + formatPath(Paths.archetypeAssessmentActions, { + archetypeId: archetypeToAssess?.id, + }) + ); + setArchetypeToAssess(null); + }} + /> ); }; diff --git a/client/src/app/pages/archetypes/components/archetype-form/archetype-form.tsx b/client/src/app/pages/archetypes/components/archetype-form/archetype-form.tsx index 56e9fd056e..7bc1d62bb3 100644 --- a/client/src/app/pages/archetypes/components/archetype-form/archetype-form.tsx +++ b/client/src/app/pages/archetypes/components/archetype-form/archetype-form.tsx @@ -14,6 +14,7 @@ import { import type { Archetype, + New, Stakeholder, StakeholderGroup, Tag, @@ -49,19 +50,17 @@ export interface ArchetypeFormValues { } export interface ArchetypeFormProps { - toEdit?: Archetype | null; + archetype?: Archetype | null; onClose: () => void; } export const ArchetypeForm: React.FC = ({ - toEdit = null, + archetype, onClose, }) => { - const isCreate = toEdit === null; const { t } = useTranslation(); const { - archetype, // TODO: Use this or just rely on `toEdit`? existingArchetypes, tags, stakeholders, @@ -69,7 +68,7 @@ export const ArchetypeForm: React.FC = ({ createArchetype, updateArchetype, } = useArchetypeFormData({ - id: toEdit?.id, + id: archetype?.id, onActionSuccess: onClose, }); @@ -84,7 +83,7 @@ export const ArchetypeForm: React.FC = ({ "Duplicate name", "An archetype with this name already exists. Use a different name.", (value) => - duplicateNameCheck(existingArchetypes, toEdit || null, value ?? "") + duplicateNameCheck(existingArchetypes, archetype || null, value ?? "") ), description: yup @@ -119,26 +118,30 @@ export const ArchetypeForm: React.FC = ({ handleSubmit, formState: { isSubmitting, isValidating, isValid, isDirty }, control, + //for debugging + getValues, + getFieldState, + formState, } = useForm({ defaultValues: { - name: toEdit?.name || "", - description: toEdit?.description || "", - comments: toEdit?.comments || "", + name: archetype?.name || "", + description: archetype?.description || "", + comments: archetype?.comments || "", - criteriaTags: toEdit?.criteriaTags?.map((tag) => tag.name).sort() ?? [], - tags: toEdit?.archetypeTags?.map((tag) => tag.name).sort() ?? [], + criteriaTags: + archetype?.criteriaTags?.map((tag) => tag.name).sort() ?? [], + tags: archetype?.tags?.map((tag) => tag.name).sort() ?? [], - stakeholders: toEdit?.stakeholders?.map((sh) => sh.name).sort() ?? [], + stakeholders: archetype?.stakeholders?.map((sh) => sh.name).sort() ?? [], stakeholderGroups: - toEdit?.stakeholderGroups?.map((sg) => sg.name).sort() ?? [], + archetype?.stakeholderGroups?.map((sg) => sg.name).sort() ?? [], }, resolver: yupResolver(validationSchema), mode: "all", }); const onValidSubmit = (values: ArchetypeFormValues) => { - const payload: Archetype = { - id: toEdit?.id || -1, // TODO: verify the -1 will be thrown out on create + const payload: New = { name: values.name.trim(), description: values.description?.trim() ?? "", comments: values.comments?.trim() ?? "", @@ -147,7 +150,7 @@ export const ArchetypeForm: React.FC = ({ .map((tagName) => tags.find((tag) => tag.name === tagName)) .filter(Boolean) as Tag[], - archetypeTags: values.tags + tags: values.tags .map((tagName) => tags.find((tag) => tag.name === tagName)) .filter(Boolean) as Tag[], @@ -166,19 +169,19 @@ export const ArchetypeForm: React.FC = ({ .filter(Boolean) as StakeholderGroup[]), }; - if (isCreate) { - createArchetype(payload); + if (archetype) { + updateArchetype({ id: archetype.id, ...payload }); } else { - updateArchetype(payload); + createArchetype(payload); } }; - + console.log("values", getValues(), getFieldState("criteriaTags"), formState); return (
@@ -186,7 +189,7 @@ export const ArchetypeForm: React.FC = ({ @@ -194,7 +197,7 @@ export const ArchetypeForm: React.FC = ({ items={tags} control={control} name="criteriaTags" - label="Criteria Tags" // TODO: l10n + label="Criteria Tags" fieldId="criteriaTags" isRequired noResultsMessage={t("message.noResultsFoundTitle")} @@ -208,7 +211,7 @@ export const ArchetypeForm: React.FC = ({ items={tags} control={control} name="tags" - label="Archetype Tags" // TODO: l10n + label="Archetype Tags" fieldId="archetypeTags" isRequired noResultsMessage={t("message.noResultsFoundTitle")} @@ -222,7 +225,7 @@ export const ArchetypeForm: React.FC = ({ items={stakeholders} control={control} name="stakeholders" - label="Stakeholder(s)" // TODO: l10n + label="Stakeholder(s)" fieldId="stakeholders" noResultsMessage={t("message.noResultsFoundTitle")} placeholderText={t("composed.selectMany", { @@ -235,7 +238,7 @@ export const ArchetypeForm: React.FC = ({ items={stakeholderGroups} control={control} name="stakeholderGroups" - label="Stakeholder Group(s)" // TODO: l10n + label="Stakeholder Group(s)" fieldId="stakeholderGroups" noResultsMessage={t("message.noResultsFoundTitle")} placeholderText={t("composed.selectMany", { @@ -260,7 +263,7 @@ export const ArchetypeForm: React.FC = ({ variant={ButtonVariant.primary} isDisabled={!isValid || isSubmitting || isValidating || !isDirty} > - {isCreate ? t("actions.create") : t("actions.save")} + {!archetype ? t("actions.create") : t("actions.save")} + {assessment?.status === "complete" ? ( + + ) : null} +
+ + {assessment ? ( + + + + ) : null} + + ); +}; + +export default DynamicAssessmentActionsRow; diff --git a/client/src/app/pages/applications/assessment-actions/components/questionnaires-table.tsx b/client/src/app/pages/assessment/components/assessment-actions/components/questionnaires-table.tsx similarity index 55% rename from client/src/app/pages/applications/assessment-actions/components/questionnaires-table.tsx rename to client/src/app/pages/assessment/components/assessment-actions/components/questionnaires-table.tsx index 132002ff08..9eaaa5f221 100644 --- a/client/src/app/pages/applications/assessment-actions/components/questionnaires-table.tsx +++ b/client/src/app/pages/assessment/components/assessment-actions/components/questionnaires-table.tsx @@ -8,24 +8,19 @@ import { TableRowContentWithControls, } from "@app/components/TableControls"; import { NoDataEmptyState } from "@app/components/NoDataEmptyState"; -import { Application, Assessment, Questionnaire } from "@app/api/models"; -import DynamicAssessmentButton from "./dynamic-assessment-button"; import { - assessmentsByAppIdQueryKey, - useDeleteAssessmentMutation, -} from "@app/queries/assessments"; -import { Button } from "@patternfly/react-core"; -import { TrashIcon } from "@patternfly/react-icons"; -import { NotificationsContext } from "@app/components/NotificationsContext"; -import { useQueryClient } from "@tanstack/react-query"; -import { useTranslation } from "react-i18next"; -import { getAxiosErrorMessage } from "@app/utils/utils"; -import { AxiosError } from "axios"; + Application, + Archetype, + Assessment, + Questionnaire, +} from "@app/api/models"; +import DynamicAssessmentActionsRow from "./dynamic-assessment-actions-row"; interface QuestionnairesTableProps { tableName: string; isFetching: boolean; application?: Application; + archetype?: Archetype; assessments?: Assessment[]; questionnaires?: Questionnaire[]; } @@ -35,41 +30,12 @@ const QuestionnairesTable: React.FC = ({ questionnaires, isFetching, application, + archetype, tableName, }) => { - const { t } = useTranslation(); - const { pushNotification } = React.useContext(NotificationsContext); - const queryClient = useQueryClient(); - - const onDeleteAssessmentSuccess = (name: string) => { - pushNotification({ - title: t("toastr.success.assessmentDiscarded", { - application: name, - }), - variant: "success", - }); - queryClient.invalidateQueries([assessmentsByAppIdQueryKey]); - }; - - const onDeleteError = (error: AxiosError) => { - pushNotification({ - title: getAxiosErrorMessage(error), - variant: "danger", - }); - }; - - const { mutate: deleteAssessment } = useDeleteAssessmentMutation( - onDeleteAssessmentSuccess, - onDeleteError - ); - - if (!questionnaires) { - return
Application is undefined
; - } - const tableControls = useLocalTableControls({ idProperty: "id", - items: questionnaires, + items: questionnaires || [], columnNames: { questionnaires: tableName, }, @@ -83,11 +49,6 @@ const QuestionnairesTable: React.FC = ({ numRenderedColumns, propHelpers: { tableProps, getThProps, getTdProps }, } = tableControls; - - if (!questionnaires || !application) { - return
No data available.
; - } - return ( <> = ({ @@ -116,6 +77,7 @@ const QuestionnairesTable: React.FC = ({ const matchingAssessment = assessments?.find( (assessment) => assessment.questionnaire.id === questionnaire.id ); + return ( = ({ > {questionnaire.name} - - {matchingAssessment ? ( - ) : null} diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page-header.tsx b/client/src/app/pages/assessment/components/assessment-page-header.tsx similarity index 93% rename from client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page-header.tsx rename to client/src/app/pages/assessment/components/assessment-page-header.tsx index 098d729932..c587b96679 100644 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-page/application-assessment-page-header.tsx +++ b/client/src/app/pages/assessment/components/assessment-page-header.tsx @@ -10,18 +10,18 @@ import { Paths } from "@app/Paths"; import { Application, Assessment } from "@app/api/models"; import { getApplicationById } from "@app/api/rest"; -export interface IApplicationAssessmentPageHeaderProps { +export interface AssessmentPageHeaderProps { assessment?: Assessment; } -export const ApplicationAssessmentPageHeader: React.FC< - IApplicationAssessmentPageHeaderProps -> = ({ assessment }) => { +export const AssessmentPageHeader: React.FC = ({ + assessment, +}) => { const { t } = useTranslation(); const history = useHistory(); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = - React.useState(false); + React.useState(false); const [application, setApplication] = useState(); diff --git a/client/src/app/pages/applications/application-assessment/components/assessment-stakeholders-form/assessment-stakeholders-form.tsx b/client/src/app/pages/assessment/components/assessment-stakeholders-form/assessment-stakeholders-form.tsx similarity index 83% rename from client/src/app/pages/applications/application-assessment/components/assessment-stakeholders-form/assessment-stakeholders-form.tsx rename to client/src/app/pages/assessment/components/assessment-stakeholders-form/assessment-stakeholders-form.tsx index 319f086285..044e1fbd1b 100644 --- a/client/src/app/pages/applications/application-assessment/components/assessment-stakeholders-form/assessment-stakeholders-form.tsx +++ b/client/src/app/pages/assessment/components/assessment-stakeholders-form/assessment-stakeholders-form.tsx @@ -12,16 +12,12 @@ import { useFormContext } from "react-hook-form"; import { useFetchStakeholders } from "@app/queries/stakeholders"; import { useFetchStakeholderGroups } from "@app/queries/stakeholdergoups"; -import { ApplicationAssessmentWizardValues } from "../application-assessment-wizard/application-assessment-wizard"; -import { HookFormPFGroupController } from "@app/components/HookFormPFFields"; -import { OptionWithValue, SimpleSelect } from "@app/components/SimpleSelect"; -import { Stakeholder, StakeholderGroup } from "@app/api/models"; import ItemsSelect from "@app/components/items-select/items-select"; +import { AssessmentWizardValues } from "../assessment-wizard/assessment-wizard"; export const AssessmentStakeholdersForm: React.FC = () => { const { t } = useTranslation(); - const { setValue, control, formState } = - useFormContext(); + const { control } = useFormContext(); const { stakeholders } = useFetchStakeholders(); const { stakeholderGroups } = useFetchStakeholderGroups(); diff --git a/client/src/app/pages/applications/application-assessment/components/assessment-summary/assessment-summary-page.css b/client/src/app/pages/assessment/components/assessment-summary/assessment-summary-page.css similarity index 100% rename from client/src/app/pages/applications/application-assessment/components/assessment-summary/assessment-summary-page.css rename to client/src/app/pages/assessment/components/assessment-summary/assessment-summary-page.css diff --git a/client/src/app/pages/applications/application-assessment/components/assessment-summary/assessment-summary-page.tsx b/client/src/app/pages/assessment/components/assessment-summary/assessment-summary-page.tsx similarity index 100% rename from client/src/app/pages/applications/application-assessment/components/assessment-summary/assessment-summary-page.tsx rename to client/src/app/pages/assessment/components/assessment-summary/assessment-summary-page.tsx diff --git a/client/src/app/pages/applications/application-assessment/components/application-assessment-wizard/application-assessment-wizard.tsx b/client/src/app/pages/assessment/components/assessment-wizard/assessment-wizard.tsx similarity index 82% rename from client/src/app/pages/applications/application-assessment/components/application-assessment-wizard/application-assessment-wizard.tsx rename to client/src/app/pages/assessment/components/assessment-wizard/assessment-wizard.tsx index 236e209715..b7353d8e56 100644 --- a/client/src/app/pages/applications/application-assessment/components/application-assessment-wizard/application-assessment-wizard.tsx +++ b/client/src/app/pages/assessment/components/assessment-wizard/assessment-wizard.tsx @@ -12,7 +12,6 @@ import { Question, Section, } from "@app/api/models"; -import { AssessmentStakeholdersForm } from "../assessment-stakeholders-form"; import { CustomWizardFooter } from "../custom-wizard-footer"; import { getApplicationById } from "@app/api/rest"; import { NotificationsContext } from "@app/components/NotificationsContext"; @@ -23,18 +22,18 @@ import { useFetchQuestionnaires } from "@app/queries/questionnaires"; import { COMMENTS_KEY, QUESTIONS_KEY, - getCommentFieldName, getQuestionFieldName, } from "../../form-utils"; import { AxiosError } from "axios"; import { - assessmentsByAppIdQueryKey, + assessmentsByItemIdQueryKey, useUpdateAssessmentMutation, } from "@app/queries/assessments"; import { useQueryClient } from "@tanstack/react-query"; import { formatPath, getAxiosErrorMessage } from "@app/utils/utils"; import { Paths } from "@app/Paths"; import { yupResolver } from "@hookform/resolvers/yup"; +import { AssessmentStakeholdersForm } from "../assessment-stakeholders-form/assessment-stakeholders-form"; export const SAVE_ACTION_KEY = "saveAction"; @@ -44,7 +43,7 @@ export enum SAVE_ACTION_VALUE { SAVE_AS_DRAFT, } -export interface ApplicationAssessmentWizardValues { +export interface AssessmentWizardValues { stakeholders: string[]; stakeholderGroups: string[]; [COMMENTS_KEY]: { @@ -56,19 +55,22 @@ export interface ApplicationAssessmentWizardValues { [SAVE_ACTION_KEY]: SAVE_ACTION_VALUE; } -export interface ApplicationAssessmentWizardProps { +export interface AssessmentWizardProps { assessment?: Assessment; isOpen: boolean; + isArchetype?: boolean; } -export const ApplicationAssessmentWizard: React.FC< - ApplicationAssessmentWizardProps -> = ({ assessment, isOpen }) => { +export const AssessmentWizard: React.FC = ({ + assessment, + isOpen, + isArchetype, +}) => { const queryClient = useQueryClient(); const { questionnaires } = useFetchQuestionnaires(); const onHandleUpdateAssessmentSuccess = () => { queryClient.invalidateQueries([ - assessmentsByAppIdQueryKey, + assessmentsByItemIdQueryKey, assessment?.application?.id, ]); }; @@ -138,15 +140,18 @@ export const ApplicationAssessmentWizard: React.FC< const validationSchema = yup.object().shape({ stakeholders: yup.array().of(yup.string()), - stakeholderGroups: yup.array().of(yup.string()), }); - const methods = useForm({ + const methods = useForm({ defaultValues: useMemo(() => { return { - // stakeholders: assessment?.stakeholders || [], - // stakeholderGroups: assessment?.stakeholderGroups || [], + stakeholders: + assessment?.stakeholders.map((stakeholder) => stakeholder.name) || [], + stakeholderGroups: + assessment?.stakeholderGroups.map( + (stakeholderGroup) => stakeholderGroup.name + ) || [], // comments: initialComments, questions: initialQuestions, [SAVE_ACTION_KEY]: SAVE_ACTION_VALUE.SAVE_AS_DRAFT, @@ -168,10 +173,9 @@ export const ApplicationAssessmentWizard: React.FC< const isFirstStepValid = () => { // TODO: Wire up stakeholder support for assessment when available - // const numberOfStakeholdlers = values.stakeholders.length; - // const numberOfGroups = values.stakeholderGroups.length; - // return numberOfStakeholdlers + numberOfGroups > 0; - return true; + const numberOfStakeholdlers = values?.stakeholders?.length || 0; + const numberOfGroups = values?.stakeholderGroups?.length || 0; + return numberOfStakeholdlers + numberOfGroups > 0; }; const isQuestionValid = (question: Question): boolean => { @@ -188,7 +192,7 @@ export const ApplicationAssessmentWizard: React.FC< const questionHasValue = (question: Question): boolean => { const questionValues = values.questions || {}; const value = questionValues[getQuestionFieldName(question, false)]; - return value !== null && value !== undefined; + return value !== null && value !== undefined && value !== ""; }; //TODO: Add comments to the sections // const commentMinLenghtIs1 = (category: QuestionnaireCategory): boolean => { @@ -198,9 +202,14 @@ export const ApplicationAssessmentWizard: React.FC< // }; const shouldNextBtnBeEnabled = (section: Section): boolean => { + const allQuestionsValid = section?.questions.every((question) => + isQuestionValid(question) + ); + const allQuestionsAnswered = section?.questions.every((question) => { + return questionHasValue(question); + }); return ( - section.questions.every((question) => isQuestionValid(question)) && - section.questions.every((question) => questionHasValue(question)) + allQuestionsAnswered && allQuestionsValid // && isCommentValid(category) ); }; @@ -213,11 +222,11 @@ export const ApplicationAssessmentWizard: React.FC< ? sortedSections.findIndex((f) => f.name === maxCategoryWithData.name) + 1 : 0; - const onInvalid = (errors: FieldErrors) => + const onInvalid = (errors: FieldErrors) => console.error("form errors", errors); const buildSectionsFromFormValues = ( - formValues: ApplicationAssessmentWizardValues + formValues: AssessmentWizardValues ): Section[] => { if (!formValues || !formValues[QUESTIONS_KEY]) { return []; @@ -252,9 +261,7 @@ export const ApplicationAssessmentWizard: React.FC< return sections; }; - const handleSaveAsDraft = async ( - formValues: ApplicationAssessmentWizardValues - ) => { + const handleSaveAsDraft = async (formValues: AssessmentWizardValues) => { try { if (!assessment?.application?.id) { console.log("An assessment must exist in order to save as draft"); @@ -276,11 +283,19 @@ export const ApplicationAssessmentWizard: React.FC< title: "Assessment has been saved as a draft.", variant: "info", }); - history.push( - formatPath(Paths.assessmentActions, { - applicationId: assessment?.application?.id, - }) - ); + if (isArchetype) { + history.push( + formatPath(Paths.archetypeAssessmentActions, { + archetypeId: assessment?.archetype?.id, + }) + ); + } else { + history.push( + formatPath(Paths.applicationAssessmentActions, { + applicationId: assessment?.application?.id, + }) + ); + } } catch (error) { pushNotification({ title: "Failed to save as a draft.", @@ -290,7 +305,7 @@ export const ApplicationAssessmentWizard: React.FC< } }; - const handleSave = async (formValues: ApplicationAssessmentWizardValues) => { + const handleSave = async (formValues: AssessmentWizardValues) => { try { if (!assessment?.application?.id) { console.log("An assessment must exist in order to save."); @@ -313,11 +328,19 @@ export const ApplicationAssessmentWizard: React.FC< variant: "success", }); - history.push( - formatPath(Paths.assessmentActions, { - applicationId: assessment?.application?.id, - }) - ); + if (isArchetype) { + history.push( + formatPath(Paths.archetypeAssessmentActions, { + archetypeId: assessment?.archetype?.id, + }) + ); + } else { + history.push( + formatPath(Paths.applicationAssessmentActions, { + applicationId: assessment?.application?.id, + }) + ); + } } catch (error) { pushNotification({ title: "Failed to save.", @@ -327,9 +350,7 @@ export const ApplicationAssessmentWizard: React.FC< } }; - const handleSaveAndReview = async ( - formValues: ApplicationAssessmentWizardValues - ) => { + const handleSaveAndReview = async (formValues: AssessmentWizardValues) => { try { if (!assessment?.application?.id) { console.log("An assessment must exist in order to save."); @@ -379,7 +400,7 @@ export const ApplicationAssessmentWizard: React.FC< } }; - const onSubmit = async (formValues: ApplicationAssessmentWizardValues) => { + const onSubmit = async (formValues: AssessmentWizardValues) => { if (!assessment?.application?.id) { console.log("An assessment must exist in order to save the form"); return; @@ -434,7 +455,12 @@ export const ApplicationAssessmentWizard: React.FC< { const saveActionValue = review @@ -480,11 +506,19 @@ export const ApplicationAssessmentWizard: React.FC< onClose={() => setIsConfirmDialogOpen(false)} onConfirm={() => { setIsConfirmDialogOpen(false); - history.push( - formatPath(Paths.assessmentActions, { - applicationId: assessment?.application?.id, - }) - ); + if (isArchetype) { + history.push( + formatPath(Paths.archetypeAssessmentActions, { + archetypeId: assessment?.archetype?.id, + }) + ); + } else { + history.push( + formatPath(Paths.applicationAssessmentActions, { + applicationId: assessment?.application?.id, + }) + ); + } }} /> )} diff --git a/client/src/app/pages/applications/application-assessment/components/custom-wizard-footer/custom-wizard-footer.tsx b/client/src/app/pages/assessment/components/custom-wizard-footer/custom-wizard-footer.tsx similarity index 89% rename from client/src/app/pages/applications/application-assessment/components/custom-wizard-footer/custom-wizard-footer.tsx rename to client/src/app/pages/assessment/components/custom-wizard-footer/custom-wizard-footer.tsx index 786e83c583..d4c5bd98e5 100644 --- a/client/src/app/pages/applications/application-assessment/components/custom-wizard-footer/custom-wizard-footer.tsx +++ b/client/src/app/pages/assessment/components/custom-wizard-footer/custom-wizard-footer.tsx @@ -69,23 +69,18 @@ export const CustomWizardFooter: React.FC = ({ - diff --git a/client/src/app/pages/review/review-page.tsx b/client/src/app/pages/review/review-page.tsx new file mode 100644 index 0000000000..6e1053b9a7 --- /dev/null +++ b/client/src/app/pages/review/review-page.tsx @@ -0,0 +1,132 @@ +import React from "react"; +import { useParams } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { + Bullseye, + FormSection, + Grid, + GridItem, + PageSection, + Text, +} from "@patternfly/react-core"; +import BanIcon from "@patternfly/react-icons/dist/esm/icons/ban-icon"; + +import { Paths, ReviewRoute } from "@app/Paths"; +import { ApplicationDetails } from "./components/application-details"; +import { ReviewForm } from "./components/review-form"; +import { SimpleEmptyState } from "@app/components/SimpleEmptyState"; +import { ConditionalRender } from "@app/components/ConditionalRender"; +import { AppPlaceholder } from "@app/components/AppPlaceholder"; +import { ApplicationAssessmentDonutChart } from "./components/application-assessment-donut-chart/application-assessment-donut-chart"; +import QuestionnaireSummary, { + SummaryType, +} from "@app/components/questionnaire-summary/questionnaire-summary"; +import { PageHeader } from "@app/components/PageHeader"; +import { useGetReviewByAppId } from "@app/queries/reviews"; + +const ReviewPage: React.FC = () => { + const { t } = useTranslation(); + + const { applicationId, archetypeId } = useParams(); + + const { application, review, fetchError, isFetching } = useGetReviewByAppId( + applicationId || "" + ); + + //TODO: Review archetypes? + // const { archetype } = useFetchArchetypeById(archetypeId || ""); + + //TODO: Add a dropdown with multiple assessments to choose from + const assessment = undefined; + + if (fetchError) { + return ( + <> + + {t("message.reviewInstructions")} + } + breadcrumbs={[ + { + title: t("terms.applications"), + path: Paths.applications, + }, + { + title: t("terms.review"), + path: Paths.applicationsReview, + }, + ]} + menuActions={[]} + /> + + + + + + + + ); + } + return ( + <> + + {t("message.reviewInstructions")} + } + breadcrumbs={[ + { + title: t("terms.applications"), + path: Paths.applications, + }, + { + title: t("terms.review"), + path: Paths.applicationsReview, + }, + ]} + menuActions={[]} + /> + + + }> + + {application && ( + +
+ + + + + + +
+
+ )} + {assessment && ( + + + + )} +
+
+ {assessment && ( + + )} +
+ + ); +}; +export default ReviewPage; diff --git a/client/src/app/queries/archetypes.ts b/client/src/app/queries/archetypes.ts index 5ab042b81e..bce61aba1c 100644 --- a/client/src/app/queries/archetypes.ts +++ b/client/src/app/queries/archetypes.ts @@ -30,7 +30,7 @@ export const useFetchArchetypes = () => { }; }; -export const useFetchArchetypeById = (id?: number) => { +export const useFetchArchetypeById = (id?: number | string) => { const { data, isLoading, error } = useQuery({ queryKey: [ARCHETYPE_QUERY_KEY, id], queryFn: () => diff --git a/client/src/app/queries/assessments.ts b/client/src/app/queries/assessments.ts index 1f0f0ac437..eb327018df 100644 --- a/client/src/app/queries/assessments.ts +++ b/client/src/app/queries/assessments.ts @@ -1,68 +1,35 @@ -import { - useMutation, - useQueries, - useQuery, - useQueryClient, - UseQueryResult, -} from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { createAssessment, deleteAssessment, getAssessmentById, - getAssessments, - getAssessmentsByAppId, + getAssessmentsByItemId, updateAssessment, } from "@app/api/rest"; import { AxiosError } from "axios"; -import { Application, Assessment, InitialAssessment } from "@app/api/models"; +import { Assessment, InitialAssessment } from "@app/api/models"; import { QuestionnairesQueryKey } from "./questionnaires"; export const assessmentsQueryKey = "assessments"; export const assessmentQueryKey = "assessment"; -export const assessmentsByAppIdQueryKey = "assessmentsByAppId"; - -export const useFetchApplicationAssessments = ( - applications: Application[] = [] -) => { - const queryResults = useQueries({ - queries: applications.map((application) => ({ - queryKey: [assessmentsQueryKey, application.id], - queryFn: async () => { - const response = await getAssessments({ - applicationId: application.id, - }); - const allAssessmentsForApp = response; - return allAssessmentsForApp[0] || []; - }, - onError: (error: AxiosError) => console.log("error, ", error), - })), - }); - const queryResultsByAppId: Record> = {}; - applications.forEach((application, i) => { - if (application.id) queryResultsByAppId[application.id] = queryResults[i]; - }); - return { - getApplicationAssessment: (id: number) => queryResultsByAppId[id]?.data, - isLoadingApplicationAssessment: (id: number) => - queryResultsByAppId[id].isLoading, - fetchErrorApplicationAssessment: (id: number) => - queryResultsByAppId[id].error as AxiosError | undefined, - }; -}; +export const assessmentsByItemIdQueryKey = "assessmentsByItemId"; export const useCreateAssessmentMutation = ( + isArchetype: boolean, onSuccess: (name: string) => void, onError: (err: AxiosError) => void ) => { const queryClient = useQueryClient(); return useMutation({ - mutationFn: (assessment: InitialAssessment) => createAssessment(assessment), + mutationFn: (assessment: InitialAssessment) => + createAssessment(assessment, isArchetype), onSuccess: (res) => { queryClient.invalidateQueries([ - assessmentsByAppIdQueryKey, + assessmentsByItemIdQueryKey, res?.application?.id, + res?.archetype?.id, ]); }, onError: onError, @@ -81,7 +48,7 @@ export const useUpdateAssessmentMutation = ( onSuccess && onSuccess(args.name); queryClient.invalidateQueries([ QuestionnairesQueryKey, - assessmentsByAppIdQueryKey, + assessmentsByItemIdQueryKey, _?.application?.id, ]); }, @@ -101,7 +68,7 @@ export const useDeleteAssessmentMutation = ( onSuccess: (_, args) => { onSuccess(args.name); queryClient.invalidateQueries([ - assessmentsByAppIdQueryKey, + assessmentsByItemIdQueryKey, args.id, QuestionnairesQueryKey, ]); @@ -123,13 +90,17 @@ export const useFetchAssessmentById = (id: number | string) => { }; }; -export const useFetchAssessmentsByAppId = (applicationId: number | string) => { +export const useFetchAssessmentsByItemId = ( + isArchetype: boolean, + itemId?: number | string +) => { const { data, isLoading, error } = useQuery({ - queryKey: [assessmentsByAppIdQueryKey, applicationId], - queryFn: () => getAssessmentsByAppId(applicationId), + queryKey: [assessmentsByItemIdQueryKey, itemId], + queryFn: () => getAssessmentsByItemId(isArchetype, itemId), onError: (error: AxiosError) => console.log("error, ", error), onSuccess: (data) => {}, }); + return { assessments: data, isFetching: isLoading, diff --git a/client/src/app/queries/bulkcopy.ts b/client/src/app/queries/bulkcopy.ts deleted file mode 100644 index 183f4fb8c1..0000000000 --- a/client/src/app/queries/bulkcopy.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { useMutation, useQuery } from "@tanstack/react-query"; -import { Application, Assessment, Review } from "@app/api/models"; -import { - createBulkCopyAssessment, - createBulkCopyReview, - getBulkCopyAssessment, -} from "@app/api/rest"; -import React from "react"; -import { NotificationsContext } from "@app/components/NotificationsContext"; -import { AxiosError } from "axios"; - -export const BulkCopyProgressQueryKey = "bulkcopyprogress"; -export const useCreateBulkCopyMutation = ({ - onSuccess, - onError, -}: { - onSuccess: (res: any) => void; - onError: (err: AxiosError) => void; -}) => { - const { pushNotification } = React.useContext(NotificationsContext); - const { - data: mutationResult, - mutate, - isLoading: isBulkCopyLoading, - reset, - } = useMutation( - ({ - assessment, - selectedApps, - review, - }: { - assessment: Assessment; - selectedApps: Application[]; - review?: Review; - }) => - createBulkCopyAssessment({ - fromAssessmentId: assessment?.id || 0, - applications: - selectedApps?.map((f) => ({ applicationId: f.id! })) || [], - }) - .then((bulkAssessment) => { - const bulkReview = review - ? createBulkCopyReview({ - sourceReview: review!.id!, - targetApplications: selectedApps.map((f) => f.id!), - }) - : undefined; - return Promise.all([bulkAssessment, bulkReview]); - }) - .then(([assessmentBulk, reviewBulk]) => { - return { - assessmentBulk, - reviewBulk, - hasReview: !!review, - }; - }), - { - onError: (error: AxiosError) => { - console.error(error); - onError(error); - }, - } - ); - - const assessmentBulkResult = mutationResult?.assessmentBulk.data || null; - - const reviewBulkResult = mutationResult?.reviewBulk || null; - - const hasReview = mutationResult?.hasReview; - - //Fetch until assessment bulk copy is complete - const { - data: assessmentData, - isInitialLoading: isBulkCopyAssessmentLoading, - } = useQuery( - [BulkCopyProgressQueryKey, assessmentBulkResult?.bulkId], - getBulkCopyAssessment, - { - onSuccess: (res) => { - // Checks that both the - // - bulk review request - // and - // - bulk assessment copy request - // are successful. Bubbles a success event if so. - - const hasSuccessfulReviewCopy = reviewBulkResult?.status === 204; - if ( - res?.data.completed && - (hasReview ? hasSuccessfulReviewCopy : true) - ) { - reset(); - onSuccess(hasSuccessfulReviewCopy); - } - }, - onError: (error: AxiosError) => { - console.error(error); - pushNotification({ - title: "Failed", - message: "Copy assessment failed.", - variant: "danger", - }); - - reset(); - onError(error); - }, - enabled: assessmentBulkResult !== null, - refetchInterval: isBulkCopyLoading ? false : 1000, - refetchIntervalInBackground: true, - refetchOnWindowFocus: false, - } - ); - - return { - mutate, - assessmentData, - isCopying: - isBulkCopyLoading || - isBulkCopyAssessmentLoading || - !!assessmentBulkResult?.bulkId, - }; -}; diff --git a/client/src/app/queries/reviews.ts b/client/src/app/queries/reviews.ts index 468de73e62..9af0e66976 100644 --- a/client/src/app/queries/reviews.ts +++ b/client/src/app/queries/reviews.ts @@ -1,34 +1,73 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { deleteReview, getReviews } from "@app/api/rest"; -import { Review } from "@app/api/models"; -import { AxiosError, AxiosResponse } from "axios"; - -export interface IReviewFetchState { - reviews: Review[]; - isFetching: boolean; - fetchError: any; - refetch: any; -} +import { + createReview, + deleteReview, + getReviewById, + getReviews, + updateReview, +} from "@app/api/rest"; +import { New, Review } from "@app/api/models"; +import { AxiosError } from "axios"; +import { useFetchApplicationByID } from "./applications"; +export const reviewQueryKey = "review"; +export const reviewsByItemIdQueryKey = "reviewsByItemId"; export const reviewsQueryKey = "reviews"; -export const useFetchReviews = (): IReviewFetchState => { - const { data, isLoading, error, refetch } = useQuery( - [reviewsQueryKey], - async () => (await getReviews()).data, - { - onError: (error) => console.log("error, ", error), - } - ); +export const useFetchReviews = () => { + const { data, isLoading, error } = useQuery({ + queryKey: [reviewsQueryKey], + queryFn: () => getReviews(), + onError: (error) => console.log("error, ", error), + }); return { reviews: data || [], isFetching: isLoading, fetchError: error, - refetch, }; }; +export const useCreateReviewMutation = ( + onSuccess?: (name: string) => void, + onError?: (err: AxiosError) => void +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (review: New) => createReview(review), + onSuccess: (res) => { + queryClient.invalidateQueries([ + reviewsByItemIdQueryKey, + res?.application?.id, + ]); + onSuccess && onSuccess(res?.application?.name || ""); + }, + onError: (error) => { + onError && onError(error as AxiosError); + }, + }); +}; + +export const useUpdateReviewMutation = ( + onSuccess?: (name: string) => void, + onError?: (err: AxiosError) => void +) => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (review: Review) => updateReview(review), + onSuccess: (_, args) => { + queryClient.invalidateQueries([ + reviewsByItemIdQueryKey, + _?.application?.id, + ]); + onSuccess && onSuccess(args?.application?.name || ""); + }, + onError: onError, + }); +}; + export interface IReviewMutation { id: number; name: string; @@ -49,3 +88,40 @@ export const useDeleteReviewMutation = ( onError: onError, }); }; + +export function useGetReviewByAppId(applicationId: string | number) { + const { + application, + isFetching: isApplicationFetching, + fetchError: applicationError, + } = useFetchApplicationByID(applicationId || ""); + + let review = null; + let isLoading = false; + let isError = false; + + const appReviewId = application?.review?.id; + + const reviewQuery = useQuery( + ["review", application?.review?.id], + () => (appReviewId ? getReviewById(appReviewId) : null), + { + enabled: !!appReviewId, + } + ); + + if (appReviewId) { + review = reviewQuery.data; + isLoading = reviewQuery.isLoading; + isError = reviewQuery.isError; + } + + return { + application, + review, + isLoading, + isError, + isFetching: isApplicationFetching || isLoading, + fetchError: applicationError || (review ? reviewQuery.error : null), + }; +} diff --git a/client/src/mocks/stub-new-work/archetypes.ts b/client/src/mocks/stub-new-work/archetypes.ts index 14559f4514..e72645e391 100644 --- a/client/src/mocks/stub-new-work/archetypes.ts +++ b/client/src/mocks/stub-new-work/archetypes.ts @@ -141,7 +141,7 @@ const data: Record = { description: "Wayne does the bare minimum", comments: "This one needs coffee", criteriaTags: [tagData["1"]], - archetypeTags: [tagData["81"]], + tags: [tagData["81"]], assessmentTags: [], stakeholders: [], stakeholderGroups: [], @@ -153,7 +153,7 @@ const data: Record = { description: "Garth has some extra tags", comments: "This one needs tea", criteriaTags: [tagData["2"]], - archetypeTags: [tagData["81"], tagData["82"]], + tags: [tagData["81"], tagData["82"]], assessmentTags: [], stakeholders: [], stakeholderGroups: [], @@ -165,10 +165,12 @@ const data: Record = { description: "Cassandra is the most complex", comments: "This one needs cakes", criteriaTags: [tagData["3"]], - archetypeTags: [tagData["81"], tagData["82"], tagData["83"]], + tags: [tagData["81"], tagData["82"], tagData["83"]], assessmentTags: [], stakeholders: [], stakeholderGroups: [], + // assessments: [{ id: 1, name: "test" }], + assessments: [], }, }; diff --git a/client/src/mocks/stub-new-work/assessments.ts b/client/src/mocks/stub-new-work/assessments.ts index 0d74654e91..fe95cd3e5a 100644 --- a/client/src/mocks/stub-new-work/assessments.ts +++ b/client/src/mocks/stub-new-work/assessments.ts @@ -20,7 +20,7 @@ const mockAssessmentArray: Assessment[] = [ name: "test", questionnaire: { id: 1, name: "Sample Questionnaire" }, description: "Sample assessment description", - risk: "AMBER", + risk: "yellow", sections: [ { name: "Application technologies 1", @@ -203,6 +203,8 @@ const mockAssessmentArray: Assessment[] = [ yellow: 4, }, application: { id: 1, name: "App 1" }, + stakeholderGroups: [], + stakeholders: [], }, ]; @@ -243,7 +245,8 @@ export const handlers = [ return res(ctx.status(404), ctx.json({ error: "Assessment not found" })); } }), - rest.post(AppRest.ASSESSMENTS, async (req, res, ctx) => { + //TODO Finish updating mocks + rest.post(`${AppRest.ARCHETYPES}/`, async (req, res, ctx) => { console.log("req need to find questionnaire id", req); const initialAssessment: InitialAssessment = await req.json(); @@ -255,7 +258,7 @@ export const handlers = [ status: "started", name: "test", description: "Sample assessment description", - risk: "AMBER", + risk: "yellow", sections: [], riskMessages: { green: "Low risk", @@ -270,6 +273,8 @@ export const handlers = [ }, application: initialAssessment.application, questionnaire: initialAssessment.questionnaire, + stakeholderGroups: [], + stakeholders: [], }; mockAssessmentArray.push(newAssessment); diff --git a/client/src/mocks/stub-new-work/index.ts b/client/src/mocks/stub-new-work/index.ts index d1c74db755..f946cb9bfd 100644 --- a/client/src/mocks/stub-new-work/index.ts +++ b/client/src/mocks/stub-new-work/index.ts @@ -1,10 +1,8 @@ import { type RestHandler } from "msw"; -import archetypes from "./archetypes"; - export default [ // ...questionnaires, // ...assessments, // ...applications, - ...archetypes, + // ...archetypes, ] as RestHandler[]; diff --git a/output.gz b/output.gz new file mode 100644 index 0000000000..83b4c69e69 --- /dev/null +++ b/output.gz @@ -0,0 +1,561 @@ +--- +id: 1 +createuser: admin.noauth +createtime: 2023-09-12T21:14:56.924175129Z +effort: 17 + +issues: +- id: 1 + createtime: 2023-09-12T21:14:56.932551879Z + ruleset: discovery-rules + rule: hardcoded-ip-address + name: "" + description: |- + Hardcoded IP Address + When migrating environments, hard-coded IP addresses may need to be modified or eliminated. + category: mandatory + effort: 1 + incidents: + - id: 1 + createtime: 2023-09-12T21:14:56.932731338Z + file: /addon/source/example-applications/example-1/src/main/resources/persistence.properties + line: 0 + message: When migrating environments, hard-coded IP addresses may need to be modified + or eliminated. + codesnip: |2 + 1 jdbc.driverClassName=oracle.jdbc.driver.OracleDriver + 2 jdbc.url=jdbc:oracle:thin:@169.60.225.216:1521/XEPDB1 + 3 jdbc.user=customers + 4 jdbc.password=customers + 5 hibernate.hbm2ddl.auto=create-drop + 6 hibernate.dialect=org.hibernate.dialect.OracleDialect + facts: + matchingText: 169.60.225.216 + - id: 2 + createtime: 2023-09-12T21:14:56.932731338Z + file: /addon/source/example-applications/example-1/target/classes/persistence.properties + line: 0 + message: When migrating environments, hard-coded IP addresses may need to be modified + or eliminated. + codesnip: |2 + 1 jdbc.driverClassName=oracle.jdbc.driver.OracleDriver + 2 jdbc.url=jdbc:oracle:thin:@169.60.225.216:1521/XEPDB1 + 3 jdbc.user=customers + 4 jdbc.password=customers + 5 hibernate.hbm2ddl.auto=create-drop + 6 hibernate.dialect=org.hibernate.dialect.OracleDialect + facts: + matchingText: 169.60.225.216 + labels: + - konveyor.io/target=cloud-readiness + - discovery +- id: 2 + createtime: 2023-09-12T21:14:56.933328546Z + ruleset: eap7/weblogic + rule: hsearch-00116 + name: "" + description: |- + Hibernate Search 5 - Changes in indexing numeric and date values + Numbers and dates are now indexed as numeric fields by default. Properties of type int, long, float, double, and their. corresponding wrapper classes are no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate numeric. encoding. The id fields are an exception to this rule. Even when they are represented by a numeric type, they are still indexed as. a string keyword by default. The use of `@NumericField` is now obsolete unless you want to specify a custom precision for the numeric. encoding. You can keep the old string-based index format by explicitly specifying a string encoding field bridge. In the case of. integers, this is the `org.hibernate.search.bridge.builtin.IntegerBridge`. Check the `org.hibernate.search.bridge.builtin` package for. other publicly available field bridges.. Date and Calendar are no longer indexed as strings. Instead, instances are encoded as long values representing the number. of milliseconds since January 1, 1970, 00:00:00 GMT. You can switch the indexing format by using the new EncodingType enum. For example:. ```java. @DateBridge(encoding=EncodingType.STRING). @CalendarBridge(encoding=EncodingType.STRING). ```. The encoding change for numbers and dates is important and can have a big impact on application behavior. If you have. a query that targets a field that was previously string-encoded, but is now encoded numerically, you must update the query. Numeric. fields must be searched with a NumericRangeQuery. You must also make sure that all fields targeted by faceting are string encoded.. If you use the Search query DSL, the correct query should be created automatically for you. + category: optional + effort: 1 + incidents: + - id: 3 + createtime: 2023-09-12T21:14:56.933410129Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/controller/CustomerController.java + line: 0 + message: Numbers and dates are now indexed as numeric fields by default. Properties + of type int, long, float, double, and their. corresponding wrapper classes are + no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate + numeric. encoding. The id fields are an exception to this rule. Even when they + are represented by a numeric type, they are still indexed as. a string keyword + by default. The use of `@NumericField` is now obsolete unless you want to specify + a custom precision for the numeric. encoding. You can keep the old string-based + index format by explicitly specifying a string encoding field bridge. In the + case of. integers, this is the `org.hibernate.search.bridge.builtin.IntegerBridge`. + Check the `org.hibernate.search.bridge.builtin` package for. other publicly + available field bridges.. Date and Calendar are no longer indexed as strings. + Instead, instances are encoded as long values representing the number. of milliseconds + since January 1, 1970, 00:00:00 GMT. You can switch the indexing format by using + the new EncodingType enum. For example:. ```java. @DateBridge(encoding=EncodingType.STRING). + @CalendarBridge(encoding=EncodingType.STRING). ```. The encoding change for + numbers and dates is important and can have a big impact on application behavior. + If you have. a query that targets a field that was previously string-encoded, + but is now encoded numerically, you must update the query. Numeric. fields must + be searched with a NumericRangeQuery. You must also make sure that all fields + targeted by faceting are string encoded.. If you use the Search query DSL, the + correct query should be created automatically for you. + codesnip: "16 @RestController\n17 @RequestMapping(\"/customers\")\n18 public + class CustomerController {\n19 \t\n20 \t@Autowired\n21 \tprivate CustomerService + customerService;\n22 \t\n23 \tprivate static Logger logger = Logger.getLogger( + CustomerController.class.getName() );\n24 \t\n25 \t@GetMapping(value = \"/{id}\", + produces = MediaType.APPLICATION_JSON_VALUE)\n26 public Customer getById(@PathVariable(\"id\") + Long id) {\n27 \t\tCustomer c = customerService.findById(id);\n28 \t\tif (c + == null) {\n29 \t\t\tthrow new ResourceNotFoundException(\"Requested order + doesn't exist\");\n30 \t\t}\n31 \t\tlogger.debug(\"Returning element: \" + + c);\n32 \t\treturn c;\n33 \t}\n34 \t\n35 \t@RequestMapping\n36 \tpublic + Page findAll(Pageable pageable){" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/controller/CustomerController.java + kind: Method + name: getById + - id: 4 + createtime: 2023-09-12T21:14:56.933410129Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + line: 0 + message: Numbers and dates are now indexed as numeric fields by default. Properties + of type int, long, float, double, and their. corresponding wrapper classes are + no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate + numeric. encoding. The id fields are an exception to this rule. Even when they + are represented by a numeric type, they are still indexed as. a string keyword + by default. The use of `@NumericField` is now obsolete unless you want to specify + a custom precision for the numeric. encoding. You can keep the old string-based + index format by explicitly specifying a string encoding field bridge. In the + case of. integers, this is the `org.hibernate.search.bridge.builtin.IntegerBridge`. + Check the `org.hibernate.search.bridge.builtin` package for. other publicly + available field bridges.. Date and Calendar are no longer indexed as strings. + Instead, instances are encoded as long values representing the number. of milliseconds + since January 1, 1970, 00:00:00 GMT. You can switch the indexing format by using + the new EncodingType enum. For example:. ```java. @DateBridge(encoding=EncodingType.STRING). + @CalendarBridge(encoding=EncodingType.STRING). ```. The encoding change for + numbers and dates is important and can have a big impact on application behavior. + If you have. a query that targets a field that was previously string-encoded, + but is now encoded numerically, you must update the query. Numeric. fields must + be searched with a NumericRangeQuery. You must also make sure that all fields + targeted by faceting are string encoded.. If you use the Search query DSL, the + correct query should be created automatically for you. + codesnip: "11 @Entity\n12 @Table(name = \"customers\")\n13 public class Customer + {\n14 \t@Id\n15 @SequenceGenerator(\n16 name = \"customersSequence\",\n17 + \ sequenceName = \"customers_id_seq\",\n18 allocationSize + = 1,\n19 initialValue = 6)\n20 @GeneratedValue(strategy = + GenerationType.SEQUENCE, generator = \"customersSequence\")\n21 \tprivate Long + id;\n22 \t\n23 \t@Column(length = 20)\n24 \tprivate String username;\n25 + \ \t\n26 \t@Column(length = 20)\n27 \tprivate String name;\n28 \t\n29 \t@Column(length + = 40)\n30 \tprivate String surname;\n31 \t" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + kind: Field + name: id + - id: 5 + createtime: 2023-09-12T21:14:56.933410129Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + line: 0 + message: Numbers and dates are now indexed as numeric fields by default. Properties + of type int, long, float, double, and their. corresponding wrapper classes are + no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate + numeric. encoding. The id fields are an exception to this rule. Even when they + are represented by a numeric type, they are still indexed as. a string keyword + by default. The use of `@NumericField` is now obsolete unless you want to specify + a custom precision for the numeric. encoding. You can keep the old string-based + index format by explicitly specifying a string encoding field bridge. In the + case of. integers, this is the `org.hibernate.search.bridge.builtin.IntegerBridge`. + Check the `org.hibernate.search.bridge.builtin` package for. other publicly + available field bridges.. Date and Calendar are no longer indexed as strings. + Instead, instances are encoded as long values representing the number. of milliseconds + since January 1, 1970, 00:00:00 GMT. You can switch the indexing format by using + the new EncodingType enum. For example:. ```java. @DateBridge(encoding=EncodingType.STRING). + @CalendarBridge(encoding=EncodingType.STRING). ```. The encoding change for + numbers and dates is important and can have a big impact on application behavior. + If you have. a query that targets a field that was previously string-encoded, + but is now encoded numerically, you must update the query. Numeric. fields must + be searched with a NumericRangeQuery. You must also make sure that all fields + targeted by faceting are string encoded.. If you use the Search query DSL, the + correct query should be created automatically for you. + codesnip: "34 \t\n35 \t@Column(name = \"zipcode\", length = 10)\n36 \tprivate + String zipCode;\n37 \t\n38 \t@Column(length = 40)\n39 \tprivate String city;\n40 + \ \t\n41 \t@Column(length = 40)\n42 \tprivate String country;\n43 \t\n44 + \ \tpublic Long getId() {\n45 \t\treturn id;\n46 \t}\n47 \tpublic void setId(Long + id) {\n48 \t\tthis.id = id;\n49 \t}\n50 \tpublic String getUsername() {\n51 + \ \t\treturn username;\n52 \t}\n53 \tpublic void setUsername(String username) + {\n54 \t\tthis.username = username;" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + kind: Method + name: getId + - id: 6 + createtime: 2023-09-12T21:14:56.933410129Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + line: 0 + message: Numbers and dates are now indexed as numeric fields by default. Properties + of type int, long, float, double, and their. corresponding wrapper classes are + no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate + numeric. encoding. The id fields are an exception to this rule. Even when they + are represented by a numeric type, they are still indexed as. a string keyword + by default. The use of `@NumericField` is now obsolete unless you want to specify + a custom precision for the numeric. encoding. You can keep the old string-based + index format by explicitly specifying a string encoding field bridge. In the + case of. integers, this is the `org.hibernate.search.bridge.builtin.IntegerBridge`. + Check the `org.hibernate.search.bridge.builtin` package for. other publicly + available field bridges.. Date and Calendar are no longer indexed as strings. + Instead, instances are encoded as long values representing the number. of milliseconds + since January 1, 1970, 00:00:00 GMT. You can switch the indexing format by using + the new EncodingType enum. For example:. ```java. @DateBridge(encoding=EncodingType.STRING). + @CalendarBridge(encoding=EncodingType.STRING). ```. The encoding change for + numbers and dates is important and can have a big impact on application behavior. + If you have. a query that targets a field that was previously string-encoded, + but is now encoded numerically, you must update the query. Numeric. fields must + be searched with a NumericRangeQuery. You must also make sure that all fields + targeted by faceting are string encoded.. If you use the Search query DSL, the + correct query should be created automatically for you. + codesnip: "37 \t\n38 \t@Column(length = 40)\n39 \tprivate String city;\n40 + \ \t\n41 \t@Column(length = 40)\n42 \tprivate String country;\n43 \t\n44 + \ \tpublic Long getId() {\n45 \t\treturn id;\n46 \t}\n47 \tpublic void setId(Long + id) {\n48 \t\tthis.id = id;\n49 \t}\n50 \tpublic String getUsername() {\n51 + \ \t\treturn username;\n52 \t}\n53 \tpublic void setUsername(String username) + {\n54 \t\tthis.username = username;\n55 \t}\n56 \tpublic String getName() + {\n57 \t\treturn name;" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + kind: Method + name: setId + - id: 7 + createtime: 2023-09-12T21:14:56.933410129Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/repository/CustomerRepository.java + line: 0 + message: Numbers and dates are now indexed as numeric fields by default. Properties + of type int, long, float, double, and their. corresponding wrapper classes are + no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate + numeric. encoding. The id fields are an exception to this rule. Even when they + are represented by a numeric type, they are still indexed as. a string keyword + by default. The use of `@NumericField` is now obsolete unless you want to specify + a custom precision for the numeric. encoding. You can keep the old string-based + index format by explicitly specifying a string encoding field bridge. In the + case of. integers, this is the `org.hibernate.search.bridge.builtin.IntegerBridge`. + Check the `org.hibernate.search.bridge.builtin` package for. other publicly + available field bridges.. Date and Calendar are no longer indexed as strings. + Instead, instances are encoded as long values representing the number. of milliseconds + since January 1, 1970, 00:00:00 GMT. You can switch the indexing format by using + the new EncodingType enum. For example:. ```java. @DateBridge(encoding=EncodingType.STRING). + @CalendarBridge(encoding=EncodingType.STRING). ```. The encoding change for + numbers and dates is important and can have a big impact on application behavior. + If you have. a query that targets a field that was previously string-encoded, + but is now encoded numerically, you must update the query. Numeric. fields must + be searched with a NumericRangeQuery. You must also make sure that all fields + targeted by faceting are string encoded.. If you use the Search query DSL, the + correct query should be created automatically for you. + codesnip: " 1 package io.konveyor.demo.ordermanagement.repository;\n 2 \n 3 + \ import io.konveyor.demo.ordermanagement.model.Customer;\n 4 import org.springframework.data.repository.PagingAndSortingRepository;\n + 5 \n 6 public interface CustomerRepository extends PagingAndSortingRepository {\n 7 \n 8 }\n" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/repository/CustomerRepository.java + kind: Interface + name: CustomerRepository + - id: 8 + createtime: 2023-09-12T21:14:56.933410129Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/service/CustomerService.java + line: 0 + message: Numbers and dates are now indexed as numeric fields by default. Properties + of type int, long, float, double, and their. corresponding wrapper classes are + no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate + numeric. encoding. The id fields are an exception to this rule. Even when they + are represented by a numeric type, they are still indexed as. a string keyword + by default. The use of `@NumericField` is now obsolete unless you want to specify + a custom precision for the numeric. encoding. You can keep the old string-based + index format by explicitly specifying a string encoding field bridge. In the + case of. integers, this is the `org.hibernate.search.bridge.builtin.IntegerBridge`. + Check the `org.hibernate.search.bridge.builtin` package for. other publicly + available field bridges.. Date and Calendar are no longer indexed as strings. + Instead, instances are encoded as long values representing the number. of milliseconds + since January 1, 1970, 00:00:00 GMT. You can switch the indexing format by using + the new EncodingType enum. For example:. ```java. @DateBridge(encoding=EncodingType.STRING). + @CalendarBridge(encoding=EncodingType.STRING). ```. The encoding change for + numbers and dates is important and can have a big impact on application behavior. + If you have. a query that targets a field that was previously string-encoded, + but is now encoded numerically, you must update the query. Numeric. fields must + be searched with a NumericRangeQuery. You must also make sure that all fields + targeted by faceting are string encoded.. If you use the Search query DSL, the + correct query should be created automatically for you. + codesnip: "12 \n13 @Service\n14 @Transactional\n15 public class CustomerService + implements ICustomerService{\n16 \t\n17 \t@Autowired\n18 \tprivate CustomerRepository + repository;\n19 \t\n20 \tprivate static Logger logger = Logger.getLogger( + CustomerService.class.getName() );\n21 \t\n22 \tpublic Customer findById(Long + id) {\n23 \t\tlogger.debug(\"Entering CustomerService.findById()\");\n24 \t\tCustomer + c = repository.findById(id).orElse(null);\n25 \t\tlogger.debug(\"Returning + element: \" + c);\n26 \t\treturn c;\n27 \t}\n28 \t\n29 \tpublic PagefindAll(Pageable + pageable) {\n30 \t\tlogger.debug(\"Entering CustomerService.findAll()\");\n31 + \ \t\tPage p = repository.findAll(pageable);\n32 \t\tlogger.debug(\"Returning + element: \" + p);" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/service/CustomerService.java + kind: Method + name: findById + - id: 9 + createtime: 2023-09-12T21:14:56.933410129Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/service/ICustomerService.java + line: 0 + message: Numbers and dates are now indexed as numeric fields by default. Properties + of type int, long, float, double, and their. corresponding wrapper classes are + no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate + numeric. encoding. The id fields are an exception to this rule. Even when they + are represented by a numeric type, they are still indexed as. a string keyword + by default. The use of `@NumericField` is now obsolete unless you want to specify + a custom precision for the numeric. encoding. You can keep the old string-based + index format by explicitly specifying a string encoding field bridge. In the + case of. integers, this is the `org.hibernate.search.bridge.builtin.IntegerBridge`. + Check the `org.hibernate.search.bridge.builtin` package for. other publicly + available field bridges.. Date and Calendar are no longer indexed as strings. + Instead, instances are encoded as long values representing the number. of milliseconds + since January 1, 1970, 00:00:00 GMT. You can switch the indexing format by using + the new EncodingType enum. For example:. ```java. @DateBridge(encoding=EncodingType.STRING). + @CalendarBridge(encoding=EncodingType.STRING). ```. The encoding change for + numbers and dates is important and can have a big impact on application behavior. + If you have. a query that targets a field that was previously string-encoded, + but is now encoded numerically, you must update the query. Numeric. fields must + be searched with a NumericRangeQuery. You must also make sure that all fields + targeted by faceting are string encoded.. If you use the Search query DSL, the + correct query should be created automatically for you. + codesnip: " 1 package io.konveyor.demo.ordermanagement.service;\n 2 \n 3 import + io.konveyor.demo.ordermanagement.model.Customer;\n 4 import org.springframework.data.domain.Page;\n + 5 import org.springframework.data.domain.Pageable;\n 6 \n 7 public interface + ICustomerService {\n 8 public Customer findById(Long id); \n 9 \t\n10 + \ \tpublic PagefindAll(Pageable pageable);\n11 \n12 }\n" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/service/ICustomerService.java + kind: Method + name: findById + links: + - url: https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.0/html-single/migration_guide/#migrate_hibernate_search_number_and_date_index_formatting_changes + title: Number and Date Index Formatting Changes in Hibernate Search 5.x + - url: http://hibernate.org/search/documentation/migrate/5.0/#number-and-date-index-format + title: Number and date index format + - url: http://docs.jboss.org/hibernate/search/5.5/api/org/hibernate/search/bridge/builtin/package-summary.html + title: Javadoc API for org.hibernate.search.bridge.builtin package + - url: http://docs.jboss.org/hibernate/search/5.5/api/org/hibernate/search/bridge/builtin/IntegerBridge.html + title: Javadoc API for IntegerBridge + labels: + - konveyor.io/source=hibernate-search4 + - konveyor.io/source=eap6 + - konveyor.io/target=hibernate-search5 + - konveyor.io/target=eap7 + - hibernate-search + - hibernate +- id: 3 + createtime: 2023-09-12T21:14:56.933920338Z + ruleset: eap7/weblogic + rule: hsearch-00119 + name: "" + description: |- + Hibernate Search 5 - Changes in indexing numeric values + Numbers and dates now indexed as numeric fields by default.. Properties of type `Date`, `Calendar` as well as `int`, `long`, `float`, `double` and their corresponding wrappers, are no longer indexed as strings. Instead, they are now indexed using Lucene’s appropriate numeric encoding.. The `id` fields are an exception to this rule: even when these are represented by a numeric type, they will still be indexed as a string keyword by default.. + category: optional + effort: 1 + incidents: + - id: 10 + createtime: 2023-09-12T21:14:56.933993046Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/controller/CustomerController.java + line: 0 + message: 'Numbers and dates now indexed as numeric fields by default.. Properties + of type `Date`, `Calendar` as well as `int`, `long`, `float`, `double` and their + corresponding wrappers, are no longer indexed as strings. Instead, they are + now indexed using Lucene’s appropriate numeric encoding.. The `id` fields are + an exception to this rule: even when these are represented by a numeric type, + they will still be indexed as a string keyword by default..' + codesnip: "16 @RestController\n17 @RequestMapping(\"/customers\")\n18 public + class CustomerController {\n19 \t\n20 \t@Autowired\n21 \tprivate CustomerService + customerService;\n22 \t\n23 \tprivate static Logger logger = Logger.getLogger( + CustomerController.class.getName() );\n24 \t\n25 \t@GetMapping(value = \"/{id}\", + produces = MediaType.APPLICATION_JSON_VALUE)\n26 public Customer getById(@PathVariable(\"id\") + Long id) {\n27 \t\tCustomer c = customerService.findById(id);\n28 \t\tif (c + == null) {\n29 \t\t\tthrow new ResourceNotFoundException(\"Requested order + doesn't exist\");\n30 \t\t}\n31 \t\tlogger.debug(\"Returning element: \" + + c);\n32 \t\treturn c;\n33 \t}\n34 \t\n35 \t@RequestMapping\n36 \tpublic + Page findAll(Pageable pageable){" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/controller/CustomerController.java + kind: Method + name: getById + - id: 11 + createtime: 2023-09-12T21:14:56.933993046Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + line: 0 + message: 'Numbers and dates now indexed as numeric fields by default.. Properties + of type `Date`, `Calendar` as well as `int`, `long`, `float`, `double` and their + corresponding wrappers, are no longer indexed as strings. Instead, they are + now indexed using Lucene’s appropriate numeric encoding.. The `id` fields are + an exception to this rule: even when these are represented by a numeric type, + they will still be indexed as a string keyword by default..' + codesnip: "11 @Entity\n12 @Table(name = \"customers\")\n13 public class Customer + {\n14 \t@Id\n15 @SequenceGenerator(\n16 name = \"customersSequence\",\n17 + \ sequenceName = \"customers_id_seq\",\n18 allocationSize + = 1,\n19 initialValue = 6)\n20 @GeneratedValue(strategy = + GenerationType.SEQUENCE, generator = \"customersSequence\")\n21 \tprivate Long + id;\n22 \t\n23 \t@Column(length = 20)\n24 \tprivate String username;\n25 + \ \t\n26 \t@Column(length = 20)\n27 \tprivate String name;\n28 \t\n29 \t@Column(length + = 40)\n30 \tprivate String surname;\n31 \t" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + kind: Field + name: id + - id: 12 + createtime: 2023-09-12T21:14:56.933993046Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + line: 0 + message: 'Numbers and dates now indexed as numeric fields by default.. Properties + of type `Date`, `Calendar` as well as `int`, `long`, `float`, `double` and their + corresponding wrappers, are no longer indexed as strings. Instead, they are + now indexed using Lucene’s appropriate numeric encoding.. The `id` fields are + an exception to this rule: even when these are represented by a numeric type, + they will still be indexed as a string keyword by default..' + codesnip: "34 \t\n35 \t@Column(name = \"zipcode\", length = 10)\n36 \tprivate + String zipCode;\n37 \t\n38 \t@Column(length = 40)\n39 \tprivate String city;\n40 + \ \t\n41 \t@Column(length = 40)\n42 \tprivate String country;\n43 \t\n44 + \ \tpublic Long getId() {\n45 \t\treturn id;\n46 \t}\n47 \tpublic void setId(Long + id) {\n48 \t\tthis.id = id;\n49 \t}\n50 \tpublic String getUsername() {\n51 + \ \t\treturn username;\n52 \t}\n53 \tpublic void setUsername(String username) + {\n54 \t\tthis.username = username;" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + kind: Method + name: getId + - id: 13 + createtime: 2023-09-12T21:14:56.933993046Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + line: 0 + message: 'Numbers and dates now indexed as numeric fields by default.. Properties + of type `Date`, `Calendar` as well as `int`, `long`, `float`, `double` and their + corresponding wrappers, are no longer indexed as strings. Instead, they are + now indexed using Lucene’s appropriate numeric encoding.. The `id` fields are + an exception to this rule: even when these are represented by a numeric type, + they will still be indexed as a string keyword by default..' + codesnip: "37 \t\n38 \t@Column(length = 40)\n39 \tprivate String city;\n40 + \ \t\n41 \t@Column(length = 40)\n42 \tprivate String country;\n43 \t\n44 + \ \tpublic Long getId() {\n45 \t\treturn id;\n46 \t}\n47 \tpublic void setId(Long + id) {\n48 \t\tthis.id = id;\n49 \t}\n50 \tpublic String getUsername() {\n51 + \ \t\treturn username;\n52 \t}\n53 \tpublic void setUsername(String username) + {\n54 \t\tthis.username = username;\n55 \t}\n56 \tpublic String getName() + {\n57 \t\treturn name;" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + kind: Method + name: setId + - id: 14 + createtime: 2023-09-12T21:14:56.933993046Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/repository/CustomerRepository.java + line: 0 + message: 'Numbers and dates now indexed as numeric fields by default.. Properties + of type `Date`, `Calendar` as well as `int`, `long`, `float`, `double` and their + corresponding wrappers, are no longer indexed as strings. Instead, they are + now indexed using Lucene’s appropriate numeric encoding.. The `id` fields are + an exception to this rule: even when these are represented by a numeric type, + they will still be indexed as a string keyword by default..' + codesnip: " 1 package io.konveyor.demo.ordermanagement.repository;\n 2 \n 3 + \ import io.konveyor.demo.ordermanagement.model.Customer;\n 4 import org.springframework.data.repository.PagingAndSortingRepository;\n + 5 \n 6 public interface CustomerRepository extends PagingAndSortingRepository {\n 7 \n 8 }\n" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/repository/CustomerRepository.java + kind: Interface + name: CustomerRepository + - id: 15 + createtime: 2023-09-12T21:14:56.933993046Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/service/CustomerService.java + line: 0 + message: 'Numbers and dates now indexed as numeric fields by default.. Properties + of type `Date`, `Calendar` as well as `int`, `long`, `float`, `double` and their + corresponding wrappers, are no longer indexed as strings. Instead, they are + now indexed using Lucene’s appropriate numeric encoding.. The `id` fields are + an exception to this rule: even when these are represented by a numeric type, + they will still be indexed as a string keyword by default..' + codesnip: "12 \n13 @Service\n14 @Transactional\n15 public class CustomerService + implements ICustomerService{\n16 \t\n17 \t@Autowired\n18 \tprivate CustomerRepository + repository;\n19 \t\n20 \tprivate static Logger logger = Logger.getLogger( + CustomerService.class.getName() );\n21 \t\n22 \tpublic Customer findById(Long + id) {\n23 \t\tlogger.debug(\"Entering CustomerService.findById()\");\n24 \t\tCustomer + c = repository.findById(id).orElse(null);\n25 \t\tlogger.debug(\"Returning + element: \" + c);\n26 \t\treturn c;\n27 \t}\n28 \t\n29 \tpublic PagefindAll(Pageable + pageable) {\n30 \t\tlogger.debug(\"Entering CustomerService.findAll()\");\n31 + \ \t\tPage p = repository.findAll(pageable);\n32 \t\tlogger.debug(\"Returning + element: \" + p);" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/service/CustomerService.java + kind: Method + name: findById + - id: 16 + createtime: 2023-09-12T21:14:56.933993046Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/service/ICustomerService.java + line: 0 + message: 'Numbers and dates now indexed as numeric fields by default.. Properties + of type `Date`, `Calendar` as well as `int`, `long`, `float`, `double` and their + corresponding wrappers, are no longer indexed as strings. Instead, they are + now indexed using Lucene’s appropriate numeric encoding.. The `id` fields are + an exception to this rule: even when these are represented by a numeric type, + they will still be indexed as a string keyword by default..' + codesnip: " 1 package io.konveyor.demo.ordermanagement.service;\n 2 \n 3 import + io.konveyor.demo.ordermanagement.model.Customer;\n 4 import org.springframework.data.domain.Page;\n + 5 import org.springframework.data.domain.Pageable;\n 6 \n 7 public interface + ICustomerService {\n 8 public Customer findById(Long id); \n 9 \t\n10 + \ \tpublic PagefindAll(Pageable pageable);\n11 \n12 }\n" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/service/ICustomerService.java + kind: Method + name: findById + links: + - url: https://access.redhat.com/documentation/en-us/red_hat_jboss_enterprise_application_platform/7.0/html-single/migration_guide/#migrate_miscellaneous_hibernate_search_changes + title: Miscellaneous Changes in Hibernate Search 5.x + - url: http://hibernate.org/search/documentation/migrate/5.5/#number-and-date-index-format + title: Numeric and Date index format + labels: + - konveyor.io/source=hibernate-search4 + - konveyor.io/source=eap6 + - konveyor.io/target=hibernate-search5 + - konveyor.io/target=eap7 + - hibernate-search + - hibernate +- id: 4 + createtime: 2023-09-12T21:14:56.934252671Z + ruleset: eap7/weblogic + rule: hibernate4-00039 + name: "" + description: |- + Hibernate 5 - Oracle12cDialect maps byte[] and Byte[] to BLOB + Previous versions of Hibernate have mapped `byte[]` and `Byte[]` to Oracle’s `LONG RAW` data type (via the JDBC `LONGVARBINARY` type). Oracle have deprecated the `LONG RAW` data type for many releases - possibly as far back as 8i.. Therefore it was decided to start having Hibernate map `byte[]` and `Byte[]` to `BLOB` for Oracle.. However, in the interest of backwards compatibility and not breaking existing applications it was also decided to limit this change to just the `Oracle12cDialect`. So starting in 5.1 applications using `Oracle12cDialect` and implicitly mapping `byte[]` and `Byte[]` values will start seeing those handled as `BLOB` data rather than `LONG RAW` data.. For existing applications that want to continue to use `Oracle12cDialect` and still continue to implicitly map `byte[]` and `Byte[]` attributes to `LONG RAW`, there is a new configuration setting you can use to enable that: `hibernate.dialect.oracle.prefer_longvarbinary`, which is `false `by default (map to `BLOB`). + category: mandatory + effort: 1 + incidents: + - id: 17 + createtime: 2023-09-12T21:14:56.934310588Z + file: /addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + line: 0 + message: 'Previous versions of Hibernate have mapped `byte[]` and `Byte[]` to + Oracle’s `LONG RAW` data type (via the JDBC `LONGVARBINARY` type). Oracle have + deprecated the `LONG RAW` data type for many releases - possibly as far back + as 8i.. Therefore it was decided to start having Hibernate map `byte[]` and + `Byte[]` to `BLOB` for Oracle.. However, in the interest of backwards compatibility + and not breaking existing applications it was also decided to limit this change + to just the `Oracle12cDialect`. So starting in 5.1 applications using `Oracle12cDialect` + and implicitly mapping `byte[]` and `Byte[]` values will start seeing those + handled as `BLOB` data rather than `LONG RAW` data.. For existing applications + that want to continue to use `Oracle12cDialect` and still continue to implicitly + map `byte[]` and `Byte[]` attributes to `LONG RAW`, there is a new configuration + setting you can use to enable that: `hibernate.dialect.oracle.prefer_longvarbinary`, + which is `false `by default (map to `BLOB`).' + codesnip: " 1 package io.konveyor.demo.ordermanagement.model;\n 2 \n 3 import + javax.persistence.Column;\n 4 import javax.persistence.Entity;\n 5 import + javax.persistence.GeneratedValue;\n 6 import javax.persistence.GenerationType;\n + 7 import javax.persistence.Id;\n 8 import javax.persistence.SequenceGenerator;\n + 9 import javax.persistence.Table;\n10 \n11 @Entity\n12 @Table(name = \"customers\")\n13 + \ public class Customer {\n14 \t@Id\n15 @SequenceGenerator(\n16 name + = \"customersSequence\",\n17 sequenceName = \"customers_id_seq\",\n18 + \ allocationSize = 1,\n19 initialValue = 6)\n20 @GeneratedValue(strategy + = GenerationType.SEQUENCE, generator = \"customersSequence\")\n21 \tprivate + Long id;" + facts: + file: file:///addon/source/example-applications/example-1/src/main/java/io/konveyor/demo/ordermanagement/model/Customer.java + kind: Class + name: Entity + labels: + - konveyor.io/source=hibernate4 + - konveyor.io/source=eap6 + - konveyor.io/target=hibernate5 + - konveyor.io/target=eap7 + - hibernate + - configuration + - Hibernate + +dependencies: +- id: 1 + createtime: 2023-09-12T21:14:56.934497213Z + name: io.konveyor.demo.config-utils + version: 1.0.0 + sha: FE4FE11AAEE77BE10035218537FBF4B2E6EF1D9F
- - - -