diff --git a/src/gitops/components/application/ApplicationDetailsTab.tsx b/src/gitops/components/application/ApplicationDetailsTab.tsx index 6206eb541..84d39ea46 100644 --- a/src/gitops/components/application/ApplicationDetailsTab.tsx +++ b/src/gitops/components/application/ApplicationDetailsTab.tsx @@ -12,6 +12,7 @@ import HealthStatus from '@gitops/Statuses/HealthStatus'; import { OperationState } from '@gitops/Statuses/OperationState'; import SyncStatus from '@gitops/Statuses/SyncStatus'; import { ArgoServer, getArgoServer, getFriendlyClusterName } from '@gitops/utils/gitops'; +import { labelControllerNamespaceKey } from '@gitops/utils/gitops'; import { useGitOpsTranslation } from '@gitops/utils/hooks/useGitOpsTranslation'; import { useObjectModifyPermissions } from '@gitops/utils/utils'; import { k8sUpdate, ResourceLink, useK8sModel } from '@openshift-console/dynamic-plugin-sdk'; @@ -114,6 +115,15 @@ const ApplicationDetailsTab: React.FC = ({ obj }) => sources = []; revisions = []; } + + // Get the controller namespace for AppProject link + const getControllerNamespace = (): string => { + if (obj?.status?.controllerNamespace) return obj.status.controllerNamespace; + if (obj?.metadata?.labels?.[labelControllerNamespaceKey]) + return obj.metadata.labels[labelControllerNamespaceKey]; + return obj?.metadata?.namespace || ''; + }; + return (
= ({ obj }) => title={t('Project')} help={t('The Argo CD Project that this application belongs to.')} > - {/* TODO - Update to handle App in Any Namespace when controller namespace is in status */} void; + launchAnnotationsModal: () => void; + launchDeleteModal: () => void; + setGroupNodeStates: React.Dispatch>; +} + const customComponentFactory = ( hrefRef: React.MutableRefObject, - application: ApplicationKind, - navigate: NavigateFunction, - launchLabelsModal: () => void, - launchAnnotationsModal: () => void, - launchDeleteModal: () => void, - setGroupNodeStates: React.Dispatch>, + paramsRef: React.RefObject, ): ComponentFactory => (kind: ModelKind, type: string) => { const createContextMenuItems = ( @@ -132,11 +136,15 @@ const customComponentFactory = { + const params = paramsRef.current; + if (!params) { + return; + } if (label === t('View in Argo CD')) { window.open(hrefRef.current, '_blank'); } if (label === t('View Details')) { - navigate(graphElement.getData().resourcePath); + params.navigate(graphElement.getData().resourcePath); } }} > @@ -180,6 +188,18 @@ const customComponentFactory = { + const params = paramsRef.current; + if (!params) { + return; + } + const { + application, + navigate, + launchLabelsModal, + launchAnnotationsModal, + launchDeleteModal, + setGroupNodeStates, + } = params; if (label === t('View in Argo CD')) { window.open(hrefRef.current, '_blank'); } else if (label === t('Edit labels')) { @@ -339,22 +359,34 @@ export const ApplicationGraphView: React.FC<{ const launchDeleteModal = useDeleteModal(application); const navigate = useNavigate(); + const contextMenuParamsRef = React.useRef({ + application, + navigate, + launchLabelsModal, + launchAnnotationsModal, + launchDeleteModal, + setGroupNodeStates, + }); + + React.useEffect(() => { + contextMenuParamsRef.current = { + application, + navigate, + launchLabelsModal, + launchAnnotationsModal, + launchDeleteModal, + setGroupNodeStates, + }; + }, [application, navigate, launchLabelsModal, launchAnnotationsModal, launchDeleteModal]); + const controller = React.useMemo(() => { const newController = new Visualization(); newController.registerLayoutFactory(customLayoutFactory); - newController.registerComponentFactory( - customComponentFactory( - hrefRef, - application, - navigate, - launchLabelsModal, - launchAnnotationsModal, - launchDeleteModal, - setGroupNodeStates, - ), - ); + newController.registerComponentFactory(customComponentFactory(hrefRef, contextMenuParamsRef)); newController.addEventListener(SELECTION_EVENT, setSelectedIds); return newController; + // Controller is created once; context menu handlers read latest params via ref. + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const resourcePaths = React.useMemo(() => { diff --git a/src/gitops/components/appset/graph/ApplicationSetGraphView.tsx b/src/gitops/components/appset/graph/ApplicationSetGraphView.tsx index 3ed3e0cb0..f1cb78229 100644 --- a/src/gitops/components/appset/graph/ApplicationSetGraphView.tsx +++ b/src/gitops/components/appset/graph/ApplicationSetGraphView.tsx @@ -432,6 +432,8 @@ export const ApplicationSetGraphView: React.FC<{ }; newController.fromModel(modelWithLayout, false); return newController; + // Controller is recreated only when the ApplicationSet identity changes. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [applicationSetUid]); // Parts of the logic to refresh and update the graph is similar to application graph view. @@ -492,12 +494,11 @@ export const ApplicationSetGraphView: React.FC<{ prevViewType.current = expandGroups; } // Register new layout factory - if (applicationSet.status?.applicationStatus?.length > 0) { - adjustedExpansionSetting = treeViewLayout === TreeViewLayout.OWNER_REFERENCE_LAYOUT; - } else { - adjustedExpansionSetting = true; - } - controller.registerLayoutFactory(customLayoutFactory(adjustedExpansionSetting)); + const expansionSetting = + applicationSet.status?.applicationStatus?.length > 0 + ? treeViewLayout === TreeViewLayout.OWNER_REFERENCE_LAYOUT + : true; + controller.registerLayoutFactory(customLayoutFactory(expansionSetting)); const hasCollapsedStepGroups = collapsedStepGroupsRef.current.size > 0; @@ -538,7 +539,15 @@ export const ApplicationSetGraphView: React.FC<{ }); } } - }, [controller, nodes, initialEdges, treeViewLayout, applications, expandGroups]); + }, [ + controller, + nodes, + initialEdges, + treeViewLayout, + applications, + expandGroups, + applicationSet.status?.applicationStatus?.length, + ]); const previousNodeCountRef = React.useRef(0); const previousNodeIdsRef = React.useRef(''); @@ -732,6 +741,11 @@ export const ApplicationSetGraphView: React.FC<{ } } + const structuralNodes = React.useMemo( + () => nodes.filter((n) => n.type !== 'filler-node'), + [nodes], + ); + // Effect to handle initial collapse of step-groups after first render // This runs after Dagre has laid out the expanded graph, then collapses React.useEffect(() => { @@ -779,13 +793,7 @@ export const ApplicationSetGraphView: React.FC<{ graph.layout(); }); } - }, [ - controller, - nodes.filter((n) => n.type !== 'filler-node'), - treeViewLayout, - expandedStepGroups, - expandGroups, - ]); + }, [controller, structuralNodes, initialNodes, treeViewLayout, expandedStepGroups, expandGroups]); return ( = ({ obj }) const status = obj.status || {}; const readyCondition = status.conditions?.find((c) => c.type === 'Ready'); - const readyLabel = readyCondition - ? readyCondition.status === 'True' ? t('True') : t('False') - : '-'; + let readyLabel = '-'; + if (readyCondition) { + readyLabel = readyCondition.status === 'True' ? t('True') : t('False'); + } return ( <> @@ -81,22 +82,14 @@ const ImageUpdaterDetailsTab: React.FC = ({ obj }) title={t('Last Checked At')} help={t('When the controller last checked for image updates.')} > - {status.lastCheckedAt ? ( - - ) : ( - '-' - )} + {status.lastCheckedAt ? : '-'} - {status.lastUpdatedAt ? ( - - ) : ( - '-' - )} + {status.lastUpdatedAt ? : '-'} = ({ ].map((key) => ({ key })); }, [showNamespaceColumn]); - const {searchParams, sortBy, direction, getSortParams} = + const { searchParams, sortBy, direction, getSortParams } = useGitOpsDataViewSort(columnSortConfig); // Get search query from URL parameters const searchQuery = searchParams.get('q') || ''; - const {t} = useTranslation('plugin__gitops-plugin'); + const { t } = useTranslation('plugin__gitops-plugin'); const columnsDV = useColumnsDV(effectiveNamespace, getSortParams); const sortedItems = React.useMemo(() => { @@ -114,10 +121,12 @@ const ImageUpdaterList: React.FC = ({ if (searchQuery) { return ( <> - {t('No ImageUpdaters match the search filter')}{' '} - "{searchQuery}". -
- {t('Try removing the filter or searching for a different term to see more ImageUpdaters.')} + {t('No ImageUpdaters match the search filter')} "{searchQuery}" + . +
+ {t( + 'Try removing the filter or searching for a different term to see more ImageUpdaters.', + )} ); } @@ -133,9 +142,7 @@ const ImageUpdaterList: React.FC = ({ {getEmptyStateBody()} @@ -175,7 +182,7 @@ const ImageUpdaterList: React.FC = ({ } helpText={ location.pathname?.includes('openshift-gitops-operator') ? ( - + ) : null } hideFavoriteButton={false} @@ -264,7 +271,7 @@ export const useColumnsDV = ( ): DataViewTh[] => { const showNamespace = !namespace || namespace === ''; const i: number = showNamespace ? 1 : 0; - const {t} = useTranslation('plugin__gitops-plugin'); + const { t } = useTranslation('plugin__gitops-plugin'); const columns: DataViewTh[] = [ { cell: t('Name'), @@ -272,21 +279,21 @@ export const useColumnsDV = ( 'aria-label': 'name', className: 'pf-m-width-20', sort: getSortParams(0), - style: {minWidth: '200px'}, + style: { minWidth: '200px' }, }, }, ...(showNamespace ? [ - { - cell: t('Namespace'), - props: { - 'aria-label': 'namespace', - className: 'pf-m-width-15', - sort: getSortParams(1), - style: {minWidth: '150px'}, + { + cell: t('Namespace'), + props: { + 'aria-label': 'namespace', + className: 'pf-m-width-15', + sort: getSortParams(1), + style: { minWidth: '150px' }, + }, }, - }, - ] + ] : []), { cell: t('Apps'), @@ -322,7 +329,7 @@ export const useColumnsDV = ( }, { cell: '', - props: {'aria-label': 'actions'}, + props: { 'aria-label': 'actions' }, }, ]; @@ -359,16 +366,17 @@ export const useImageUpdaterRowsDV = ( }, ...(showNamespace ? [ - { - cell: , - id: obj.metadata.namespace, - dataLabel: 'Namespace', - }, - ] + { + cell: , + id: obj.metadata.namespace, + dataLabel: 'Namespace', + }, + ] : []), { id: 'apps', - cell: obj.status?.applicationsMatched != null ? String(obj.status.applicationsMatched) : '-', + cell: + obj.status?.applicationsMatched != null ? String(obj.status.applicationsMatched) : '-', dataLabel: 'Apps', }, { @@ -380,7 +388,7 @@ export const useImageUpdaterRowsDV = ( id: 'last-checked', cell: obj.status?.lastCheckedAt ? (
- +
) : ( '-' @@ -394,8 +402,8 @@ export const useImageUpdaterRowsDV = ( }, { id: 'actions-' + index, - cell: , - props: {className: 'gitops-imageupdater-list__actions-cell'}, + cell: , + props: { className: 'gitops-imageupdater-list__actions-cell' }, }, ]); }); @@ -404,7 +412,7 @@ export const useImageUpdaterRowsDV = ( const ImageUpdaterActionsCell: React.FC<{ imageUpdater: ImageUpdaterKind; -}> = ({imageUpdater}) => { +}> = ({ imageUpdater }) => { const actionList: Action[] = useImageUpdaterActionsProvider(imageUpdater); return (
@@ -435,8 +443,8 @@ const getFilters = (t: (key: string) => string): RowFilter[] => [ ); }, items: [ - {id: 'has-apps', title: t('Has Apps')}, - {id: 'no-apps', title: t('No Apps')}, + { id: 'has-apps', title: t('Has Apps') }, + { id: 'no-apps', title: t('No Apps') }, ], }, { @@ -460,8 +468,8 @@ const getFilters = (t: (key: string) => string): RowFilter[] => [ ); }, items: [ - {id: 'ready', title: t('Ready')}, - {id: 'not-ready', title: t('Not Ready')}, + { id: 'ready', title: t('Ready') }, + { id: 'not-ready', title: t('Not Ready') }, ], }, ]; diff --git a/src/gitops/components/imageupdater/ImageUpdaterNavPage.tsx b/src/gitops/components/imageupdater/ImageUpdaterNavPage.tsx index b5b194101..ab2a6f395 100644 --- a/src/gitops/components/imageupdater/ImageUpdaterNavPage.tsx +++ b/src/gitops/components/imageupdater/ImageUpdaterNavPage.tsx @@ -1,11 +1,10 @@ import * as React from 'react'; +import { modelToGroupVersionKind } from '@gitops/utils/utils'; import { HorizontalNav, useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; import { ErrorState } from '@patternfly/react-component-groups'; import { Bullseye, Spinner } from '@patternfly/react-core'; -import {modelToGroupVersionKind} from '@gitops/utils/utils'; - import { ImageUpdaterKind, ImageUpdaterModel } from '../../models/ImageUpdaterModel'; import { useGitOpsTranslation } from '../../utils/hooks/useGitOpsTranslation'; import DetailsPageHeader from '../shared/DetailsPageHeader/DetailsPageHeader'; diff --git a/src/gitops/components/imageupdater/ImageUpdaterRecentUpdatesTab.tsx b/src/gitops/components/imageupdater/ImageUpdaterRecentUpdatesTab.tsx index aee14ba65..f3c5c686a 100644 --- a/src/gitops/components/imageupdater/ImageUpdaterRecentUpdatesTab.tsx +++ b/src/gitops/components/imageupdater/ImageUpdaterRecentUpdatesTab.tsx @@ -2,7 +2,13 @@ import * as React from 'react'; import { RouteComponentProps } from 'react-router'; import { Timestamp } from '@openshift-console/dynamic-plugin-sdk'; -import { EmptyState, EmptyStateBody, PageSection, PageSectionVariants, Title } from '@patternfly/react-core'; +import { + EmptyState, + EmptyStateBody, + PageSection, + PageSectionVariants, + Title, +} from '@patternfly/react-core'; import { DataViewTh, DataViewTr } from '@patternfly/react-data-view/dist/esm/DataViewTable'; import { CubesIcon } from '@patternfly/react-icons'; import { Tbody, Td, ThProps, Tr } from '@patternfly/react-table'; @@ -16,8 +22,6 @@ type ImageUpdaterRecentUpdatesTabProps = RouteComponentProps<{ ns: string; name: }; const ImageUpdaterRecentUpdatesTab: React.FC = ({ obj }) => { - if (!obj) return null; - const { t } = useGitOpsTranslation(); const columnSortConfig = React.useMemo( @@ -32,15 +36,17 @@ const ImageUpdaterRecentUpdatesTab: React.FC const columnsDV = useColumnsDV(getSortParams, t); - const recentUpdates: ImageUpdaterRecentUpdate[] = obj?.status?.recentUpdates || []; - - const sortedUpdates = React.useMemo( - () => sortData(recentUpdates, sortBy, direction), - [recentUpdates, sortBy, direction], - ); + const sortedUpdates = React.useMemo(() => { + const updates = obj?.status?.recentUpdates || []; + return sortData(updates, sortBy, direction); + }, [obj, sortBy, direction]); const rows = useRowsDV(sortedUpdates); + if (!obj) { + return null; + } + const empty = ( @@ -188,10 +194,13 @@ const sortData = ( } if (direction === 'asc') { - return aValue < bValue ? -1 : aValue > bValue ? 1 : 0; - } else { - return aValue > bValue ? -1 : aValue < bValue ? 1 : 0; + if (aValue < bValue) return -1; + if (aValue > bValue) return 1; + return 0; } + if (aValue > bValue) return -1; + if (aValue < bValue) return 1; + return 0; }); }; diff --git a/src/gitops/components/project/ProjectList.tsx b/src/gitops/components/project/ProjectList.tsx index 65cd33687..e92a6b134 100644 --- a/src/gitops/components/project/ProjectList.tsx +++ b/src/gitops/components/project/ProjectList.tsx @@ -134,6 +134,7 @@ const ProjectList: React.FC = ({ }, [filteredData, searchQuery]); const rows = useProjectsRowsDV(filteredBySearch, namespace, applications, appsLoaded); + const showNamespaceColumn = !namespace || namespace === ''; // Check if there are projects initially (before search) const hasProjects = React.useMemo(() => { @@ -225,14 +226,22 @@ const ProjectList: React.FC = ({ nameFilterPlaceholder={t('Search by name...')} /> )} - +
+ +
); @@ -319,6 +328,17 @@ export const sortData = ( }); }; +const sortableHeaderProps = ( + ariaLabel: string, + className: string, + sort: ThProps['sort'], +): ThProps => ({ + 'aria-label': ariaLabel, + className, + sort, + tooltip: '', +}); + export const useColumnsDV = ( namespace: string | undefined, getSortParams: (columnIndex: number) => ThProps['sort'], @@ -329,56 +349,38 @@ export const useColumnsDV = ( const columns: DataViewTh[] = [ { cell: t('Name'), - props: { - 'aria-label': 'name', - className: 'pf-m-width-25', - sort: getSortParams(0), - style: { minWidth: '200px' }, - }, + props: sortableHeaderProps('name', 'pf-m-width-20', getSortParams(0)), }, ...(showNamespace ? [ { cell: t('Namespace'), - props: { - 'aria-label': 'namespace', - className: 'pf-m-width-15', - sort: getSortParams(1), - style: { minWidth: '150px' }, - }, + props: sortableHeaderProps('namespace', 'pf-m-width-15', getSortParams(1)), }, ] : []), { cell: t('Description'), - props: { - 'aria-label': 'description', - className: 'pf-m-width-25', - sort: getSortParams(1 + i), - }, + props: sortableHeaderProps('description', 'pf-m-width-10', getSortParams(1 + i)), }, { cell: t('Applications'), - props: { - 'aria-label': 'applications', - className: 'pf-m-width-15', - sort: getSortParams(2 + i), - }, + props: sortableHeaderProps('applications', 'pf-m-width-15', getSortParams(2 + i)), }, { cell: t('Labels'), props: { 'aria-label': 'labels', - className: 'pf-m-width-15', + className: 'pf-m-width-20', }, }, { cell: t('Last Updated'), - props: { - 'aria-label': 'last updated', - className: 'pf-m-width-15', - sort: getSortParams(showNamespace ? 5 : 4), - }, + props: sortableHeaderProps( + 'last updated', + 'pf-m-width-15', + getSortParams(showNamespace ? 5 : 4), + ), }, { cell: '', @@ -406,14 +408,12 @@ export const useProjectsRowsDV = ( rows.push([ { cell: ( -
- -
+ ), id: 'name', dataLabel: 'Name', @@ -429,16 +429,25 @@ export const useProjectsRowsDV = ( : []), { id: 'description', - cell: obj.spec?.description || '-', + dataLabel: 'Description', + cell: ( + {obj.spec?.description || '-'} + ), }, { id: 'applications', - cell: appsLoaded ? appsCount.toString() : '-', + dataLabel: 'Applications', + cell: ( + + {appsLoaded ? appsCount.toString() : '-'} + + ), }, { id: 'labels', + dataLabel: 'Labels', cell: ( -
+
), diff --git a/src/gitops/components/project/ProjectRolesTab.tsx b/src/gitops/components/project/ProjectRolesTab.tsx index 751af8ab7..7246291b3 100644 --- a/src/gitops/components/project/ProjectRolesTab.tsx +++ b/src/gitops/components/project/ProjectRolesTab.tsx @@ -254,7 +254,7 @@ const useRolesRowsDV = (roles: Role[], t: (key: string) => string): DataViewTr[] roles.forEach((role, index) => { rows.push([ { - cell: {role.name}, + cell: role.name || '-', id: `name-${index}`, dataLabel: t('Name'), }, diff --git a/src/gitops/components/project/project-list.scss b/src/gitops/components/project/project-list.scss index 26cc7b70f..bb39eb9d5 100644 --- a/src/gitops/components/project/project-list.scss +++ b/src/gitops/components/project/project-list.scss @@ -1,22 +1,245 @@ -// Prevent vertical text wrapping in Name and Namespace columns -.pf-c-table tbody td[data-label='Name'], -.pf-c-table tbody td[data-label='Namespace'] { - white-space: nowrap !important; - overflow: hidden; - text-overflow: ellipsis; - - a, - .co-resource-item { +// PF6 DataViewTh applies modifier="truncate" on headers. With table-layout: fixed, truncation +// must happen on the sort-button label text so TableText can detect overflow and show tooltips. +@mixin gitops-project-list-sortable-header-tooltip { + .pf-v6-c-table__button, + .pf-c-table__button { + width: 100%; + max-width: 100%; + min-width: 0; + } + + .pf-v6-c-table__button-content, + .pf-c-table__button-content { + width: 100%; + max-width: 100%; + min-width: 0; + } + + .pf-v6-c-table__text, + .pf-c-table__text { + display: block; overflow: hidden; text-overflow: ellipsis; - display: inline-block; + white-space: nowrap; + min-width: 0; max-width: 100%; } } -// Description column text truncation -.pf-c-table tbody td[data-label='Description'] { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; +@mixin gitops-project-list-full-header { + --pf-v6-c-table--cell--MaxWidth: none; + --pf-v6-c-table--m-truncate--cell--MaxWidth: none; + --pf-v6-c-table--cell--Overflow: visible; + --pf-v6-c-table--cell--TextOverflow: clip; + --pf-v6-c-table--cell--WhiteSpace: nowrap; + max-width: none !important; + overflow: visible !important; + + .pf-v6-c-table__button, + .pf-v6-c-table__button-content, + .pf-v6-c-table__text, + .pf-c-table__button, + .pf-c-table__text { + overflow: visible; + text-overflow: clip; + max-width: none; + width: auto; + } +} + +.gitops-project-list { + .pf-v6-c-table, + .pf-c-table { + table-layout: fixed; + width: 100%; + } + + td { + vertical-align: top; + } + + // All-projects view: Name, Namespace, Description, Applications, Labels, Last Updated, Actions + &--with-namespace { + th:nth-child(1), + td:nth-child(1) { + width: 20%; + min-width: 14rem; + } + + th:nth-child(2), + td:nth-child(2) { + width: 14%; + min-width: 9.5rem; + white-space: nowrap; + } + + th:nth-child(3), + td:nth-child(3) { + width: 12%; + min-width: 8rem; + } + + th:nth-child(4), + td:nth-child(4) { + width: 12%; + white-space: nowrap; + } + + td:nth-child(4) { + min-width: 3rem; + } + + th:nth-child(5), + td:nth-child(5) { + width: 18%; + overflow: hidden; + } + + th:nth-child(6), + td:nth-child(6) { + width: 14%; + white-space: nowrap; + } + + th:nth-child(7), + td:nth-child(7) { + width: 4%; + white-space: nowrap; + } + + // Name and description wrap instead of ellipsis truncation. + td:nth-child(1), + td:nth-child(3) { + white-space: normal; + word-break: normal; + overflow-wrap: anywhere; + } + + td:nth-child(1) { + .co-resource-item { + display: inline-flex; + align-items: flex-start; + max-width: 100%; + min-width: 0; + } + + .co-resource-item__resource-name { + white-space: normal; + word-break: normal; + overflow-wrap: anywhere; + min-width: 0; + flex: 1; + } + } + + th:nth-child(1), + th:nth-child(2), + th:nth-child(3) { + @include gitops-project-list-sortable-header-tooltip; + } + + th:nth-child(4) { + min-width: 11rem; + @include gitops-project-list-full-header; + } + + th:nth-child(6) { + min-width: 11.5rem; + @include gitops-project-list-full-header; + } + } + + &__applications-count { + display: inline-block; + min-width: 1.5rem; + text-align: right; + } + + &__description { + white-space: normal; + word-break: normal; + overflow-wrap: anywhere; + } + + // Single-namespace view: Name, Description, Applications, Labels, Last Updated, Actions + &:not(.gitops-project-list--with-namespace) { + th:nth-child(1), + td:nth-child(1) { + width: 22%; + min-width: 14rem; + } + + th:nth-child(2), + td:nth-child(2) { + width: 14%; + min-width: 8rem; + } + + th:nth-child(3), + td:nth-child(3) { + width: 11%; + white-space: nowrap; + } + + td:nth-child(3) { + min-width: 3rem; + } + + th:nth-child(4), + td:nth-child(4) { + width: 22%; + overflow: hidden; + } + + th:nth-child(5), + td:nth-child(5) { + width: 16%; + white-space: nowrap; + } + + th:nth-child(6), + td:nth-child(6) { + width: 4%; + white-space: nowrap; + } + + td:nth-child(1), + td:nth-child(2) { + white-space: normal; + word-break: normal; + overflow-wrap: anywhere; + } + + td:nth-child(1) { + .co-resource-item { + display: inline-flex; + align-items: flex-start; + max-width: 100%; + min-width: 0; + } + + .co-resource-item__resource-name { + white-space: normal; + word-break: normal; + overflow-wrap: anywhere; + min-width: 0; + flex: 1; + } + } + + th:nth-child(1), + th:nth-child(2) { + @include gitops-project-list-sortable-header-tooltip; + } + + th:nth-child(3) { + min-width: 11rem; + @include gitops-project-list-full-header; + } + + th:nth-child(5) { + min-width: 11.5rem; + @include gitops-project-list-full-header; + } + } } diff --git a/src/gitops/components/rollout/RolloutList.tsx b/src/gitops/components/rollout/RolloutList.tsx index bece10b6e..4f47f3b05 100644 --- a/src/gitops/components/rollout/RolloutList.tsx +++ b/src/gitops/components/rollout/RolloutList.tsx @@ -1,22 +1,15 @@ import * as React from 'react'; import { Link } from 'react-router-dom-v5-compat'; -import classNames from 'classnames'; import TechPreviewBadge from 'src/plugin/import/badges/TechPreviewBadge'; import { AppProjectKind } from '@gitops/models/AppProjectModel'; import ActionsDropdown from '@gitops/utils/components/ActionDropDown/ActionDropDown'; import { isApplicationRefreshing } from '@gitops/utils/gitops'; import { useGitOpsTranslation } from '@gitops/utils/hooks/useGitOpsTranslation'; -import { - getSelectorSearchURL, - kindForReference, - modelToGroupVersionKind, - modelToRef, -} from '@gitops/utils/utils'; +import { getSelectorSearchURL, modelToGroupVersionKind, modelToRef } from '@gitops/utils/utils'; import { Action, K8sResourceCommon, - K8sResourceKindReference, ListPageBody, ListPageCreate, ListPageFilter, @@ -28,8 +21,7 @@ import { } from '@openshift-console/dynamic-plugin-sdk'; import { ResourceLink } from '@openshift-console/dynamic-plugin-sdk'; import { ErrorState } from '@patternfly/react-component-groups'; -import { EmptyState, EmptyStateBody, LabelGroup, Spinner } from '@patternfly/react-core'; -import { Label as PfLabel } from '@patternfly/react-core'; +import { EmptyState, EmptyStateBody, Spinner } from '@patternfly/react-core'; import { DataViewTh, DataViewTr } from '@patternfly/react-data-view/dist/esm/DataViewTable'; import { CubesIcon, SearchIcon } from '@patternfly/react-icons'; import { Tbody, Td, ThProps, Tr } from '@patternfly/react-table'; @@ -39,6 +31,7 @@ import { useShowOperandsInAllNamespaces, } from '../shared/AllNamespaces'; import { GitOpsDataViewTable, useGitOpsDataViewSort } from '../shared/DataView'; +import { MetadataLabels } from '../shared/MetadataLabels/MetadataLabels'; import { useRolloutActionsProvider } from './hooks/useRolloutActionsProvider'; import { RolloutKind, RolloutModel } from './model/RolloutModel'; @@ -357,53 +350,6 @@ export const useColumnsDV = ( return columns; }; -type MetadataLabelsProps = { - kind: K8sResourceKindReference; - labels?: { [key: string]: string }; -}; - -const MetadataLabels: React.FC = ({ kind, labels }) => { - const { t } = useGitOpsTranslation(); - return labels && Object.keys(labels).length > 0 ? ( - - {Object.keys(labels || {})?.map((key) => { - return ( - - {labels[key] ? `${key}=${labels[key]}` : key} - - ); - })} - - ) : ( - {t('No labels')} - ); -}; - -type LabelProps = { - kind: K8sResourceKindReference; - name: string; - value: string; - expand: boolean; -}; - -const LabelL: React.SFC = ({ kind, name, value, expand }) => { - const selector = value ? `${name}=${value}` : name; - const href = getSelectorSearchURL('', kind, selector); - const kindOf = `co-m-${kindForReference(kind.toLowerCase())}`; - const klass = classNames(kindOf, { 'co-m-expand': expand }, 'co-label'); - return ( - <> - - - {name} - - {value && =} - {value && {value}} - - - ); -}; - export const useRolloutsRowsDV = (rolloutsList, namespace): DataViewTr[] => { const rows: DataViewTr[] = []; if (rolloutsList == undefined || rolloutsList.length == 0) { @@ -455,10 +401,11 @@ export const useRolloutsRowsDV = (rolloutsList, namespace): DataViewTr[] => { { id: 'labels', cell: ( -
+
), diff --git a/src/gitops/components/shared/ApplicationList.tsx b/src/gitops/components/shared/ApplicationList.tsx index 787ddc6fd..a13c9dd6a 100644 --- a/src/gitops/components/shared/ApplicationList.tsx +++ b/src/gitops/components/shared/ApplicationList.tsx @@ -25,6 +25,7 @@ import { useApplicationActionsProvider } from '../..//hooks/useApplicationAction import RevisionFragment from '../..//Revision/Revision'; import HealthStatusFragment from '../..//Statuses/HealthStatus'; import { HealthStatus, SyncStatus } from '../..//utils/constants'; +import { labelControllerNamespaceKey } from '../..//utils/gitops'; import { ApplicationKind, ApplicationModel, @@ -424,6 +425,11 @@ const useApplicationRowsDV = (applicationsList, namespace): DataViewTr[] => { id: app.spec?.project, cell: app.spec?.project && ( diff --git a/src/gitops/components/shared/MetadataLabels/MetadataLabels.tsx b/src/gitops/components/shared/MetadataLabels/MetadataLabels.tsx index 016b8ec02..183c717a2 100644 --- a/src/gitops/components/shared/MetadataLabels/MetadataLabels.tsx +++ b/src/gitops/components/shared/MetadataLabels/MetadataLabels.tsx @@ -35,12 +35,13 @@ const LabelL: React.FC = ({ kind, name, value, expand }) => { type MetadataLabelsProps = { kind: K8sResourceKindReference; labels?: { [key: string]: string }; + numLabels?: number; }; -export const MetadataLabels: React.FC = ({ kind, labels }) => { +export const MetadataLabels: React.FC = ({ kind, labels, numLabels = 10 }) => { const { t } = useGitOpsTranslation(); return labels && Object.keys(labels).length > 0 ? ( - + {Object.keys(labels || {})?.map((key) => { return ( diff --git a/src/gitops/models/ImageUpdaterModel.ts b/src/gitops/models/ImageUpdaterModel.ts index 292147933..8d15430be 100644 --- a/src/gitops/models/ImageUpdaterModel.ts +++ b/src/gitops/models/ImageUpdaterModel.ts @@ -1,8 +1,8 @@ +import { modelToRef } from 'src/gitops/utils/utils'; + import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk'; import { K8sModel } from '@openshift-console/dynamic-plugin-sdk/lib/api/common-types'; -import { modelToRef } from 'src/gitops/utils/utils'; - export type ImageUpdaterCondition = { lastTransitionTime: string; message: string;