diff --git a/locales/en/plugin__gitops-plugin.json b/locales/en/plugin__gitops-plugin.json index ffdbf1a23..126221794 100644 --- a/locales/en/plugin__gitops-plugin.json +++ b/locales/en/plugin__gitops-plugin.json @@ -108,11 +108,11 @@ "Edit ImageUpdater": "Edit ImageUpdater", "Delete ImageUpdater": "Delete ImageUpdater", "Error: Missing required route parameters": "Error: Missing required route parameters", + "True": "True", + "False": "False", "ImageUpdater details": "ImageUpdater details", "Ready": "Ready", "Whether the last reconciliation completed without errors.": "Whether the last reconciliation completed without errors.", - "True": "True", - "False": "False", "Applications Matched": "Applications Matched", "Number of applications matched by this ImageUpdater.": "Number of applications matched by this ImageUpdater.", "Images Managed": "Images Managed", @@ -255,17 +255,22 @@ "No pods": "No pods", "There are no pods associated with the rollout.": "There are no pods associated with the rollout.", "Edit Pod": "Edit Pod", + "Edit Rollout": "Edit Rollout", "Promote": "Promote", "Full Promote": "Full Promote", "Abort": "Abort", "Retry": "Retry", "Restart": "Restart", - "Edit Rollout": "Edit Rollout", + "Rollback": "Rollback", "Age": "Age", "Info": "Info", "Ready containers": "Ready containers", "ready": "ready", "0 Pods": "0 Pods", + "Scaling down in:": "Scaling down in:", + "{{x}} failed with an error.": "{{x}} failed with an error.", + "Rollout Revisions": "Rollout Revisions", + "Close": "Close", "Stable": "Stable", "Active": "Active", "Preview": "Preview", @@ -274,6 +279,7 @@ "Replicas": "Replicas", "The number of desired replicas for the rollout": "The number of desired replicas for the rollout", "The current status of the rollout": "The current status of the rollout", + "There is no rollout status. Check that the Rollout Manager is created and is available.": "There is no rollout status. Check that the Rollout Manager is created and is available.", "Strategy": "Strategy", "Whether the rollout is using a blue-green or canary strategy": "Whether the rollout is using a blue-green or canary strategy", "No Argo Rollouts": "No Argo Rollouts", @@ -284,12 +290,10 @@ "Create Rollout": "Create Rollout", "Pods": "Pods", "Selector": "Selector", - "No labels": "No labels", "Rollout Status": "Rollout Status", "Revisions": "Revisions", "There was an error retrieving the rollout. Check your connection and reload the page.": "There was an error retrieving the rollout. Check your connection and reload the page.", "There was an error retrieving the rollout revisions. Check your connection and reload the page.": "There was an error retrieving the rollout revisions. Check your connection and reload the page.", - "Rollout Revisions": "Rollout Revisions", "Active Service": "Active Service", "The active blue-green service": "The active blue-green service", "Preview Service": "Preview Service", @@ -322,6 +326,7 @@ "There was an error retrieving applicationsets. Check your connection and reload the page.": "There was an error retrieving applicationsets. Check your connection and reload the page.", "ApplicationSets": "ApplicationSets", "Create ApplicationSet": "Create ApplicationSet", + "No labels": "No labels", "Name must be unique within a namespace.": "Name must be unique within a namespace.", "Namespace defines the space within which each name must be unique.": "Namespace defines the space within which each name must be unique.", "Map of string keys and values that can be used to organize and categorize (scope and select) objects.": "Map of string keys and values that can be used to organize and categorize (scope and select) objects.", diff --git a/locales/ja/plugin__gitops-plugin.json b/locales/ja/plugin__gitops-plugin.json index 2430d79c3..6f0e1797c 100644 --- a/locales/ja/plugin__gitops-plugin.json +++ b/locales/ja/plugin__gitops-plugin.json @@ -108,11 +108,11 @@ "Edit ImageUpdater": "Edit ImageUpdater", "Delete ImageUpdater": "Delete ImageUpdater", "Error: Missing required route parameters": "Error: Missing required route parameters", + "True": "True", + "False": "False", "ImageUpdater details": "ImageUpdater details", "Ready": "Ready", "Whether the last reconciliation completed without errors.": "Whether the last reconciliation completed without errors.", - "True": "True", - "False": "False", "Applications Matched": "Applications Matched", "Number of applications matched by this ImageUpdater.": "Number of applications matched by this ImageUpdater.", "Images Managed": "Images Managed", @@ -255,17 +255,22 @@ "No pods": "No pods", "There are no pods associated with the rollout.": "There are no pods associated with the rollout.", "Edit Pod": "Edit Pod", + "Edit Rollout": "Edit Rollout", "Promote": "Promote", "Full Promote": "Full Promote", "Abort": "Abort", "Retry": "Retry", "Restart": "Restart", - "Edit Rollout": "Edit Rollout", + "Rollback": "Rollback", "Age": "Age", "Info": "Info", "Ready containers": "Ready containers", "ready": "ready", "0 Pods": "0 Pods", + "Scaling down in:": "Scaling down in:", + "{{x}} failed with an error.": "{{x}} failed with an error.", + "Rollout Revisions": "Rollout Revisions", + "Close": "Close", "Stable": "Stable", "Active": "Active", "Preview": "Preview", @@ -274,6 +279,7 @@ "Replicas": "Replicas", "The number of desired replicas for the rollout": "The number of desired replicas for the rollout", "The current status of the rollout": "The current status of the rollout", + "There is no rollout status. Check that the Rollout Manager is created and is available.": "There is no rollout status. Check that the Rollout Manager is created and is available.", "Strategy": "Strategy", "Whether the rollout is using a blue-green or canary strategy": "Whether the rollout is using a blue-green or canary strategy", "No Argo Rollouts": "No Argo Rollouts", @@ -284,12 +290,10 @@ "Create Rollout": "Create Rollout", "Pods": "Pods", "Selector": "Selector", - "No labels": "No labels", "Rollout Status": "Rollout Status", "Revisions": "Revisions", "There was an error retrieving the rollout. Check your connection and reload the page.": "There was an error retrieving the rollout. Check your connection and reload the page.", "There was an error retrieving the rollout revisions. Check your connection and reload the page.": "There was an error retrieving the rollout revisions. Check your connection and reload the page.", - "Rollout Revisions": "Rollout Revisions", "Active Service": "Active Service", "The active blue-green service": "The active blue-green service", "Preview Service": "Preview Service", @@ -322,6 +326,7 @@ "There was an error retrieving applicationsets. Check your connection and reload the page.": "There was an error retrieving applicationsets. Check your connection and reload the page.", "ApplicationSets": "ApplicationSets", "Create ApplicationSet": "Create ApplicationSet", + "No labels": "No labels", "Name must be unique within a namespace.": "Name must be unique within a namespace.", "Namespace defines the space within which each name must be unique.": "Namespace defines the space within which each name must be unique.", "Map of string keys and values that can be used to organize and categorize (scope and select) objects.": "Map of string keys and values that can be used to organize and categorize (scope and select) objects.", diff --git a/locales/ko/plugin__gitops-plugin.json b/locales/ko/plugin__gitops-plugin.json index 9b2a17804..5b3df2c04 100644 --- a/locales/ko/plugin__gitops-plugin.json +++ b/locales/ko/plugin__gitops-plugin.json @@ -108,11 +108,11 @@ "Edit ImageUpdater": "Edit ImageUpdater", "Delete ImageUpdater": "Delete ImageUpdater", "Error: Missing required route parameters": "Error: Missing required route parameters", + "True": "True", + "False": "False", "ImageUpdater details": "ImageUpdater details", "Ready": "Ready", "Whether the last reconciliation completed without errors.": "Whether the last reconciliation completed without errors.", - "True": "True", - "False": "False", "Applications Matched": "Applications Matched", "Number of applications matched by this ImageUpdater.": "Number of applications matched by this ImageUpdater.", "Images Managed": "Images Managed", @@ -255,17 +255,22 @@ "No pods": "No pods", "There are no pods associated with the rollout.": "There are no pods associated with the rollout.", "Edit Pod": "Edit Pod", + "Edit Rollout": "Edit Rollout", "Promote": "Promote", "Full Promote": "Full Promote", "Abort": "Abort", "Retry": "Retry", "Restart": "Restart", - "Edit Rollout": "Edit Rollout", + "Rollback": "Rollback", "Age": "Age", "Info": "Info", "Ready containers": "Ready containers", "ready": "ready", "0 Pods": "0 Pods", + "Scaling down in:": "Scaling down in:", + "{{x}} failed with an error.": "{{x}} failed with an error.", + "Rollout Revisions": "Rollout Revisions", + "Close": "Close", "Stable": "Stable", "Active": "Active", "Preview": "Preview", @@ -274,6 +279,7 @@ "Replicas": "Replicas", "The number of desired replicas for the rollout": "The number of desired replicas for the rollout", "The current status of the rollout": "The current status of the rollout", + "There is no rollout status. Check that the Rollout Manager is created and is available.": "There is no rollout status. Check that the Rollout Manager is created and is available.", "Strategy": "Strategy", "Whether the rollout is using a blue-green or canary strategy": "Whether the rollout is using a blue-green or canary strategy", "No Argo Rollouts": "No Argo Rollouts", @@ -284,12 +290,10 @@ "Create Rollout": "Create Rollout", "Pods": "Pods", "Selector": "Selector", - "No labels": "No labels", "Rollout Status": "Rollout Status", "Revisions": "Revisions", "There was an error retrieving the rollout. Check your connection and reload the page.": "There was an error retrieving the rollout. Check your connection and reload the page.", "There was an error retrieving the rollout revisions. Check your connection and reload the page.": "There was an error retrieving the rollout revisions. Check your connection and reload the page.", - "Rollout Revisions": "Rollout Revisions", "Active Service": "Active Service", "The active blue-green service": "The active blue-green service", "Preview Service": "Preview Service", @@ -322,6 +326,7 @@ "There was an error retrieving applicationsets. Check your connection and reload the page.": "There was an error retrieving applicationsets. Check your connection and reload the page.", "ApplicationSets": "ApplicationSets", "Create ApplicationSet": "Create ApplicationSet", + "No labels": "No labels", "Name must be unique within a namespace.": "Name must be unique within a namespace.", "Namespace defines the space within which each name must be unique.": "Namespace defines the space within which each name must be unique.", "Map of string keys and values that can be used to organize and categorize (scope and select) objects.": "Map of string keys and values that can be used to organize and categorize (scope and select) objects.", diff --git a/locales/zh/plugin__gitops-plugin.json b/locales/zh/plugin__gitops-plugin.json index 873866551..b60c9c46e 100644 --- a/locales/zh/plugin__gitops-plugin.json +++ b/locales/zh/plugin__gitops-plugin.json @@ -108,11 +108,11 @@ "Edit ImageUpdater": "Edit ImageUpdater", "Delete ImageUpdater": "Delete ImageUpdater", "Error: Missing required route parameters": "Error: Missing required route parameters", + "True": "True", + "False": "False", "ImageUpdater details": "ImageUpdater details", "Ready": "Ready", "Whether the last reconciliation completed without errors.": "Whether the last reconciliation completed without errors.", - "True": "True", - "False": "False", "Applications Matched": "Applications Matched", "Number of applications matched by this ImageUpdater.": "Number of applications matched by this ImageUpdater.", "Images Managed": "Images Managed", @@ -255,17 +255,22 @@ "No pods": "No pods", "There are no pods associated with the rollout.": "There are no pods associated with the rollout.", "Edit Pod": "Edit Pod", + "Edit Rollout": "Edit Rollout", "Promote": "Promote", "Full Promote": "Full Promote", "Abort": "Abort", "Retry": "Retry", "Restart": "Restart", - "Edit Rollout": "Edit Rollout", + "Rollback": "Rollback", "Age": "Age", "Info": "Info", "Ready containers": "Ready containers", "ready": "ready", "0 Pods": "0 Pods", + "Scaling down in:": "Scaling down in:", + "{{x}} failed with an error.": "{{x}} failed with an error.", + "Rollout Revisions": "Rollout Revisions", + "Close": "Close", "Stable": "Stable", "Active": "Active", "Preview": "Preview", @@ -274,6 +279,7 @@ "Replicas": "Replicas", "The number of desired replicas for the rollout": "The number of desired replicas for the rollout", "The current status of the rollout": "The current status of the rollout", + "There is no rollout status. Check that the Rollout Manager is created and is available.": "There is no rollout status. Check that the Rollout Manager is created and is available.", "Strategy": "Strategy", "Whether the rollout is using a blue-green or canary strategy": "Whether the rollout is using a blue-green or canary strategy", "No Argo Rollouts": "No Argo Rollouts", @@ -284,12 +290,10 @@ "Create Rollout": "Create Rollout", "Pods": "Pods", "Selector": "Selector", - "No labels": "No labels", "Rollout Status": "Rollout Status", "Revisions": "Revisions", "There was an error retrieving the rollout. Check your connection and reload the page.": "There was an error retrieving the rollout. Check your connection and reload the page.", "There was an error retrieving the rollout revisions. Check your connection and reload the page.": "There was an error retrieving the rollout revisions. Check your connection and reload the page.", - "Rollout Revisions": "Rollout Revisions", "Active Service": "Active Service", "The active blue-green service": "The active blue-green service", "Preview Service": "Preview Service", @@ -322,6 +326,7 @@ "There was an error retrieving applicationsets. Check your connection and reload the page.": "There was an error retrieving applicationsets. Check your connection and reload the page.", "ApplicationSets": "ApplicationSets", "Create ApplicationSet": "Create ApplicationSet", + "No labels": "No labels", "Name must be unique within a namespace.": "Name must be unique within a namespace.", "Namespace defines the space within which each name must be unique.": "Namespace defines the space within which each name must be unique.", "Map of string keys and values that can be used to organize and categorize (scope and select) objects.": "Map of string keys and values that can be used to organize and categorize (scope and select) objects.", diff --git a/package.json b/package.json index c7e262ca3..0822eb3d0 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,8 @@ "dagre": "^0.8.5", "git-url-parse": "^13.1.0", "lodash": "^4.18.1", + "moment": "^2.29.4", + "rxjs": "^7.8.1", "react": "17.0.2", "react-helmet": "^6.1.0", "react-redux": "7.2.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67af753fb..01d14afc6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: lodash: specifier: ^4.18.1 version: 4.18.1 + moment: + specifier: ^2.29.4 + version: 2.30.1 react: specifier: 17.0.2 version: 17.0.2 @@ -52,6 +55,9 @@ importers: redux: specifier: 4.0.1 version: 4.0.1 + rxjs: + specifier: ^7.8.1 + version: 7.8.2 devDependencies: '@dagrejs/dagre': specifier: ^1.1.8 @@ -4205,6 +4211,9 @@ packages: mobx@6.13.6: resolution: {integrity: sha512-r19KNV0uBN4b+ER8Z0gA4y+MzDYIQ2SvOmn3fUrqPnWXdQfakd9yfbPBDBF/p5I+bd3N5Rk1fHONIvMay+bJGA==} + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -4880,6 +4889,9 @@ packages: rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -10725,6 +10737,8 @@ snapshots: mobx@6.13.6: {} + moment@2.30.1: {} + ms@2.0.0: {} ms@2.1.3: {} @@ -11415,6 +11429,10 @@ snapshots: rw@1.3.3: {} + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 diff --git a/src/gitops/components/rollout/RolloutDetailsTab.tsx b/src/gitops/components/rollout/RolloutDetailsTab.tsx index 168c5b69b..c5ab38975 100644 --- a/src/gitops/components/rollout/RolloutDetailsTab.tsx +++ b/src/gitops/components/rollout/RolloutDetailsTab.tsx @@ -119,7 +119,13 @@ const RolloutDetailsTab: React.FC = ({ obj }) => { diff --git a/src/gitops/components/rollout/RolloutList.tsx b/src/gitops/components/rollout/RolloutList.tsx index 4f47f3b05..d73cb8daa 100644 --- a/src/gitops/components/rollout/RolloutList.tsx +++ b/src/gitops/components/rollout/RolloutList.tsx @@ -109,7 +109,7 @@ const RolloutList: React.FC = ({ const { t } = useGitOpsTranslation(); - const columnsDV = useColumnsDV(namespace, getSortParams); + const columnsDV = useColumnsDV(namespace, getSortParams, t); const sortedRollouts = React.useMemo(() => { return sortData(rollouts, sortBy, direction); }, [rollouts, sortBy, direction]); @@ -132,7 +132,7 @@ const RolloutList: React.FC = ({ }); }, [filteredData, searchQuery]); - const rows = useRolloutsRowsDV(filteredBySearch, namespace); + const rows = useRolloutsRowsDV(filteredBySearch, namespace, t); const empty = ( @@ -278,9 +278,9 @@ export const sortData = ( export const useColumnsDV = ( namespace: string, getSortParams: (columnIndex: number) => ThProps['sort'], + t: (key: string) => string, ) => { const i: number = namespace ? 0 : 1; - const { t } = useGitOpsTranslation(); const columns: DataViewTh[] = [ { cell: t('Name'), @@ -350,7 +350,11 @@ export const useColumnsDV = ( return columns; }; -export const useRolloutsRowsDV = (rolloutsList, namespace): DataViewTr[] => { +export const useRolloutsRowsDV = ( + rolloutsList, + namespace, + t: (key: string) => string, +): DataViewTr[] => { const rows: DataViewTr[] = []; if (rolloutsList == undefined || rolloutsList.length == 0) { return rows; @@ -386,7 +390,18 @@ export const useRolloutsRowsDV = (rolloutsList, namespace): DataViewTr[] => { : []), { id: 'status', - cell: , + cell: ( + + ), }, { id: 'pods', diff --git a/src/gitops/components/rollout/RolloutNavPage.tsx b/src/gitops/components/rollout/RolloutNavPage.tsx index 53a4434c8..ef262663b 100644 --- a/src/gitops/components/rollout/RolloutNavPage.tsx +++ b/src/gitops/components/rollout/RolloutNavPage.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import { useLocation } from 'react-router-dom-v5-compat'; import { useGitOpsTranslation } from '@gitops/utils/hooks/useGitOpsTranslation'; import { HorizontalNav, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; @@ -10,6 +11,7 @@ import EventsTab from '../shared/EventsTab/EventsTab'; import ResourceYAMLTab from '../shared/ResourceYAMLTab/ResourceYAMLTab'; import { useRolloutActionsProvider } from './hooks/useRolloutActionsProvider'; +import { useRolloutRevisionsActionsProvider } from './hooks/useRolloutRevisionsActionsProvider'; import { RolloutKind, RolloutModel } from './model/RolloutModel'; import RolloutDetailsTab from './RolloutDetailsTab'; import RolloutPodsTab from './RolloutPodsTab'; @@ -34,7 +36,10 @@ const RolloutNavPage: React.FC = ({ name, namespace, kind }) = namespace, }); - const [actions] = useRolloutActionsProvider(rollout); + const { pathname } = useLocation(); + const [rolloutActions] = useRolloutActionsProvider(rollout); + const [revisionActions] = useRolloutRevisionsActionsProvider(rollout); + const actions = /\/revisions\/?$/.test(pathname) ? revisionActions : rolloutActions; const pages = React.useMemo( () => [ diff --git a/src/gitops/components/rollout/RolloutRevisionsTab.tsx b/src/gitops/components/rollout/RolloutRevisionsTab.tsx index 84e65f098..3cce4b03c 100644 --- a/src/gitops/components/rollout/RolloutRevisionsTab.tsx +++ b/src/gitops/components/rollout/RolloutRevisionsTab.tsx @@ -7,7 +7,7 @@ import { useGitOpsTranslation } from '@gitops/utils/hooks/useGitOpsTranslation'; import { resourceAsArray } from '@gitops/utils/utils'; import { K8sResourceCommon, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; import { ErrorState } from '@patternfly/react-component-groups'; -import { Flex, FlexItem, PageSection, PageSectionVariants, Title } from '@patternfly/react-core'; +import { Flex, FlexItem, PageSection, PageSectionVariants } from '@patternfly/react-core'; import { ReplicaSetKind, RolloutKind } from './model/RolloutModel'; import { Revisions } from './revisions/Revisions'; @@ -51,10 +51,6 @@ const RolloutRevisionsTab: React.FC = ({ obj: rollout variant={PageSectionVariants.default} className={classNames('co-m-pane__body', { 'co-m-pane__body--section-heading': true })} > - - {t('Rollout Revisions')} - - {/* eslint-disable-next-line no-nested-ternary */} {loadError || podsloadError ? (
diff --git a/src/gitops/components/rollout/RolloutStatus.tsx b/src/gitops/components/rollout/RolloutStatus.tsx index b20bb6f1d..31e11aa11 100644 --- a/src/gitops/components/rollout/RolloutStatus.tsx +++ b/src/gitops/components/rollout/RolloutStatus.tsx @@ -15,9 +15,14 @@ import { RolloutStatus } from './utils/rollout-utils'; interface RolloutStatusProps { status: RolloutStatus; message?: string; + showPhaseLabel?: boolean; } -export const RolloutStatusFragment: React.FC = ({ status, message }) => { +export const RolloutStatusFragment: React.FC = ({ + status, + message, + showPhaseLabel = true, +}) => { let icon: React.ReactNode; switch (status) { case RolloutStatus.Progressing: { @@ -41,9 +46,9 @@ export const RolloutStatusFragment: React.FC = ({ status, me } return ( - Phase: + {showPhaseLabel && Phase:} {icon} {status}{' '} - {status == RolloutStatus.Degraded && message && ( + {(status == RolloutStatus.Degraded || status === undefined) && message && ( diff --git a/src/gitops/components/rollout/hooks/useRolloutActionsProvider.tsx b/src/gitops/components/rollout/hooks/useRolloutActionsProvider.tsx index adcdd2697..af1955a59 100644 --- a/src/gitops/components/rollout/hooks/useRolloutActionsProvider.tsx +++ b/src/gitops/components/rollout/hooks/useRolloutActionsProvider.tsx @@ -23,81 +23,6 @@ export const useRolloutActionsProvider: UseRolloutActionsProvider = (rollout) => const actions = React.useMemo( () => [ - // { - // id: 'gitops-action-promote', - // // disabled: !isDeploying(rollout), - // label: t('Promote'), - // accessReview: { - // group: RolloutModel.apiGroup, - // verb: 'patch' as K8sVerb, - // resource: RolloutModel.plural, - // namespace: rollout?.metadata?.namespace, - // }, - // cta: () => - // // TODO - Show toast alert if it fails but this is proving more challenging then I thought - // // promoteRollout(rollout, false) - // {}, - // }, - // { - // id: 'gitops-action-promote-full', - // // disabled: !isDeploying(rollout), - // label: t('Full Promote'), - // accessReview: { - // group: RolloutModel.apiGroup, - // verb: 'patch' as K8sVerb, - // resource: RolloutModel.plural, - // namespace: rollout?.metadata?.namespace, - // }, - // cta: () => - // // TODO - Show toast alert if it fails but this is proving more challenging then I thought - // // promoteRollout(rollout, true) - // {}, - // }, - // { - // id: 'gitops-action-abort', - // // disabled: !isDeploying(rollout), - // label: t('Abort'), - // accessReview: { - // group: RolloutModel.apiGroup, - // verb: 'patch' as K8sVerb, - // resource: RolloutModel.plural, - // namespace: rollout?.metadata?.namespace, - // }, - // cta: () => - // // TODO - Show toast alert if it fails but this is proving more challenging then I thought - // // abortRollout(rollout) - // {}, - // }, - // { - // id: 'gitops-action-retry', - // disabled: rollout?.status?.phase !== RolloutStatus.Degraded, - // label: t('Retry'), - // accessReview: { - // group: RolloutModel.apiGroup, - // verb: 'patch' as K8sVerb, - // resource: RolloutModel.plural, - // namespace: rollout?.metadata?.namespace, - // }, - // cta: () => - // // TODO - Show toast alert if it fails but this is proving more challenging then I thought - // // retryRollout(rollout) - // {}, - // }, - // { - // id: 'gitops-action-restart', - // disabled: false, - // label: t('Restart'), - // accessReview: { - // group: RolloutModel.apiGroup, - // verb: 'patch' as K8sVerb, - // resource: RolloutModel.plural, - // namespace: rollout?.metadata?.namespace, - // }, - // cta: () => - // // TODO - Show toast alert if it fails but this is proving more challenging then I thought - // // restartRollout(rollout) - // {}, - // }, { id: 'gitops-action-edit-labels-rollout', disabled: false, diff --git a/src/gitops/components/rollout/hooks/useRolloutRevisionsActionsProvider.tsx b/src/gitops/components/rollout/hooks/useRolloutRevisionsActionsProvider.tsx new file mode 100644 index 000000000..854aeeb25 --- /dev/null +++ b/src/gitops/components/rollout/hooks/useRolloutRevisionsActionsProvider.tsx @@ -0,0 +1,109 @@ +import * as React from 'react'; + +import { Action, K8sVerb } from '@openshift-console/dynamic-plugin-sdk'; + +import { useGitOpsTranslation } from '../../../utils/hooks/useGitOpsTranslation'; +import { RolloutKind, RolloutModel } from '../model/RolloutModel'; +import { abortRollout, promoteRollout, restartRollout, retryRollout } from '../services/Rollout'; +import { isDeploying, RolloutStatus } from '../utils/rollout-utils'; + +type UseRolloutRevisionsActionsProvider = ( + rollout: RolloutKind, + onError?: (error: Error | string, action: string) => void, +) => [actions: Action[]]; + +export const useRolloutRevisionsActionsProvider: UseRolloutRevisionsActionsProvider = ( + rollout, + onError, +) => { + const { t } = useGitOpsTranslation(); + + const actions = React.useMemo( + () => [ + { + id: 'gitops-action-promote', + disabled: !isDeploying(rollout), + label: t('Promote'), + accessReview: { + group: RolloutModel.apiGroup, + verb: 'patch' as K8sVerb, + resource: RolloutModel.plural, + namespace: rollout?.metadata?.namespace, + }, + cta: () => + void promoteRollout(rollout, false).catch((err: unknown) => { + onError?.(err instanceof Error ? err : String(err), t('Promote')); + }), + }, + { + id: 'gitops-action-promote-full', + disabled: !isDeploying(rollout), + label: t('Full Promote'), + accessReview: { + group: RolloutModel.apiGroup, + verb: 'patch' as K8sVerb, + resource: RolloutModel.plural, + namespace: rollout?.metadata?.namespace, + }, + cta: () => + void promoteRollout(rollout, true).catch((err: unknown) => { + onError?.(err instanceof Error ? err : String(err), t('Full Promote')); + }), + }, + { + id: 'separator-rollout-actions', + label: '', + cta: () => undefined, + }, + { + id: 'gitops-action-abort', + disabled: !isDeploying(rollout), + label: t('Abort'), + accessReview: { + group: RolloutModel.apiGroup, + verb: 'patch' as K8sVerb, + resource: RolloutModel.plural, + namespace: rollout?.metadata?.namespace, + }, + cta: () => + void abortRollout(rollout).catch((err: unknown) => { + onError?.(err instanceof Error ? err : String(err), t('Abort')); + }), + }, + { + id: 'gitops-action-retry', + disabled: rollout?.status?.phase !== RolloutStatus.Degraded, + label: t('Retry'), + accessReview: { + group: RolloutModel.apiGroup, + verb: 'patch' as K8sVerb, + resource: RolloutModel.plural, + namespace: rollout?.metadata?.namespace, + }, + cta: () => + void retryRollout(rollout).catch((err: unknown) => { + onError?.(err instanceof Error ? err : String(err), t('Retry')); + }), + }, + { + id: 'gitops-action-restart', + disabled: false, + label: t('Restart'), + accessReview: { + group: RolloutModel.apiGroup, + verb: 'patch' as K8sVerb, + resource: RolloutModel.plural, + namespace: rollout?.metadata?.namespace, + }, + cta: () => { + void restartRollout(rollout).catch((err: unknown) => { + onError?.(err instanceof Error ? err : String(err), t('Restart')); + }); + }, + }, + ], + [rollout, t, onError], + ); + + return [actions]; +}; diff --git a/src/gitops/components/rollout/hooks/useRolloutRevisionsRSActionsProvider.tsx b/src/gitops/components/rollout/hooks/useRolloutRevisionsRSActionsProvider.tsx new file mode 100644 index 000000000..a19a1a62a --- /dev/null +++ b/src/gitops/components/rollout/hooks/useRolloutRevisionsRSActionsProvider.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; + +import { Action, K8sVerb } from '@openshift-console/dynamic-plugin-sdk'; + +import { useGitOpsTranslation } from '../../../utils/hooks/useGitOpsTranslation'; +import { RolloutKind, RolloutModel } from '../model/RolloutModel'; +import { ReplicaSetInfo } from '../revisions/ReplicaSetInfo'; +import { rollbackRollout } from '../services/Rollout'; + +type UseRolloutRevisionsRSActionsProvider = ( + rollout: RolloutKind, + replicaSet: ReplicaSetInfo, + index: number, + onError?: (err: Error | string, action: string) => void, +) => [actions: Action[]]; + +export const useRolloutRevisionsRSActionsProvider: UseRolloutRevisionsRSActionsProvider = ( + rollout, + replicaSet, + index, + onError, +) => { + const { t } = useGitOpsTranslation(); + + const actions = React.useMemo( + () => [ + { + id: 'gitops-action-rollback-' + index, + disabled: index === 0 || !replicaSet?.replicaSet, + label: t('Rollback'), + accessReview: { + group: RolloutModel.apiGroup, + verb: 'patch' as K8sVerb, + resource: RolloutModel.plural, + namespace: rollout?.metadata?.namespace, + }, + cta: () => { + void rollbackRollout(rollout, replicaSet.replicaSet).catch((err: unknown) => { + onError?.(err instanceof Error ? err : String(err), t('Rollback')); + }); + }, + }, + ], + [rollout, replicaSet, index, onError, t], + ); + + return [actions]; +}; diff --git a/src/gitops/components/rollout/revisions/ReplicaSetInfo.ts b/src/gitops/components/rollout/revisions/ReplicaSetInfo.ts index 7f805da3b..7ece138f1 100644 --- a/src/gitops/components/rollout/revisions/ReplicaSetInfo.ts +++ b/src/gitops/components/rollout/revisions/ReplicaSetInfo.ts @@ -50,6 +50,7 @@ export type ReplicaSetInfo = { podTemplateHash: string; analysisRuns: AnalysisRunInfo[]; replicaSet?: K8sResourceCommon; + replicaSetScaleDownDeadline?: string; }; function getReplicaSetStatus( @@ -184,6 +185,7 @@ export const getReplicaSetInfo = async ( podTemplateHash: podTemplateHash, analysisRuns: getAnalysisRunInfo(analysisRuns, podTemplateHash, revision), replicaSet: rs, + replicaSetScaleDownDeadline: rs.metadata?.annotations?.['scale-down-deadline'], }); }); diff --git a/src/gitops/components/rollout/revisions/Revisions.scss b/src/gitops/components/rollout/revisions/Revisions.scss new file mode 100644 index 000000000..1c06ccdf1 --- /dev/null +++ b/src/gitops/components/rollout/revisions/Revisions.scss @@ -0,0 +1,7 @@ +.gitops-revisions-table { + tbody > tr:first-child { + --pf-v6-c-table__tr--BorderBlockEndWidth: 1px; + + border-top: 1px solid; + } +} diff --git a/src/gitops/components/rollout/revisions/Revisions.tsx b/src/gitops/components/rollout/revisions/Revisions.tsx index c4c4fddfb..bc1a8264f 100644 --- a/src/gitops/components/rollout/revisions/Revisions.tsx +++ b/src/gitops/components/rollout/revisions/Revisions.tsx @@ -1,19 +1,46 @@ import * as React from 'react'; import { Link } from 'react-router-dom-v5-compat'; +import moment from 'moment'; import { getPodStatus } from '@gitops/components/shared/pod-utils'; import { PodKind } from '@gitops/topology/console/types'; +import ActionsDropdown from '@gitops/utils/components/ActionDropDown/ActionDropDown'; import { isApplicationRefreshing } from '@gitops/utils/gitops'; import { t } from '@gitops/utils/hooks/useGitOpsTranslation'; -import { getResourceUrl, modelToGroupVersionKind, resourceAsArray } from '@gitops/utils/utils'; import { + formatDuration, + getResourceUrl, + modelToGroupVersionKind, + resourceAsArray, +} from '@gitops/utils/utils'; +import { + Action, K8sModel, ResourceLink, Selector, useK8sModel, useK8sWatchResource, } from '@openshift-console/dynamic-plugin-sdk'; -import { Label, LabelGroup, Spinner, Tooltip } from '@patternfly/react-core'; +import { + Alert, + AlertActionCloseButton, + AlertGroup, + AlertVariant, + Button, + Divider, + Flex, + FlexItem, + Label, + LabelGroup, + Spinner, + Title, + Toolbar, + ToolbarContent, + ToolbarGroup, + ToolbarItem, + // Popover, + Tooltip, +} from '@patternfly/react-core'; import { DataViewTh, DataViewTrTree } from '@patternfly/react-data-view/dist/esm/DataViewTable'; import DataViewTableTree from '@patternfly/react-data-view/dist/esm/DataViewTableTree'; import { FolderIcon, FolderOpenIcon } from '@patternfly/react-icons'; @@ -23,9 +50,15 @@ import { EyeIcon } from '@patternfly/react-icons/dist/esm/icons/eye-icon'; import { MigrationIcon } from '@patternfly/react-icons/dist/esm/icons/migration-icon'; import { RunningIcon } from '@patternfly/react-icons/dist/esm/icons/running-icon'; +import { Ticker } from '../../shared/Ticker/Ticker'; import { AnalysisRunStatusFragment } from '../components/AnalysisRunStatus/AnalysisRunStatus'; +import { useRolloutRevisionsActionsProvider } from '../hooks/useRolloutRevisionsActionsProvider'; +import { useRolloutRevisionsRSActionsProvider } from '../hooks/useRolloutRevisionsRSActionsProvider'; import { AnalysisRunKind } from '../model/AnalysisRunModel'; import { ReplicaSetKind, RolloutKind, RolloutModel } from '../model/RolloutModel'; +import { RolloutStatusFragment } from '../RolloutStatus'; +import { abortRollout, promoteRollout, restartRollout, retryRollout } from '../services/Rollout'; +import { isDeploying, RolloutStatus } from '../utils/rollout-utils'; import { getAnalysisRunSelector, @@ -35,6 +68,8 @@ import { ReplicaSetStatus, } from './ReplicaSetInfo'; +import './Revisions.scss'; + interface RevisionsProps { rollout: RolloutKind; replicaSets: ReplicaSetKind[]; @@ -73,14 +108,68 @@ const getColumnsDV = (): DataViewTh[] => { 'aria-label': 'info', }, }, + { + cell: '', + props: { 'aria-label': 'actions' }, + }, ]; return columns; }; +const RolloutActionsCell: React.FC<{ + app: RolloutKind; + index: number; + onError?: (error: Error | string, action: string) => void; +}> = ({ app, index, onError }) => { + const actionList: [actions: Action[]] = useRolloutRevisionsActionsProvider(app, onError); + return ( +
+ +
+ ); +}; + +const RolloutRevisionRSActionsCell: React.FC<{ + rollout: RolloutKind; + replicaSet: ReplicaSetInfo; + index: number; + onError?: (error: Error | string, action: string) => void; +}> = ({ rollout, replicaSet, index, onError }) => { + const actionList: [actions: Action[]] = useRolloutRevisionsRSActionsProvider( + rollout, + replicaSet, + index, + onError, + ); + return ( +
+ +
+ ); +}; + +type RevisionAlert = { + key: string; + title: string; + message: string; + details?: string; + variant: AlertVariant; + actionLinks?: React.ReactNode; +}; + const getRowsDV = ( replicaSetInfo: ReplicaSetInfo[], replicaSetModel: K8sModel, obj: RolloutKind, + onRevisionError?: (error: Error | string, action: string) => void, ): DataViewTrTree[] => { const rows: DataViewTrTree[] = []; const rsChildren: DataViewTrTree[] = []; @@ -93,6 +182,7 @@ const getRowsDV = ( @@ -102,8 +192,25 @@ const getRowsDV = (
, obj.kind, - obj.status?.phase, + { + cell: ( + <> + { + + } + + ), + }, getAgeInMinutes(obj.metadata?.creationTimestamp) + 'm', + { + cell: <>, + }, + { + cell: , + }, ], }); @@ -172,9 +279,46 @@ const getRowsDV = ( ), }, replicaSet?.replicaSet.kind, - replicaSet?.status, + { + cell: ( +
+
{replicaSet?.status}
+ {replicaSet.replicaSetScaleDownDeadline && ( +
+ + {(now) => { + const time = moment(replicaSet.replicaSetScaleDownDeadline).diff( + now.toDate(), + 'second', + ); + return time <= 0 ? null : ( + + + + ); + }} + +
+ )} +
+ ), + }, getAgeInMinutes(replicaSet?.replicaSet?.metadata?.creationTimestamp) + 'm', getStatusSection(replicaSet.statuses), + { + cell: ( + + ), + }, ], ...{ children: podsChildren }, }); @@ -186,6 +330,7 @@ const getRowsDV = ( export const Revisions: React.FC = ({ rollout, replicaSets, pods }) => { const [replicaSetInfo, setReplicaSetInfo] = React.useState([]); + const [alerts, setAlerts] = React.useState([]); const selector: Selector = React.useMemo( () => getAnalysisRunSelector(resourceAsArray(replicaSets)), @@ -201,6 +346,23 @@ export const Revisions: React.FC = ({ rollout, replicaSets, pods selector: selector, }); + const removeAlert = React.useCallback((key: string) => { + setAlerts((prev) => prev.filter((alert) => alert.key !== key)); + }, []); + + const onRevisionError = React.useCallback((error: Error | string, action: string) => { + setAlerts((prev) => [ + ...prev, + { + key: `${Date.now()}-${prev.length}`, + title: t('{{x}} failed with an error.', { x: action }), + message: error instanceof Error ? error.message : error, + details: error instanceof Error ? error.stack : undefined, + variant: AlertVariant.danger, + }, + ]); + }, []); + React.useEffect(() => { getReplicaSetInfo( rollout, @@ -212,11 +374,128 @@ export const Revisions: React.FC = ({ rollout, replicaSets, pods }); }, [rollout, replicaSets, analysisRuns, pods]); - const rows = getRowsDV(replicaSetInfo, replicaSetModel, rollout); + const rows = getRowsDV(replicaSetInfo, replicaSetModel, rollout, onRevisionError); return ( <> + + + + + {t('Rollout Revisions')} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {alerts.map(({ key, title, message, details, variant }) => ( + removeAlert(key)} + actionClose={ + removeAlert(key)} /> + } + > + {message} + {details &&
{details}
} +
+ ))} +
{rollout.metadata && ( { , @@ -267,7 +546,7 @@ const getStatusSection = (statuses: ReplicaSetStatus[]) => { return ( {statuses.includes(ReplicaSetStatus.Stable) && ( -