Skip to content

Commit 24608cf

Browse files
committed
Add basic properties of resources as shared component (#7713)
Signed-off-by: Keith Chong <kykchong@redhat.com>
1 parent dc6df38 commit 24608cf

File tree

3 files changed

+275
-1
lines changed

3 files changed

+275
-1
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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react';
2+
import * as _ from 'lodash-es';
3+
4+
import {
5+
K8sResourceKind,
6+
OwnerReference,
7+
ResourceLink,
8+
} from '@openshift-console/dynamic-plugin-sdk';
9+
10+
import { useGitOpsTranslation } from '../../hooks/useGitOpsTranslation';
11+
12+
export const OwnerReferences: React.FC<OwnerReferencesProps> = ({ resource }) => {
13+
const { t } = useGitOpsTranslation();
14+
const owners = (_.get(resource.metadata, 'ownerReferences') || []).map((o: OwnerReference) => (
15+
<ResourceLink key={o.uid} kind={o.kind} name={o.name} namespace={resource.metadata.namespace} />
16+
));
17+
return owners.length ? (
18+
<>{owners}</>
19+
) : (
20+
<span className="pf-v6-u-text-color-subtle">{t('public~No owner')}</span>
21+
);
22+
};
23+
24+
type OwnerReferencesProps = {
25+
resource: K8sResourceKind;
26+
};
27+
28+
OwnerReferences.displayName = 'OwnerReferences';

src/gitops/utils/utils.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { K8sResourceCommon, K8sVerb, useAccessReview } from '@openshift-console/dynamic-plugin-sdk';
1+
import {
2+
GroupVersionKind,
3+
K8sResourceCommon,
4+
K8sResourceKindReference,
5+
K8sVerb,
6+
useAccessReview,
7+
} from '@openshift-console/dynamic-plugin-sdk';
28
import { K8sModel } from '@openshift-console/dynamic-plugin-sdk/lib/api/common-types';
39

410
const modelToRef = (obj: K8sModel) => `${obj.apiGroup}~${obj.apiVersion}~${obj.kind}`;
@@ -90,3 +96,8 @@ export function getSelectorSearchURL(namespace: string, kind: string, selector:
9096
return '/search?kind=' + kind + '&q=' + selector;
9197
}
9298
}
99+
100+
export const isGroupVersionKind = (ref: GroupVersionKind | string) => ref?.split('~').length === 3;
101+
102+
export const kindForReference = (ref: K8sResourceKindReference) =>
103+
isGroupVersionKind(ref) ? ref.split('~')[2] : ref;

0 commit comments

Comments
 (0)