diff --git a/locales/en/plugin__gitops-plugin.json b/locales/en/plugin__gitops-plugin.json index ffdbf1a23..d9117e16c 100644 --- a/locales/en/plugin__gitops-plugin.json +++ b/locales/en/plugin__gitops-plugin.json @@ -25,13 +25,15 @@ "Sync Status": "Sync Status", "History": "History", "Events": "Events", + "Application resources": "Application resources", + "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.": "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", "No resources": "No resources", "There are no resources associated with the application.": "There are no resources associated with the application.", - "Application resources": "Application resources", - "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.": "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", "Name": "Name", "Namespace": "Namespace", "Sync Wave": "Sync Wave", + "No Sync Status": "No Sync Status", + "None": "None", "Kind": "Kind", "Type": "Type", "Repository": "Repository", @@ -108,11 +110,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", @@ -284,7 +286,6 @@ "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.", @@ -311,7 +312,7 @@ "There are no Argo CD Applications in all projects.": "There are no Argo CD Applications in all projects.", "There was an error retrieving applications. Check your connection and reload the page.": "There was an error retrieving applications. Check your connection and reload the page.", "ApplicationSet Applications": "ApplicationSet Applications", - "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.": "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.", + "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.": "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.", "Revision": "Revision", "No Argo CD ApplicationSets match the label filter": "No Argo CD ApplicationSets match the label filter", "Try removing the filter or selecting a different label to see more ApplicationSets.": "Try removing the filter or selecting a different label to see more ApplicationSets.", @@ -322,6 +323,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.", @@ -332,6 +334,8 @@ "Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.": "Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.", "Owner references link this resource to its parent object. For example, Applications generated by an ApplicationSet will have that ApplicationSet as their owner. This relationship enables proper resource lifecycle management and garbage collection.": "Owner references link this resource to its parent object. For example, Applications generated by an ApplicationSet will have that ApplicationSet as their owner. This relationship enables proper resource lifecycle management and garbage collection.", "This details page is under tech preview, but not necessarily the resource it represents": "This details page is under tech preview, but not necessarily the resource it represents", + "List view": "List view", + "Graph view": "Graph view", "Sync": "Sync", "Stop": "Stop", "Refresh": "Refresh", diff --git a/locales/ja/plugin__gitops-plugin.json b/locales/ja/plugin__gitops-plugin.json index 2430d79c3..e30d7c626 100644 --- a/locales/ja/plugin__gitops-plugin.json +++ b/locales/ja/plugin__gitops-plugin.json @@ -25,13 +25,15 @@ "Sync Status": "Sync Status", "History": "History", "Events": "Events", + "Application resources": "Application resources", + "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.": "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", "No resources": "No resources", "There are no resources associated with the application.": "There are no resources associated with the application.", - "Application resources": "Application resources", - "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.": "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", "Name": "Name", "Namespace": "Namespace", "Sync Wave": "Sync Wave", + "No Sync Status": "No Sync Status", + "None": "None", "Kind": "Kind", "Type": "Type", "Repository": "Repository", @@ -108,11 +110,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", @@ -284,7 +286,6 @@ "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.", @@ -311,7 +312,7 @@ "There are no Argo CD Applications in all projects.": "There are no Argo CD Applications in all projects.", "There was an error retrieving applications. Check your connection and reload the page.": "There was an error retrieving applications. Check your connection and reload the page.", "ApplicationSet Applications": "ApplicationSet Applications", - "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.": "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.", + "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.": "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.", "Revision": "リビジョン", "No Argo CD ApplicationSets match the label filter": "No Argo CD ApplicationSets match the label filter", "Try removing the filter or selecting a different label to see more ApplicationSets.": "Try removing the filter or selecting a different label to see more ApplicationSets.", @@ -322,6 +323,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.", @@ -332,6 +334,8 @@ "Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.": "Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.", "Owner references link this resource to its parent object. For example, Applications generated by an ApplicationSet will have that ApplicationSet as their owner. This relationship enables proper resource lifecycle management and garbage collection.": "Owner references link this resource to its parent object. For example, Applications generated by an ApplicationSet will have that ApplicationSet as their owner. This relationship enables proper resource lifecycle management and garbage collection.", "This details page is under tech preview, but not necessarily the resource it represents": "This details page is under tech preview, but not necessarily the resource it represents", + "List view": "List view", + "Graph view": "Graph view", "Sync": "Sync", "Stop": "Stop", "Refresh": "Refresh", diff --git a/locales/ko/plugin__gitops-plugin.json b/locales/ko/plugin__gitops-plugin.json index 9b2a17804..84efc1ccc 100644 --- a/locales/ko/plugin__gitops-plugin.json +++ b/locales/ko/plugin__gitops-plugin.json @@ -25,13 +25,15 @@ "Sync Status": "Sync Status", "History": "History", "Events": "Events", + "Application resources": "Application resources", + "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.": "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", "No resources": "No resources", "There are no resources associated with the application.": "There are no resources associated with the application.", - "Application resources": "Application resources", - "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.": "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", "Name": "Name", "Namespace": "Namespace", "Sync Wave": "Sync Wave", + "No Sync Status": "No Sync Status", + "None": "None", "Kind": "Kind", "Type": "Type", "Repository": "Repository", @@ -108,11 +110,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", @@ -284,7 +286,6 @@ "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.", @@ -311,7 +312,7 @@ "There are no Argo CD Applications in all projects.": "There are no Argo CD Applications in all projects.", "There was an error retrieving applications. Check your connection and reload the page.": "There was an error retrieving applications. Check your connection and reload the page.", "ApplicationSet Applications": "ApplicationSet Applications", - "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.": "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.", + "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.": "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.", "Revision": "개정 버전", "No Argo CD ApplicationSets match the label filter": "No Argo CD ApplicationSets match the label filter", "Try removing the filter or selecting a different label to see more ApplicationSets.": "Try removing the filter or selecting a different label to see more ApplicationSets.", @@ -322,6 +323,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.", @@ -332,6 +334,8 @@ "Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.": "Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.", "Owner references link this resource to its parent object. For example, Applications generated by an ApplicationSet will have that ApplicationSet as their owner. This relationship enables proper resource lifecycle management and garbage collection.": "Owner references link this resource to its parent object. For example, Applications generated by an ApplicationSet will have that ApplicationSet as their owner. This relationship enables proper resource lifecycle management and garbage collection.", "This details page is under tech preview, but not necessarily the resource it represents": "This details page is under tech preview, but not necessarily the resource it represents", + "List view": "List view", + "Graph view": "Graph view", "Sync": "Sync", "Stop": "Stop", "Refresh": "Refresh", diff --git a/locales/zh/plugin__gitops-plugin.json b/locales/zh/plugin__gitops-plugin.json index 873866551..0ab083b71 100644 --- a/locales/zh/plugin__gitops-plugin.json +++ b/locales/zh/plugin__gitops-plugin.json @@ -25,13 +25,15 @@ "Sync Status": "Sync Status", "History": "History", "Events": "Events", + "Application resources": "Application resources", + "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.": "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", "No resources": "No resources", "There are no resources associated with the application.": "There are no resources associated with the application.", - "Application resources": "Application resources", - "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.": "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", "Name": "Name", "Namespace": "Namespace", "Sync Wave": "Sync Wave", + "No Sync Status": "No Sync Status", + "None": "None", "Kind": "Kind", "Type": "Type", "Repository": "Repository", @@ -108,11 +110,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", @@ -284,7 +286,6 @@ "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.", @@ -311,7 +312,7 @@ "There are no Argo CD Applications in all projects.": "There are no Argo CD Applications in all projects.", "There was an error retrieving applications. Check your connection and reload the page.": "There was an error retrieving applications. Check your connection and reload the page.", "ApplicationSet Applications": "ApplicationSet Applications", - "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.": "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.", + "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.": "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.", "Revision": "修订", "No Argo CD ApplicationSets match the label filter": "No Argo CD ApplicationSets match the label filter", "Try removing the filter or selecting a different label to see more ApplicationSets.": "Try removing the filter or selecting a different label to see more ApplicationSets.", @@ -322,6 +323,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.", @@ -332,6 +334,8 @@ "Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.": "Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.", "Owner references link this resource to its parent object. For example, Applications generated by an ApplicationSet will have that ApplicationSet as their owner. This relationship enables proper resource lifecycle management and garbage collection.": "Owner references link this resource to its parent object. For example, Applications generated by an ApplicationSet will have that ApplicationSet as their owner. This relationship enables proper resource lifecycle management and garbage collection.", "This details page is under tech preview, but not necessarily the resource it represents": "This details page is under tech preview, but not necessarily the resource it represents", + "List view": "List view", + "Graph view": "Graph view", "Sync": "Sync", "Stop": "Stop", "Refresh": "Refresh", diff --git a/src/gitops/components/application/ApplicationResourcesTab.tsx b/src/gitops/components/application/ApplicationResourcesTab.tsx index 01680784d..162b28458 100644 --- a/src/gitops/components/application/ApplicationResourcesTab.tsx +++ b/src/gitops/components/application/ApplicationResourcesTab.tsx @@ -3,24 +3,11 @@ import { RouteComponentProps } from 'react-router'; import classNames from 'classnames'; import { useArgoServer } from '@gitops/hooks/useArgoServer'; -import { useResourceActionsProvider } from '@gitops/hooks/useResourceActionsProvider'; -import HealthStatus from '@gitops/Statuses/HealthStatus'; -import SyncStatus from '@gitops/Statuses/SyncStatus'; -import ActionDropDown from '@gitops/utils/components/ActionDropDown/ActionDropDown'; + import { t } from '@gitops/utils/hooks/useGitOpsTranslation'; import { ApplicationKind, ApplicationResourceStatus } from '@gitops-models/ApplicationModel'; +import { useUserSettings } from '@openshift-console/dynamic-plugin-sdk'; import { - Action, - K8sGroupVersionKind, - ListPageFilter, - ResourceLink, - RowFilter, - RowFilterItem, - useListPageFilter, -} from '@openshift-console/dynamic-plugin-sdk'; -import { - EmptyState, - EmptyStateBody, Flex, FlexItem, PageBody, @@ -28,16 +15,13 @@ import { PageSectionVariants, Title, } from '@patternfly/react-core'; -import { DataViewState } from '@patternfly/react-data-view/dist/esm/DataView'; -import { DataViewTh, DataViewTr } from '@patternfly/react-data-view/dist/esm/DataViewTable'; -import { CubesIcon } from '@patternfly/react-icons'; -import { Tbody, Td, Tr } from '@patternfly/react-table'; - -import { getApplicationArgoUrl } from '../../utils/gitops'; import ArgoCDLink from '../shared/ArgoCDLink/ArgoCDLink'; -import { GitOpsDataViewTable, useGitOpsDataViewSort } from '../shared/DataView'; - -import { ApplicationGraphView } from './graph/ApplicationGraphView'; +import ApplicationResourcesView from './ApplicationResourcesView'; +import { + APPLICATION_RESOURCES_VIEW_SETTING_KEY, + ApplicationResourcesViewType, +} from './ApplicationResourcesViewType'; +import { getApplicationArgoUrl } from '@gitops/utils/gitops'; type ApplicationResourcesTabProps = RouteComponentProps<{ ns: string; @@ -48,7 +32,23 @@ type ApplicationResourcesTabProps = RouteComponentProps<{ const ApplicationResourcesTab: React.FC = ({ obj }) => { const argoServer = useArgoServer(obj); - const argoUrl = getApplicationArgoUrl(argoServer, obj); + const argoURL = getApplicationArgoUrl(argoServer, obj); + + const [savedViewType, setSavedViewType, viewSettingsLoaded] = + useUserSettings( + APPLICATION_RESOURCES_VIEW_SETTING_KEY, + ApplicationResourcesViewType.graph, + false, + ); + const [viewType, setViewType] = React.useState( + ApplicationResourcesViewType.graph, + ); + + React.useEffect(() => { + if (viewSettingsLoaded) { + setViewType(savedViewType ?? ApplicationResourcesViewType.graph); + } + }, [savedViewType, viewSettingsLoaded]); let resources: ApplicationResourceStatus[]; if (obj?.status?.resources) { @@ -57,42 +57,18 @@ const ApplicationResourcesTab: React.FC = ({ obj } resources = []; } - const columnSortConfig = React.useMemo( - () => - ['name', 'namespace', 'sync-wave', 'sync-status', 'health-status', 'actions'].map((key) => ({ - key, - })), - [], + const onViewChange = React.useCallback( + (newViewType: ApplicationResourcesViewType) => { + setViewType(newViewType); + setSavedViewType(newViewType); + }, + [setSavedViewType], ); - const { sortBy, direction, getSortParams } = useGitOpsDataViewSort(columnSortConfig); - const columnsDV = useResourceColumnsDV(getSortParams); - const sortedResources = React.useMemo(() => { - return sortData(resources, sortBy, direction); - }, [resources, sortBy, direction]); - - // TODO: use alternate filter since it is deprecated. See DataTableView potentially - const resourceFilters = React.useMemo(() => filters(sortedResources), [sortedResources]); - const [data, filteredData, onFilterChange] = useListPageFilter(sortedResources, resourceFilters); - - const memoizedFilteredResources = React.useMemo(() => [...filteredData], [filteredData]); - const isEmptyResources = memoizedFilteredResources.length === 0; - - const rows = useResourceRowsDV(memoizedFilteredResources, obj, argoUrl); + if (!viewSettingsLoaded) { + return null; + } - const empty = ( - - - - - - {t('There are no resources associated with the application.')} - - - - - - ); return (
= ({ obj } {t('Application resources')} + {t( - "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", + "The graph and table views show health and sync status for the application's immediate resources only. Click the Argo CD Link to see the complete resource tree. Use the filter to filter resources based on status and kind.", )} - {argoUrl ? : <>} + + + - <> - {obj.metadata && ( - - - - - - - - - - - )} - + {obj?.metadata && ( + + )}
); }; -const sortData = ( - data: ApplicationResourceStatus[], - sortBy: string | undefined, - direction: 'asc' | 'desc' | undefined, -) => { - if (!sortBy || !direction) return data; - - return [...data].sort((a, b) => { - let aValue: any, bValue: any; - - switch (sortBy) { - case 'name': - aValue = a.name || ''; - bValue = b.name || ''; - break; - case 'namespace': - aValue = a.namespace || ''; - bValue = b.namespace || ''; - break; - case 'sync-wave': - aValue = a.syncWave || ''; - bValue = b.syncWave || ''; - break; - case 'sync-status': - aValue = a.status || ''; - bValue = b.status || ''; - break; - case 'health-status': - aValue = a.health?.status || ''; - bValue = b.health?.status || ''; - break; - default: - return 0; - } - - if (direction === 'asc') { - // eslint-disable-next-line no-nested-ternary - return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; - } else { - // eslint-disable-next-line no-nested-ternary - return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; - } - }); -}; - -export const useResourceColumnsDV = (getSortParams) => { - const columns: DataViewTh[] = [ - { - cell: t('Name'), - props: { - 'aria-label': 'name', - className: 'pf-m-width-25', - sort: getSortParams(0), - }, - }, - { - cell: t('Namespace'), - props: { - 'aria-label': 'namespace', - className: 'pf-m-width-20', - sort: getSortParams(1), - }, - }, - { - cell: t('Sync Wave'), - props: { - 'aria-label': 'sync wave', - className: 'pf-m-width-15', - sort: getSortParams(2), - }, - }, - { - cell: t('Sync Status'), - props: { - 'aria-label': 'sync status', - className: 'pf-m-width-15', - sort: getSortParams(3), - }, - }, - { - cell: t('Health Status'), - props: { - 'aria-label': 'health status', - className: 'pf-m-width-15', - sort: getSortParams(4), - }, - }, - { - cell: '', - props: { 'aria-label': 'actions' }, - }, - ]; - - return columns; -}; - -const useResourceRowsDV = ( - resources: ApplicationResourceStatus[], - obj: ApplicationKind, - argoBaseURL: string, -): DataViewTr[] => { - const rows: DataViewTr[] = []; - - resources.forEach((resource, index) => { - const gvk: K8sGroupVersionKind = { - version: resource.version, - group: resource.group, - kind: resource.kind, - }; - - rows.push([ - { - cell: ( -
- -
- ), - id: resource.name + '-' + index, - dataLabel: 'Name', - }, - { - cell: resource.namespace ? resource.namespace : '-', - id: resource.namespace, - dataLabel: 'Namespace', - }, - { - id: 'sync-wave-' + index, - cell: <>{resource.syncWave || '-'}, - dataLabel: 'Sync Order', - }, - { - id: 'sync-status-' + index, - cell: <>{resource.status ? : '-'}, - }, - { - id: 'health-status-' + index, - cell: ( - <> - {resource.health?.status && ( - - )} - {!resource.health?.status && '-'} - - ), - }, - { - id: 'actions-' + index, - cell: , - props: { style: { paddingTop: 8, paddingRight: 0, paddingLeft: 0, width: 10 } }, - }, - ]); - }); - return rows; -}; - -const ResourceActionsCell: React.FC<{ - resource: ApplicationResourceStatus; - app: ApplicationKind; - argoBaseURL: string; -}> = ({ resource, app, argoBaseURL }) => { - const actionList: [actions: Action[]] = useResourceActionsProvider(resource, app, argoBaseURL); - return ( -
- -
- ); -}; - -const filters = (resources: ApplicationResourceStatus[]): RowFilter[] => { - return [ - { - filterGroupName: t('Sync Status'), - type: 'resource-sync', - reducer: (resource) => (resource.status ? resource.status : 'No Sync Status'), - filter: (input, resource) => { - if (input.selected?.length) { - if (resource?.status) { - return input.selected.includes(resource.status); - } else { - return input.selected.includes('No Sync Status'); // The resource has no health status and the None filter is selected - } - } - return true; - }, - items: resources - .map((resource) => { - return { - id: resource.status ? resource.status : 'No Sync Status', - title: resource.status ? resource.status : 'No Sync Status', - }; - }) - .reduce(function (result: RowFilterItem[], resource: RowFilterItem) { - if (!result.some((item) => item.id === resource.id)) { - result.push(resource); - } - return result; - }, []), - }, - { - filterGroupName: t('Health Status'), - type: 'resource-health', - reducer: (resource) => (resource.health ? resource.health.status : 'None'), - filter: (input, resource) => { - if (input.selected?.length) { - if (resource?.health?.status) { - return input.selected.includes(resource.health.status); - } else if (input.selected.includes('None')) { - return true; - } - return false; - } - return true; - }, - items: resources - .map((resource) => { - return { - id: resource.health && resource.health.status ? resource.health.status : 'None', - title: resource.health && resource.health.status ? resource.health.status : 'None', - }; - }) - .reduce(function (result: RowFilterItem[], resource: RowFilterItem) { - if (!result.some((item) => item.id === resource.id)) { - result.push(resource); - } - return result; - }, []), - }, - { - filterGroupName: t('Kind'), - type: 'resource-kind', - reducer: (resource) => resource.kind, - filter: (input, resource) => { - if (input.selected?.length) { - return input.selected.includes(resource.kind); - } else { - return true; - } - }, - items: resources - .map((resource) => { - return { id: resource.kind, title: resource.kind }; - }) - .reduce(function (result: RowFilterItem[], resource: RowFilterItem) { - if (!result.some((item) => item.id === resource.id)) { - result.push(resource); - } - return result; - }, []), - }, - ]; -}; - export default ApplicationResourcesTab; diff --git a/src/gitops/components/application/ApplicationResourcesToolbar.tsx b/src/gitops/components/application/ApplicationResourcesToolbar.tsx new file mode 100644 index 000000000..564eed748 --- /dev/null +++ b/src/gitops/components/application/ApplicationResourcesToolbar.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; + +import GitOpsViewSwitcher from '../shared/GitOpsViewSwitcher'; +import { GitOpsViewType } from '../shared/GitOpsViewType'; + +type ApplicationResourcesToolbarProps = { + viewType: GitOpsViewType; + onViewChange: (view: GitOpsViewType) => void; + isDisabled?: boolean; +}; + +const ApplicationResourcesToolbar: React.FC = (props) => ( + +); + +export default ApplicationResourcesToolbar; diff --git a/src/gitops/components/application/ApplicationResourcesView.tsx b/src/gitops/components/application/ApplicationResourcesView.tsx new file mode 100644 index 000000000..18e12c249 --- /dev/null +++ b/src/gitops/components/application/ApplicationResourcesView.tsx @@ -0,0 +1,390 @@ +import * as React from 'react'; + +import HealthStatus from '@gitops/Statuses/HealthStatus'; +import SyncStatus from '@gitops/Statuses/SyncStatus'; +import { t } from '@gitops/utils/hooks/useGitOpsTranslation'; +import { ApplicationKind, ApplicationResourceStatus } from '@gitops-models/ApplicationModel'; +import { + K8sGroupVersionKind, + ListPageFilter, + ResourceLink, + RowFilter, + RowFilterItem, + useListPageFilter, +} from '@openshift-console/dynamic-plugin-sdk'; +import { + EmptyState, + EmptyStateBody, + Flex, + FlexItem, + Stack, + StackItem, +} from '@patternfly/react-core'; +import { DataViewState } from '@patternfly/react-data-view/dist/esm/DataView'; +import { DataViewTh, DataViewTr } from '@patternfly/react-data-view/dist/esm/DataViewTable'; +import { CubesIcon } from '@patternfly/react-icons'; +import { Tbody, Td, Tr } from '@patternfly/react-table'; + +import { GitOpsDataViewTable, useGitOpsDataViewSort } from '../shared/DataView'; +import ResourceActionsCell from '../shared/ResourceActionsCell/ResourceActionsCell'; + +import { ApplicationGraphView } from './graph/ApplicationGraphView'; +import ApplicationResourcesToolbar from './ApplicationResourcesToolbar'; +import { ApplicationResourcesViewType } from './ApplicationResourcesViewType'; + +import '../shared/GitOpsGraphListView.scss'; + +type ApplicationResourcesViewProps = { + application: ApplicationKind; + resources: ApplicationResourceStatus[]; + viewType: ApplicationResourcesViewType; + onViewChange: (view: ApplicationResourcesViewType) => void; + argoBaseURL: string; +}; + +const ApplicationResourcesView: React.FC = ({ + application, + resources, + viewType, + onViewChange, + argoBaseURL, +}) => { + const columnSortConfig = React.useMemo( + () => + ['name', 'namespace', 'sync-wave', 'sync-status', 'health-status', 'actions'].map((key) => ({ + key, + })), + [], + ); + + const { sortBy, direction, getSortParams } = useGitOpsDataViewSort(columnSortConfig); + const columnsDV = useResourceColumnsDV(getSortParams); + const sortedResources = React.useMemo( + () => sortData(resources, sortBy, direction), + [resources, sortBy, direction], + ); + + const resourceFilters = React.useMemo(() => filters(sortedResources), [sortedResources]); + const [data, filteredResources, onFilterChange] = useListPageFilter( + sortedResources, + resourceFilters, + ); + + const isEmptyResources = filteredResources.length === 0; + const rows = useResourceRowsDV(filteredResources, application, argoBaseURL); + const isListView = viewType === ApplicationResourcesViewType.list; + + const empty = ( + + + + + + {t('There are no resources associated with the application.')} + + + + + + ); + + return ( +
+ + + + + + + + + + + + +
+ {isListView ? ( + + ) : ( +
+ +
+ )} +
+
+
+
+ ); +}; + +const sortData = ( + data: ApplicationResourceStatus[], + sortBy: string | undefined, + direction: 'asc' | 'desc' | undefined, +) => { + if (!sortBy || !direction) return data; + + return [...data].sort((a, b) => { + let aValue: any, bValue: any; + + switch (sortBy) { + case 'name': + aValue = a.name || ''; + bValue = b.name || ''; + break; + case 'namespace': + aValue = a.namespace || ''; + bValue = b.namespace || ''; + break; + case 'sync-wave': + aValue = a.syncWave || ''; + bValue = b.syncWave || ''; + break; + case 'sync-status': + aValue = a.status || ''; + bValue = b.status || ''; + break; + case 'health-status': + aValue = a.health?.status || ''; + bValue = b.health?.status || ''; + break; + default: + return 0; + } + + if (direction === 'asc') { + // eslint-disable-next-line no-nested-ternary + return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; + } else { + // eslint-disable-next-line no-nested-ternary + return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; + } + }); +}; + +export const useResourceColumnsDV = (getSortParams) => { + const columns: DataViewTh[] = [ + { + cell: t('Name'), + props: { + 'aria-label': 'name', + className: 'pf-m-width-25', + sort: getSortParams(0), + }, + }, + { + cell: t('Namespace'), + props: { + 'aria-label': 'namespace', + className: 'pf-m-width-20', + sort: getSortParams(1), + }, + }, + { + cell: t('Sync Wave'), + props: { + 'aria-label': 'sync wave', + className: 'pf-m-width-15', + sort: getSortParams(2), + }, + }, + { + cell: t('Sync Status'), + props: { + 'aria-label': 'sync status', + className: 'pf-m-width-15', + sort: getSortParams(3), + }, + }, + { + cell: t('Health Status'), + props: { + 'aria-label': 'health status', + className: 'pf-m-width-15', + sort: getSortParams(4), + }, + }, + { + cell: '', + props: { 'aria-label': 'actions' }, + }, + ]; + + return columns; +}; + +const useResourceRowsDV = ( + resources: ApplicationResourceStatus[], + obj: ApplicationKind, + argoBaseURL: string, +): DataViewTr[] => { + const rows: DataViewTr[] = []; + + resources.forEach((resource, index) => { + const gvk: K8sGroupVersionKind = { + version: resource.version, + group: resource.group, + kind: resource.kind, + }; + + rows.push([ + { + cell: ( +
+ +
+ ), + id: resource.name + '-' + index, + dataLabel: 'Name', + }, + { + cell: resource.namespace ? resource.namespace : '-', + id: resource.namespace, + dataLabel: 'Namespace', + }, + { + id: 'sync-wave-' + index, + cell: <>{resource.syncWave || '-'}, + dataLabel: 'Sync Order', + }, + { + id: 'sync-status-' + index, + cell: <>{resource.status ? : '-'}, + }, + { + id: 'health-status-' + index, + cell: ( + <> + {resource.health?.status && ( + + )} + {!resource.health?.status && '-'} + + ), + }, + { + id: 'actions-' + index, + cell: , + props: { style: { paddingTop: 8, paddingRight: 0, paddingLeft: 0, width: 10 } }, + }, + ]); + }); + return rows; +}; + +const filters = (resources: ApplicationResourceStatus[]): RowFilter[] => { + return [ + { + filterGroupName: t('Sync Status'), + type: 'resource-sync', + reducer: (resource) => (resource.status ? resource.status : t('No Sync Status')), + filter: (input, resource) => { + if (input.selected?.length) { + if (resource?.status) { + return input.selected.includes(resource.status); + } else { + return input.selected.includes(t('No Sync Status')); + } + } + return true; + }, + items: resources + .map((resource) => { + return { + id: resource.status ? resource.status : t('No Sync Status'), + title: resource.status ? resource.status : t('No Sync Status'), + }; + }) + .reduce(function (result: RowFilterItem[], resource: RowFilterItem) { + if (!result.some((item) => item.id === resource.id)) { + result.push(resource); + } + return result; + }, []), + }, + { + filterGroupName: t('Health Status'), + type: 'resource-health', + reducer: (resource) => (resource.health ? resource.health.status : t('None')), + filter: (input, resource) => { + if (input.selected?.length) { + if (resource?.health?.status) { + return input.selected.includes(resource.health.status); + } else if (input.selected.includes(t('None'))) { + return true; + } + return false; + } + return true; + }, + items: resources + .map((resource) => { + return { + id: resource.health && resource.health.status ? resource.health.status : t('None'), + title: resource.health && resource.health.status ? resource.health.status : t('None'), + }; + }) + .reduce(function (result: RowFilterItem[], resource: RowFilterItem) { + if (!result.some((item) => item.id === resource.id)) { + result.push(resource); + } + return result; + }, []), + }, + { + filterGroupName: t('Kind'), + type: 'resource-kind', + reducer: (resource) => resource.kind, + filter: (input, resource) => { + if (input.selected?.length) { + return input.selected.includes(resource.kind); + } else { + return true; + } + }, + items: resources + .map((resource) => { + return { id: resource.kind, title: resource.kind }; + }) + .reduce(function (result: RowFilterItem[], resource: RowFilterItem) { + if (!result.some((item) => item.id === resource.id)) { + result.push(resource); + } + return result; + }, []), + }, + ]; +}; + +export default ApplicationResourcesView; diff --git a/src/gitops/components/application/ApplicationResourcesViewType.ts b/src/gitops/components/application/ApplicationResourcesViewType.ts new file mode 100644 index 000000000..3c9eabc06 --- /dev/null +++ b/src/gitops/components/application/ApplicationResourcesViewType.ts @@ -0,0 +1,4 @@ +export { + APPLICATION_RESOURCES_VIEW_SETTING_KEY, + GitOpsViewType as ApplicationResourcesViewType, +} from '../shared/GitOpsViewType'; diff --git a/src/gitops/components/application/ApplicationSyncStatusTab.tsx b/src/gitops/components/application/ApplicationSyncStatusTab.tsx index 481a73f4b..8134dad0c 100644 --- a/src/gitops/components/application/ApplicationSyncStatusTab.tsx +++ b/src/gitops/components/application/ApplicationSyncStatusTab.tsx @@ -3,15 +3,12 @@ import { RouteComponentProps } from 'react-router'; import classNames from 'classnames'; import { useArgoServer } from '@gitops/hooks/useArgoServer'; -import { useResourceActionsProvider } from '@gitops/hooks/useResourceActionsProvider'; import { OperationState } from '@gitops/Statuses/OperationState'; import SyncStatus from '@gitops/Statuses/SyncStatus'; -import ActionDropDown from '@gitops/utils/components/ActionDropDown/ActionDropDown'; import { getApplicationArgoUrl, getDuration } from '@gitops/utils/gitops'; import { t } from '@gitops/utils/hooks/useGitOpsTranslation'; import { ApplicationKind, ApplicationResourceStatus } from '@gitops-models/ApplicationModel'; import { - Action, K8sGroupVersionKind, ResourceLink, Timestamp, @@ -38,6 +35,7 @@ import { Tbody, Td, ThProps, Tr } from '@patternfly/react-table'; import { DetailsDescriptionGroup } from '../shared/BaseDetailsSummary/BaseDetailsSummary'; import { GitOpsDataViewTable, useGitOpsDataViewSort } from '../shared/DataView'; +import ResourceActionsCell from '../shared/ResourceActionsCell/ResourceActionsCell'; import { ConditionsPopover } from './Conditions/ConditionsPopover'; @@ -402,7 +400,7 @@ const useResourceRowsDV = ( }, { id: 'actions-' + index, - cell: , + cell: , props: { style: { paddingTop: 8, paddingRight: 0, paddingLeft: 0, width: 10 }, className: 'dropdown-kebab-pf pf-c-table__action', @@ -413,22 +411,4 @@ const useResourceRowsDV = ( return rows; }; -const ResourceActionsCell: React.FC<{ - resource: ApplicationResourceStatus; - app: ApplicationKind; - argoBaseURL: string; -}> = ({ resource, app, argoBaseURL }) => { - const actionList: [actions: Action[]] = useResourceActionsProvider(resource, app, argoBaseURL); - - return ( -
- -
- ); -}; - export default ApplicationSyncStatusTab; diff --git a/src/gitops/components/application/graph/nodes/ApplicationNode.tsx b/src/gitops/components/application/graph/nodes/ApplicationNode.tsx index 2180cbf76..ce152fb6e 100644 --- a/src/gitops/components/application/graph/nodes/ApplicationNode.tsx +++ b/src/gitops/components/application/graph/nodes/ApplicationNode.tsx @@ -43,7 +43,7 @@ const ApplicationHealthStatusIcon = ({ status }: { status: HealthStatus }) => { let icon = null; switch (status) { case HealthStatus.HEALTHY: - icon = ; + icon = ; break; case HealthStatus.MISSING: icon = ; diff --git a/src/gitops/components/imageupdater/ImageUpdaterList.tsx b/src/gitops/components/imageupdater/ImageUpdaterList.tsx index bb3307ea1..9a183b0d2 100644 --- a/src/gitops/components/imageupdater/ImageUpdaterList.tsx +++ b/src/gitops/components/imageupdater/ImageUpdaterList.tsx @@ -402,7 +402,7 @@ export const useImageUpdaterRowsDV = ( }, { id: 'actions-' + index, - cell: , + cell: , props: { className: 'gitops-imageupdater-list__actions-cell' }, }, ]); @@ -412,13 +412,14 @@ export const useImageUpdaterRowsDV = ( const ImageUpdaterActionsCell: React.FC<{ imageUpdater: ImageUpdaterKind; -}> = ({ imageUpdater }) => { + index: number; +}> = ({ imageUpdater, index }) => { const actionList: Action[] = useImageUpdaterActionsProvider(imageUpdater); return (
diff --git a/src/gitops/components/project/ProjectList.tsx b/src/gitops/components/project/ProjectList.tsx index dbfcac004..3eddd7d6f 100644 --- a/src/gitops/components/project/ProjectList.tsx +++ b/src/gitops/components/project/ProjectList.tsx @@ -528,7 +528,7 @@ export const useProjectsRowsDV = ( }, { id: 'actions-' + index, - cell: , + cell: , props: { style: { paddingTop: 8, paddingRight: 0, paddingLeft: 0, width: 10 } }, }, ]); @@ -538,13 +538,14 @@ export const useProjectsRowsDV = ( const ProjectActionsCell: React.FC<{ project: AppProjectKind; -}> = ({ project }) => { + index: number; +}> = ({ project, index }) => { const actionList: Action[] = useProjectActionsProvider(project); return (
diff --git a/src/gitops/components/rollout/RolloutList.tsx b/src/gitops/components/rollout/RolloutList.tsx index 4f47f3b05..d83524c49 100644 --- a/src/gitops/components/rollout/RolloutList.tsx +++ b/src/gitops/components/rollout/RolloutList.tsx @@ -445,7 +445,7 @@ export const useRolloutsRowsDV = (rolloutsList, namespace): DataViewTr[] => { }, { id: 'actions-' + index, - cell: , + cell: , props: { style: { paddingTop: 8, paddingRight: 0, paddingLeft: 0, width: 10 } }, }, ]); @@ -455,13 +455,14 @@ export const useRolloutsRowsDV = (rolloutsList, namespace): DataViewTr[] => { const RolloutActionsCell: React.FC<{ app: RolloutKind; -}> = ({ app }) => { + index: number; +}> = ({ app, index }) => { const actionList: [actions: Action[]] = useRolloutActionsProvider(app); return (
diff --git a/src/gitops/components/rollout/components/PodList/PodList.tsx b/src/gitops/components/rollout/components/PodList/PodList.tsx index 28ae41064..3a4a3ef34 100644 --- a/src/gitops/components/rollout/components/PodList/PodList.tsx +++ b/src/gitops/components/rollout/components/PodList/PodList.tsx @@ -317,7 +317,7 @@ const usePodRowsDV = (podsList: PodKind[], memResults, cpuResults, namespace): D }, { id: 'actions-' + index, - cell: , + cell: , props: { className: 'dropdown-kebab-pf pf-c-table__action' }, }, ]); @@ -327,13 +327,14 @@ const usePodRowsDV = (podsList: PodKind[], memResults, cpuResults, namespace): D const PodRowActions: React.FC<{ pod: PodKind; -}> = ({ pod }) => { + index: number; +}> = ({ pod, index }) => { const actionList: [actions: Action[]] = usePodActionsProvider(pod); return (
diff --git a/src/gitops/components/shared/ApplicationList.tsx b/src/gitops/components/shared/ApplicationList.tsx index a13c9dd6a..9afa12f2d 100644 --- a/src/gitops/components/shared/ApplicationList.tsx +++ b/src/gitops/components/shared/ApplicationList.tsx @@ -37,12 +37,12 @@ import SyncStatusFragment from '../../Statuses/SyncStatus'; import ActionsDropdown from '../../utils/components/ActionDropDown/ActionDropDown'; import { isApplicationRefreshing } from '../../utils/gitops'; import { modelToGroupVersionKind, modelToRef } from '../../utils/utils'; -import { ApplicationSetGraphView } from '../appset/graph/ApplicationSetGraphView'; import { ShowOperandsInAllNamespacesRadioGroup, useShowOperandsInAllNamespaces, } from './AllNamespaces'; +import ApplicationSetApplicationsView from './ApplicationSetApplicationsView'; import { GitOpsDataViewTable, useGitOpsDataViewSort } from './DataView'; interface ApplicationProps { @@ -133,6 +133,7 @@ const ApplicationList: React.FC = ({ // TODO: use alternate filter since it is deprecated. See DataTableView potentially // PatternFly filters work on owned apps only (the dataset that will be displayed) const filters = getFilters(t); + // const filters = React.useMemo(() => getFilters(t), [t]); const [data, filteredData, onFilterChange] = useListPageFilter(ownedApps, filters); // Filter by search query if present (after other filters) @@ -231,33 +232,17 @@ const ApplicationList: React.FC = ({ {/* Show an AppSet specific title if showTitle is undefined. We don't want a duplicate title from above */} {appset && ( - {/* {showTitle == undefined && ( */} {t('ApplicationSet Applications')} - {/* )} */} {t( - "The graph and table views show the ApplicationSet's applications. Use the filter below the graph to filter applications based on their health and sync status.", + "The graph and table views show the ApplicationSet's applications. Use the filter to filter applications based on their health and sync status.", )} - - - )} - {!hideNameLabelFilters && hasOwnedApplications && ( + {!appset && !hideNameLabelFilters && hasOwnedApplications && ( = ({ nameFilterPlaceholder={t('plugin__gitops-plugin~Search by name...')} /> )} - + {appset && ( + + )} + {!appset && ( + + )} ); @@ -328,14 +335,14 @@ export const sortData = ( }); }; -const ApplicationActionsCell: React.FC<{ app: ApplicationKind }> = ({ app }) => { +const ApplicationActionsCell: React.FC<{ app: ApplicationKind, index: number }> = ({ app, index}) => { const actionList: [actions: Action[]] = useApplicationActionsProvider(app); return (
@@ -437,7 +444,7 @@ const useApplicationRowsDV = (applicationsList, namespace): DataViewTr[] => { }, { id: 'actions-' + index, - cell: , + cell: , props: { style: { paddingTop: 8, paddingRight: 0, paddingLeft: 0, width: 10 } }, }, ]); diff --git a/src/gitops/components/shared/ApplicationSetApplicationsView.tsx b/src/gitops/components/shared/ApplicationSetApplicationsView.tsx new file mode 100644 index 000000000..c3acb54dd --- /dev/null +++ b/src/gitops/components/shared/ApplicationSetApplicationsView.tsx @@ -0,0 +1,146 @@ +import * as React from 'react'; + +import { ApplicationKind } from '@gitops/models/ApplicationModel'; +import { ApplicationSetKind } from '@gitops/models/ApplicationSetModel'; +import { ListPageFilter, RowFilter, useUserSettings } from '@openshift-console/dynamic-plugin-sdk'; +import { Flex, FlexItem, Stack, StackItem } from '@patternfly/react-core'; +import { DataViewState } from '@patternfly/react-data-view/dist/esm/DataView'; +import { DataViewTh, DataViewTr } from '@patternfly/react-data-view/dist/esm/DataViewTable'; + +import { ApplicationSetGraphView } from '../appset/graph/ApplicationSetGraphView'; + +import { GitOpsDataViewTable } from './DataView'; +import GitOpsViewSwitcher from './GitOpsViewSwitcher'; +import { APPLICATION_SET_APPLICATIONS_VIEW_SETTING_KEY, GitOpsViewType } from './GitOpsViewType'; + +import './GitOpsGraphListView.scss'; + +type ApplicationSetApplicationsViewProps = { + applicationSet: ApplicationSetKind; + ownedApps: ApplicationKind[]; + filteredApplications: ApplicationKind[]; + hideNameLabelFilters?: boolean; + hasOwnedApplications: boolean; + rowFilters: RowFilter[]; + listPageFilterData: ApplicationKind[]; + onFilterChange: (type: string, value: { selected?: string[]; all?: string[] }) => void; + nameFilterPlaceholder: string; + loaded: boolean; + columns: DataViewTh[]; + rows: DataViewTr[]; + emptyState: React.ReactNode; + errorState?: React.ReactNode; + isError?: boolean; + isEmpty: boolean; +}; + +const ApplicationSetApplicationsView: React.FC = ({ + applicationSet, + ownedApps, + filteredApplications, + hideNameLabelFilters, + hasOwnedApplications, + rowFilters, + listPageFilterData, + onFilterChange, + nameFilterPlaceholder, + loaded, + columns, + rows, + emptyState, + errorState, + isError, + isEmpty, +}) => { + const [savedViewType, setSavedViewType, viewSettingsLoaded] = useUserSettings( + APPLICATION_SET_APPLICATIONS_VIEW_SETTING_KEY, + GitOpsViewType.graph, + false, + ); + const [viewType, setViewType] = React.useState(GitOpsViewType.graph); + + React.useEffect(() => { + if (viewSettingsLoaded) { + setViewType(savedViewType ?? GitOpsViewType.graph); + } + }, [savedViewType, viewSettingsLoaded]); + + const onViewChange = React.useCallback( + (newViewType: GitOpsViewType) => { + setViewType(newViewType); + setSavedViewType(newViewType); + }, + [setSavedViewType], + ); + + const isListView = viewType === GitOpsViewType.list; + + if (!viewSettingsLoaded) { + return null; + } + + return ( +
+ + + + + {!hideNameLabelFilters && hasOwnedApplications && ( + + )} + + + + + + + +
+ {isListView ? ( + + ) : ( +
+ +
+ )} +
+
+
+
+ ); +}; + +export default ApplicationSetApplicationsView; diff --git a/src/gitops/components/shared/ApplicationSetList.tsx b/src/gitops/components/shared/ApplicationSetList.tsx index 96f799663..0e74f387c 100644 --- a/src/gitops/components/shared/ApplicationSetList.tsx +++ b/src/gitops/components/shared/ApplicationSetList.tsx @@ -294,11 +294,11 @@ const ApplicationSetList: React.FC = ({ ); }; -const ApplicationSetActionsCell: React.FC<{ appSet: ApplicationSetKind }> = ({ appSet }) => { +const ApplicationSetActionsCell: React.FC<{ appSet: ApplicationSetKind, index: number }> = ({ appSet, index }) => { const [actions] = useApplicationSetActionsProvider(appSet); return (
- +
); }; @@ -353,7 +353,7 @@ const useApplicationSetRowsDV = ( }, { id: 'actions-' + index, - cell: , + cell: , props: { style: { paddingTop: 8, paddingRight: 0, paddingLeft: 0, width: 10 } }, }, ]); diff --git a/src/gitops/components/shared/GitOpsGraphListView.scss b/src/gitops/components/shared/GitOpsGraphListView.scss new file mode 100644 index 000000000..6d71a9ce6 --- /dev/null +++ b/src/gitops/components/shared/GitOpsGraphListView.scss @@ -0,0 +1,53 @@ +.gitops-graph-list-view { + display: flex; + flex-direction: column; + margin-top: var(--pf-t--global--spacer--md); + + &__content { + min-height: 0; + } + + &__panel, + &__list-panel { + min-height: 1000px; + } + + &__list-panel { + display: flex; + flex-direction: column; + } + + &__toolbar-row { + margin-bottom: var(--pf-t--global--spacer--sm); + } + + &__header { + display: flex; + justify-content: flex-end; + flex-shrink: 0; + padding-right: 30px; + } + + &__graph { + box-sizing: border-box; + width: 95%; + height: 1000px; + margin: 30px; + border: 1px solid gray; + display: flex; + flex-direction: column; + + > .gitops-topology-view { + flex: 1; + height: 100%; + min-height: 0; + } + + .gitops-topology-view, + .pf-topology-container, + .pf-topology-content { + height: 100%; + width: 100%; + } + } +} diff --git a/src/gitops/components/shared/GitOpsViewSwitcher.tsx b/src/gitops/components/shared/GitOpsViewSwitcher.tsx new file mode 100644 index 000000000..a09cc05d9 --- /dev/null +++ b/src/gitops/components/shared/GitOpsViewSwitcher.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; + +import { t } from '@gitops/utils/hooks/useGitOpsTranslation'; +import { Button, Icon, Tooltip } from '@patternfly/react-core'; +import { ListIcon, TopologyIcon } from '@patternfly/react-icons'; + +import { GitOpsViewType } from './GitOpsViewType'; + +type GitOpsViewSwitcherProps = { + viewType: GitOpsViewType; + onViewChange: (view: GitOpsViewType) => void; + isDisabled?: boolean; + testId?: string; +}; + +const GitOpsViewSwitcher: React.FC = ({ + viewType, + onViewChange, + isDisabled = false, + testId = 'gitops-view-switcher', +}) => { + const showGraphView = viewType === GitOpsViewType.graph; + const viewChangeTooltipContent = showGraphView + ? t('plugin__gitops-plugin~List view') + : t('plugin__gitops-plugin~Graph view'); + + return ( + +