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 */} = ({ }, [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 (