Skip to content

Commit b858b0b

Browse files
authored
Merge pull request #107 from keithchong/7713-AddDetailsHeaderAndYamlCommonComponents
Add shared YAML tab and Details Header, and Base Resource Details Properties (#7713)
2 parents ef7e1ff + ef4d137 commit b858b0b

File tree

11 files changed

+788
-35
lines changed

11 files changed

+788
-35
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
import * as React from 'react';
2+
import classNames from 'classnames';
3+
4+
import { OwnerReferences } from '@gitops/utils/components/OwnerReferences/owner-references';
5+
import { useGitOpsTranslation } from '@gitops/utils/hooks/useGitOpsTranslation';
6+
import { kindForReference, useObjectModifyPermissions } from '@gitops/utils/utils';
7+
import {
8+
K8sModel,
9+
K8sResourceKind,
10+
K8sResourceKindReference,
11+
ResourceLink,
12+
Timestamp,
13+
useAnnotationsModal,
14+
useLabelsModal,
15+
} from '@openshift-console/dynamic-plugin-sdk';
16+
import {
17+
Button,
18+
DescriptionList,
19+
DescriptionListDescription,
20+
DescriptionListGroup,
21+
DescriptionListTermHelpText,
22+
DescriptionListTermHelpTextButton,
23+
Flex,
24+
FlexItem,
25+
LabelGroup,
26+
Popover,
27+
Split,
28+
SplitItem,
29+
} from '@patternfly/react-core';
30+
import { Label as PfLabel } from '@patternfly/react-core';
31+
import { PencilAltIcon } from '@patternfly/react-icons';
32+
33+
type DetailsDescriptionGroupProps = {
34+
title: string;
35+
help: string;
36+
};
37+
38+
export const DetailsDescriptionGroup = (
39+
props: React.PropsWithChildren<DetailsDescriptionGroupProps>,
40+
) => {
41+
return (
42+
<DescriptionListGroup className="pf-c-description-list__group">
43+
<DescriptionListTermHelpText className="pf-c-description-list__term">
44+
<Popover headerContent={<div>{props.title}</div>} bodyContent={<div>{props.help}</div>}>
45+
<DescriptionListTermHelpTextButton>{props.title}</DescriptionListTermHelpTextButton>
46+
</Popover>
47+
</DescriptionListTermHelpText>
48+
<DescriptionListDescription>{props.children}</DescriptionListDescription>
49+
</DescriptionListGroup>
50+
);
51+
};
52+
53+
type LabelProps = {
54+
kind: K8sResourceKindReference;
55+
name: string;
56+
value: string;
57+
expand: boolean;
58+
};
59+
60+
const LabelL: React.SFC<LabelProps> = ({ kind, name, value, expand }) => {
61+
const href = `/search?kind=${kind}&q=${value ? encodeURIComponent(`${name}=${value}`) : name}`;
62+
const kindOf = `co-m-${kindForReference(kind.toLowerCase())}`;
63+
const klass = classNames(kindOf, { 'co-m-expand': expand }, 'co-label');
64+
return (
65+
<>
66+
<PfLabel className={klass} color={'blue'} href={href}>
67+
<span className="co-label__key" data-test="label-key">
68+
{name}
69+
</span>
70+
{value && <span className="co-label__eq">=</span>}
71+
{value && <span className="co-label__value">{value}</span>}
72+
</PfLabel>
73+
</>
74+
);
75+
};
76+
77+
type MetadataLabelsProps = {
78+
kind: K8sResourceKindReference;
79+
labels?: { [key: string]: string };
80+
};
81+
82+
const MetadataLabels: React.FC<MetadataLabelsProps> = ({ kind, labels }) => {
83+
return labels && Object.keys(labels).length > 0 ? (
84+
<LabelGroup numLabels={10} className="co-label-group metadata-labels-group">
85+
{Object.keys(labels || {})?.map((key) => {
86+
return (
87+
<LabelL key={key} kind={kind} name={key} value={labels[key]} expand={true}>
88+
{labels[key] ? `${key}=${labels[key]}` : key}
89+
</LabelL>
90+
);
91+
})}
92+
</LabelGroup>
93+
) : (
94+
<span className="metadata-labels-no-labels">No labels</span>
95+
);
96+
};
97+
98+
export const BaseDetailsSummary: React.FC<BaseDetailsSummaryProps> = ({ obj, model, nameLink }) => {
99+
const { t } = useGitOpsTranslation();
100+
const [canPatch, canUpdate] = useObjectModifyPermissions(obj, model);
101+
const launchLabelsModal = useLabelsModal(obj);
102+
const launchAnnotationsModal = useAnnotationsModal(obj);
103+
104+
return (
105+
<>
106+
<DescriptionList className="pf-c-description-list">
107+
<DescriptionListGroup className="pf-c-description-list__group">
108+
<DescriptionListTermHelpText className="pf-c-description-list__term">
109+
<Popover
110+
headerContent={<div>{t('Name')}</div>}
111+
bodyContent={<div>{t('Name must be unique within a namespace.')}</div>}
112+
>
113+
<DescriptionListTermHelpTextButton>{t('Name')}</DescriptionListTermHelpTextButton>
114+
</Popover>
115+
</DescriptionListTermHelpText>
116+
<DescriptionListDescription>
117+
<Flex>
118+
<FlexItem>{obj?.metadata?.name}</FlexItem>
119+
{nameLink && <FlexItem>{nameLink}</FlexItem>}
120+
</Flex>
121+
</DescriptionListDescription>
122+
</DescriptionListGroup>
123+
<DetailsDescriptionGroup
124+
title={t('Namespace')}
125+
help={t('Namespace defines the space within which each name must be unique.')}
126+
>
127+
<ResourceLink kind="Namespace" name={obj?.metadata?.namespace} />
128+
</DetailsDescriptionGroup>
129+
<DescriptionListGroup className="pf-c-description-list__group">
130+
<Split>
131+
<SplitItem isFilled>
132+
<DescriptionListTermHelpText className="pf-c-description-list__term">
133+
<Popover
134+
headerContent={<div>{t('Labels')}</div>}
135+
bodyContent={
136+
<div>
137+
{t(
138+
'Map of string keys and values that can be used to organize and categorize (scope and select) objects.',
139+
)}
140+
</div>
141+
}
142+
>
143+
<DescriptionListTermHelpTextButton>
144+
{t('Labels')}
145+
</DescriptionListTermHelpTextButton>
146+
</Popover>
147+
</DescriptionListTermHelpText>
148+
</SplitItem>
149+
<SplitItem>
150+
<Button
151+
variant="link"
152+
isInline
153+
isDisabled={!canPatch}
154+
icon={<PencilAltIcon />}
155+
iconPosition={'right'}
156+
onClick={launchLabelsModal}
157+
>
158+
{t(' Edit')}
159+
</Button>
160+
</SplitItem>
161+
</Split>
162+
<DescriptionListDescription
163+
className={classNames('valueClassName', {
164+
'co-editable-label-group': canPatch || canUpdate,
165+
})}
166+
>
167+
<MetadataLabels
168+
kind={model.apiGroup + '~' + model.apiVersion + '~' + model.kind}
169+
labels={obj?.metadata?.labels}
170+
/>
171+
</DescriptionListDescription>
172+
</DescriptionListGroup>
173+
174+
<DescriptionListGroup className="pf-c-description-list__group">
175+
<DescriptionListTermHelpText className="pf-c-description-list__term">
176+
<Popover
177+
headerContent={<div>{t('Annotations')}</div>}
178+
bodyContent={
179+
<div>
180+
{t(
181+
'Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects.',
182+
)}
183+
</div>
184+
}
185+
>
186+
<DescriptionListTermHelpTextButton>
187+
{t('Annotations')}
188+
</DescriptionListTermHelpTextButton>
189+
</Popover>
190+
</DescriptionListTermHelpText>
191+
<DescriptionListDescription>
192+
<div>
193+
<Button
194+
variant="link"
195+
isInline
196+
isDisabled={!canPatch}
197+
icon={<PencilAltIcon />}
198+
iconPosition={'right'}
199+
onClick={launchAnnotationsModal}
200+
>
201+
{(obj.metadata?.annotations ? Object.keys(obj.metadata.annotations).length : 0) +
202+
t(' Annotations')}
203+
</Button>
204+
</div>
205+
</DescriptionListDescription>
206+
</DescriptionListGroup>
207+
208+
<DetailsDescriptionGroup
209+
title={t('Created at')}
210+
help={t(
211+
'Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.',
212+
)}
213+
>
214+
<Timestamp timestamp={obj?.metadata?.creationTimestamp} />
215+
</DetailsDescriptionGroup>
216+
<DetailsDescriptionGroup
217+
title={t('Owner')}
218+
help={t(
219+
'Time is a wrapper around time. Time which supports correct marshaling to YAML and JSON.',
220+
)}
221+
>
222+
<OwnerReferences resource={obj} />
223+
</DetailsDescriptionGroup>
224+
</DescriptionList>
225+
</>
226+
);
227+
};
228+
229+
export type BaseDetailsSummaryProps = {
230+
obj: K8sResourceKind;
231+
model: K8sModel;
232+
nameLink?: React.ReactNode;
233+
};
234+
235+
export default BaseDetailsSummary;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import * as React from 'react';
2+
import { Link } from 'react-router-dom-v5-compat';
3+
import DevPreviewBadge from 'src/components/import/badges/DevPreviewBadge';
4+
5+
import FavoriteButton from '@gitops/components/shared/FavoriteButton/FavoriteButton';
6+
import ActionsDropdown from '@gitops/utils/components/ActionDropDown/ActionDropDown';
7+
import { DEFAULT_NAMESPACE } from '@gitops/utils/constants';
8+
import { isApplicationRefreshing } from '@gitops/utils/gitops';
9+
import { useGitOpsTranslation } from '@gitops/utils/hooks/useGitOpsTranslation';
10+
import { Action, K8sModel, K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk';
11+
import {
12+
ActionList,
13+
ActionListGroup,
14+
ActionListItem,
15+
Breadcrumb,
16+
BreadcrumbItem,
17+
Flex,
18+
PageBreadcrumb,
19+
PageGroup,
20+
PageSection,
21+
Spinner,
22+
Title,
23+
} from '@patternfly/react-core';
24+
25+
import './details-page-header.scss';
26+
27+
const PaneHeading: React.FC = ({ children }) => (
28+
<Flex
29+
alignItems={{ default: 'alignItemsCenter' }}
30+
justifyContent={{ default: 'justifyContentSpaceBetween' }}
31+
>
32+
{children}
33+
</Flex>
34+
);
35+
36+
type DetailsPageTitleProps = {
37+
breadcrumb: React.ReactNode;
38+
};
39+
40+
const DetailsPageTitle: React.FC<DetailsPageTitleProps> = ({ breadcrumb, children }) => (
41+
<div>
42+
<PageGroup>
43+
<PageBreadcrumb>{breadcrumb}</PageBreadcrumb>
44+
<PageSection className="details-page-title" hasBodyWrapper={false}>
45+
{children}
46+
</PageSection>
47+
</PageGroup>
48+
</div>
49+
);
50+
51+
type DetailsPageHeaderProps = {
52+
obj: K8sResourceCommon;
53+
model: K8sModel;
54+
name: string;
55+
namespace: string;
56+
actions: Action[];
57+
iconText: string;
58+
iconTitle: string;
59+
};
60+
61+
const DetailsPageHeader: React.FC<DetailsPageHeaderProps> = ({
62+
obj,
63+
model,
64+
name,
65+
namespace,
66+
actions,
67+
iconText,
68+
iconTitle,
69+
}) => {
70+
const { t } = useGitOpsTranslation();
71+
return (
72+
<>
73+
<div>
74+
<DetailsPageTitle
75+
breadcrumb={
76+
<Breadcrumb className="pf-c-breadcrumb co-breadcrumb">
77+
<BreadcrumbItem>
78+
<Link
79+
to={`/k8s/ns/${namespace || DEFAULT_NAMESPACE}/${
80+
model.apiGroup + '~' + model.apiVersion + '~' + model.kind
81+
}`}
82+
>
83+
Argo CD {t(model.labelPlural)}
84+
</Link>
85+
</BreadcrumbItem>
86+
<BreadcrumbItem>Argo CD {t(model.labelPlural + ' details')}</BreadcrumbItem>
87+
</Breadcrumb>
88+
}
89+
>
90+
<PaneHeading>
91+
<Title headingLevel="h1" className="details-page-header">
92+
<span
93+
className="co-m-resource-icon co-m-resource-icon--lg argocd-resource-icon"
94+
title={iconTitle}
95+
>
96+
{iconText}
97+
</span>
98+
<span className="co-resource-item__resource-name">
99+
{name ?? obj?.metadata?.name}{' '}
100+
{isApplicationRefreshing(obj) ? <Spinner size="md" /> : <span></span>}
101+
</span>
102+
<span
103+
className="details-page-header__item"
104+
style={{ marginLeft: '10px', marginBottom: '5px' }}
105+
>
106+
<DevPreviewBadge />
107+
</span>
108+
</Title>
109+
<ActionList className="co-actions">
110+
<ActionListGroup>
111+
<ActionListItem>
112+
<FavoriteButton defaultName={name ?? obj?.metadata?.name} />
113+
</ActionListItem>
114+
<ActionListItem>
115+
<ActionsDropdown actions={actions} isKebabToggle={false} />
116+
</ActionListItem>
117+
</ActionListGroup>
118+
</ActionList>
119+
</PaneHeading>
120+
</DetailsPageTitle>
121+
</div>
122+
</>
123+
);
124+
};
125+
126+
export default DetailsPageHeader;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
.argocd-resource-icon {
2+
background-color: #E9654B !important;
3+
color: white !important;
4+
}
5+
6+
$co-button-height: calc(
7+
var(--pf-t--global--font--size--body--default) * var(--pf-t--global--font--line-height--body) +
8+
var(--pf-t--global--spacer--control--vertical--default) * 2
9+
);
10+
11+
.co-actions {
12+
// ensure all actions are of the same height regardless of content
13+
min-height: $co-button-height;
14+
15+
> .pf-v6-c-action-list__group {
16+
// remove extra spacing when a component is rendered but has no children
17+
> div:empty {
18+
display: none;
19+
}
20+
}
21+
}
22+
23+
.details-page-header {
24+
display: flex;
25+
align-items: center;
26+
&__item {
27+
padding-left: '16px';
28+
margin-left: '16px';
29+
}
30+
}
31+
.details-page-title {
32+
padding-block-start: var(--pf-t--global--spacer--sm);
33+
}

0 commit comments

Comments
 (0)