Skip to content

Commit 876e65a

Browse files
committed
Added UI support for managing Group level rules
1 parent 4ef61b7 commit 876e65a

File tree

10 files changed

+158
-37
lines changed

10 files changed

+158
-37
lines changed

app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java

+4
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,10 @@ public void createGroupRule(String groupId, CreateRule data) {
325325
throw new MissingRequiredParameterException("config");
326326
}
327327

328+
if (new GroupId(groupId).isDefaultGroup()) {
329+
throw new NotAllowedException("Default group is not allowed");
330+
}
331+
328332
RuleConfigurationDto config = new RuleConfigurationDto();
329333
config.setConfiguration(data.getConfig());
330334

ui/ui-app/src/app/components/ruleList/RuleList.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
CompatibilityLabel,
77
CompatibilitySelect,
88
IntegrityLabel,
9-
IntegritySelect,
9+
IntegritySelect, RuleListType,
1010
RuleValue,
1111
ValidityLabel,
1212
ValiditySelect
@@ -19,7 +19,7 @@ export type RuleListProps = {
1919
onDisableRule: (ruleType: string) => void;
2020
onConfigureRule: (ruleType: string, config: string) => void;
2121
rules: Rule[];
22-
isGlobalRules: boolean;
22+
type: RuleListType;
2323
};
2424

2525
const NAME_COLUMN_WIDTH: string = "250px";
@@ -151,13 +151,13 @@ export const RuleList: FunctionComponent<RuleListProps> = (props: RuleListProps)
151151
}
152152

153153
const validityDescription = (
154-
<span>Ensure that content is <em>valid</em> when updating this artifact.</span>
154+
<span>Ensure that content is <em>valid</em> when creating an artifact or artifact version.</span>
155155
);
156156
const compatibilityDescription = (
157-
<span>Enforce a compatibility level when updating this artifact (for example, select Backward for backwards compatibility).</span>
157+
<span>Enforce a compatibility level when creating a new artifact version (for example, select Backward for backwards compatibility).</span>
158158
);
159159
const integrityDescription = (
160-
<span>Enforce artifact reference integrity when creating or updating artifacts. Enable and configure this rule to ensure that artifact references provided are correct.</span>
160+
<span>Enforce artifact reference integrity when creating an artifact or artifact version. Enable and configure this rule to ensure that provided artifact references are correct.</span>
161161
);
162162

163163
return (
@@ -176,7 +176,7 @@ export const RuleList: FunctionComponent<RuleListProps> = (props: RuleListProps)
176176
</Tooltip>
177177
</FlexItem>
178178
<FlexItem className="rule-actions">
179-
<RuleValue isGlobalRule={props.isGlobalRules} actions={validityRuleActions} label={validityRuleLabel} />
179+
<RuleValue type={props.type} actions={validityRuleActions} label={validityRuleLabel} />
180180
</FlexItem>
181181
</Flex>
182182
</GridItem>
@@ -194,7 +194,7 @@ export const RuleList: FunctionComponent<RuleListProps> = (props: RuleListProps)
194194
</Tooltip>
195195
</FlexItem>
196196
<FlexItem className="rule-actions">
197-
<RuleValue isGlobalRule={props.isGlobalRules} actions={compatibilityRuleActions} label={compatibilityRuleLabel} />
197+
<RuleValue type={props.type} actions={compatibilityRuleActions} label={compatibilityRuleLabel} />
198198
</FlexItem>
199199
</Flex>
200200
</GridItem>
@@ -212,7 +212,7 @@ export const RuleList: FunctionComponent<RuleListProps> = (props: RuleListProps)
212212
</Tooltip>
213213
</FlexItem>
214214
<FlexItem className="rule-actions">
215-
<RuleValue isGlobalRule={props.isGlobalRules} actions={integrityRuleActions} label={integrityRuleLabel} />
215+
<RuleValue type={props.type} actions={integrityRuleActions} label={integrityRuleLabel} />
216216
</FlexItem>
217217
</Flex>
218218
</GridItem>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export enum RuleListType {
2+
Global, Group, Artifact
3+
}

ui/ui-app/src/app/components/ruleList/RuleValue.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import React, { FunctionComponent } from "react";
22
import { If } from "@apicurio/common-ui-components";
33
import { useUserService } from "@services/useUserService.ts";
44
import { useConfigService } from "@services/useConfigService.ts";
5+
import { RuleListType } from "@app/components";
56

67

78
export type RuleValueProps = {
8-
isGlobalRule: boolean;
9+
type: RuleListType;
910
actions: React.ReactElement;
1011
label: React.ReactElement;
1112
};
@@ -17,7 +18,7 @@ export const RuleValue: FunctionComponent<RuleValueProps> = (props: RuleValuePro
1718
const userIsAdmin: boolean = user.isUserAdmin();
1819
const userIsDev: boolean = user.isUserDeveloper();
1920

20-
const isEditable: boolean = !readOnly && (props.isGlobalRule ? userIsAdmin : userIsDev);
21+
const isEditable: boolean = !readOnly && (props.type === RuleListType.Global ? userIsAdmin : userIsDev);
2122

2223
return (
2324
<>
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
export * from "./CompatibilitySelect.tsx";
2-
export * from "./CompatibilityLabel.tsx";
3-
export * from "./IntegritySelect.tsx";
4-
export * from "./IntegrityLabel.tsx";
5-
export * from "./ValiditySelect.tsx";
6-
export * from "./ValidityLabel.tsx";
7-
export * from "./RuleList.tsx";
8-
export * from "./RuleValue.tsx";
1+
export * from "./CompatibilitySelect";
2+
export * from "./CompatibilityLabel";
3+
export * from "./IntegritySelect";
4+
export * from "./IntegrityLabel";
5+
export * from "./ValiditySelect";
6+
export * from "./ValidityLabel";
7+
export * from "./RuleList";
8+
export * from "./RuleValue";
9+
export * from "./RuleListType";

ui/ui-app/src/app/pages/artifact/components/tabs/ArtifactInfoTabContent.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FunctionComponent } from "react";
22
import "./ArtifactInfoTabContent.css";
33
import "@app/styles/empty.css";
4-
import { ArtifactTypeIcon, IfAuth, IfFeature, RuleList } from "@app/components";
4+
import { ArtifactTypeIcon, IfAuth, IfFeature, RuleList, RuleListType } from "@app/components";
55
import {
66
Button,
77
Card,
@@ -152,7 +152,7 @@ export const ArtifactInfoTabContent: FunctionComponent<ArtifactInfoTabContentPro
152152
the equivalent global rules.
153153
</p>
154154
<RuleList
155-
isGlobalRules={false}
155+
type={RuleListType.Artifact}
156156
rules={props.rules}
157157
onEnableRule={props.onEnableRule}
158158
onDisableRule={props.onDisableRule}

ui/ui-app/src/app/pages/group/GroupPage.tsx

+50-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { LoggerService, useLoggerService } from "@services/useLoggerService.ts";
2424
import { GroupsService, useGroupsService } from "@services/useGroupsService.ts";
2525
import { ArtifactsTabContent } from "@app/pages/group/components/tabs/ArtifactsTabContent.tsx";
2626
import { ApiError } from "@models/apiError.model.ts";
27-
import { CreateArtifact, GroupMetaData, SearchedArtifact } from "@sdk/lib/generated-client/models";
27+
import { CreateArtifact, GroupMetaData, Rule, RuleType, SearchedArtifact } from "@sdk/lib/generated-client/models";
2828

2929

3030
export type GroupPageProps = {
@@ -49,6 +49,7 @@ export const GroupPage: FunctionComponent<GroupPageProps> = () => {
4949
const [isInvalidContentModalOpen, setInvalidContentModalOpen] = useState<boolean>(false);
5050
const [artifactToDelete, setArtifactToDelete] = useState<SearchedArtifact>();
5151
const [artifactDeleteSuccessCallback, setArtifactDeleteSuccessCallback] = useState<() => void>();
52+
const [rules, setRules] = useState<Rule[]>([]);
5253

5354
const appNavigation: AppNavigation = useAppNavigation();
5455
const logger: LoggerService = useLoggerService();
@@ -69,6 +70,11 @@ export const GroupPage: FunctionComponent<GroupPageProps> = () => {
6970
.catch(error => {
7071
setPageError(toPageError(error, "Error loading page data."));
7172
}),
73+
groups.getGroupRules(groupId as string)
74+
.then(setRules)
75+
.catch(error => {
76+
setPageError(toPageError(error, "Error loading page data."));
77+
}),
7278
];
7379
};
7480

@@ -136,6 +142,40 @@ export const GroupPage: FunctionComponent<GroupPageProps> = () => {
136142
});
137143
};
138144

145+
const doEnableRule = (ruleType: string): void => {
146+
logger.debug("[GroupPage] Enabling rule:", ruleType);
147+
let config: string = "FULL";
148+
if (ruleType === "COMPATIBILITY") {
149+
config = "BACKWARD";
150+
}
151+
groups.createGroupRule(groupId as string, ruleType, config).catch(error => {
152+
setPageError(toPageError(error, `Error enabling "${ ruleType }" group rule.`));
153+
});
154+
setRules([...rules, { config, ruleType: ruleType as RuleType }]);
155+
};
156+
157+
const doDisableRule = (ruleType: string): void => {
158+
logger.debug("[GroupPage] Disabling rule:", ruleType);
159+
groups.deleteGroupRule(groupId as string, ruleType).catch(error => {
160+
setPageError(toPageError(error, `Error disabling "${ ruleType }" group rule.`));
161+
});
162+
setRules(rules.filter(r => r.ruleType !== ruleType));
163+
};
164+
165+
const doConfigureRule = (ruleType: string, config: string): void => {
166+
logger.debug("[GroupPage] Configuring rule:", ruleType, config);
167+
groups.updateGroupRule(groupId as string, ruleType, config).catch(error => {
168+
setPageError(toPageError(error, `Error configuring "${ ruleType }" group rule.`));
169+
});
170+
setRules(rules.map(r => {
171+
if (r.ruleType === ruleType) {
172+
return { config, ruleType: r.ruleType };
173+
} else {
174+
return r;
175+
}
176+
}));
177+
};
178+
139179
const closeInvalidContentModal = (): void => {
140180
setInvalidContentModalOpen(false);
141181
};
@@ -201,7 +241,15 @@ export const GroupPage: FunctionComponent<GroupPageProps> = () => {
201241

202242
const tabs: any[] = [
203243
<Tab data-testid="info-tab" eventKey="overview" title="Overview" key="overview" tabContentId="tab-info">
204-
<GroupInfoTabContent group={group as GroupMetaData} onEditMetaData={() => setIsEditModalOpen(true)} onChangeOwner={() => {}} />
244+
<GroupInfoTabContent
245+
group={group as GroupMetaData}
246+
rules={rules}
247+
onEnableRule={doEnableRule}
248+
onDisableRule={doDisableRule}
249+
onConfigureRule={doConfigureRule}
250+
onEditMetaData={() => setIsEditModalOpen(true)}
251+
onChangeOwner={() => {}}
252+
/>
205253
</Tab>,
206254
<Tab data-testid="artifacts-tab" eventKey="artifacts" title="Artifacts" key="artifacts" tabContentId="tab-artifacts">
207255
<ArtifactsTabContent

ui/ui-app/src/app/pages/group/components/tabs/GroupInfoTabContent.tsx

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FunctionComponent } from "react";
22
import "./GroupInfoTabContent.css";
33
import "@app/styles/empty.css";
4-
import { IfAuth, IfFeature } from "@app/components";
4+
import { IfAuth, IfFeature, RuleList, RuleListType } from "@app/components";
55
import {
66
Button,
77
Card,
@@ -18,17 +18,21 @@ import {
1818
Label,
1919
Truncate
2020
} from "@patternfly/react-core";
21-
import { IndustryIcon, OutlinedFolderIcon, PencilAltIcon } from "@patternfly/react-icons";
21+
import { OutlinedFolderIcon, PencilAltIcon } from "@patternfly/react-icons";
2222
import { FromNow, If } from "@apicurio/common-ui-components";
2323
import { isStringEmptyOrUndefined } from "@utils/string.utils.ts";
24-
import { GroupMetaData } from "@sdk/lib/generated-client/models";
24+
import { GroupMetaData, Rule } from "@sdk/lib/generated-client/models";
2525
import { labelsToAny } from "@utils/rest.utils.ts";
2626

2727
/**
2828
* Properties
2929
*/
3030
export type GroupInfoTabContentProps = {
3131
group: GroupMetaData;
32+
rules: Rule[];
33+
onEnableRule: (ruleType: string) => void;
34+
onDisableRule: (ruleType: string) => void;
35+
onConfigureRule: (ruleType: string, config: string) => void;
3236
onEditMetaData: () => void;
3337
onChangeOwner: () => void;
3438
};
@@ -135,16 +139,13 @@ export const GroupInfoTabContent: FunctionComponent<GroupInfoTabContentProps> =
135139
individually enabled, configured, and disabled. Group-specific rules override
136140
the equivalent global rules.
137141
</p>
138-
<p>
139-
<b><IndustryIcon /> Under construction </b>
140-
</p>
141-
{/*<RuleList*/}
142-
{/* isGlobalRules={false}*/}
143-
{/* rules={props.rules}*/}
144-
{/* onEnableRule={props.onEnableRule}*/}
145-
{/* onDisableRule={props.onDisableRule}*/}
146-
{/* onConfigureRule={props.onConfigureRule}*/}
147-
{/*/>*/}
142+
<RuleList
143+
type={RuleListType.Group}
144+
rules={props.rules}
145+
onEnableRule={props.onEnableRule}
146+
onDisableRule={props.onDisableRule}
147+
onConfigureRule={props.onConfigureRule}
148+
/>
148149
</CardBody>
149150
</Card>
150151
</div>

ui/ui-app/src/app/pages/rules/RulesPage.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { FunctionComponent, useEffect, useState } from "react";
22
import "./RulesPage.css";
33
import { PageSection, PageSectionVariants, TextContent } from "@patternfly/react-core";
4-
import { RootPageHeader, RuleList } from "@app/components";
4+
import { RootPageHeader, RuleList, RuleListType } from "@app/components";
55
import { PageDataLoader, PageError, PageErrorHandler, toPageError } from "@app/pages";
66
import { AdminService, useAdminService } from "@services/useAdminService.ts";
77
import { LoggerService, useLoggerService } from "@services/useLoggerService.ts";
@@ -81,7 +81,7 @@ export const RulesPage: FunctionComponent<RulesPageProps> = () => {
8181
<PageSection variant={PageSectionVariants.default} isFilled={true}>
8282
<React.Fragment>
8383
<RuleList
84-
isGlobalRules={true}
84+
type={RuleListType.Global}
8585
rules={rules}
8686
onEnableRule={doEnableRule}
8787
onDisableRule={doDisableRule}

ui/ui-app/src/services/useGroupsService.ts

+63
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,47 @@ const deleteGroup = async (config: ConfigService, auth: AuthService, groupId: st
8282
};
8383

8484

85+
const getGroupRules = async (config: ConfigService, auth: AuthService, groupId: string|null): Promise<Rule[]> => {
86+
groupId = normalizeGroupId(groupId);
87+
88+
console.info("[GroupsService] Getting the list of rules for group: ", groupId);
89+
return getRegistryClient(config, auth).groups.byGroupId(groupId).rules.get().then(ruleTypes => {
90+
return Promise.all(ruleTypes!.map(rt => getGroupRule(config, auth, groupId, rt)));
91+
});
92+
};
93+
94+
const getGroupRule = async (config: ConfigService, auth: AuthService, groupId: string|null, ruleType: string): Promise<Rule> => {
95+
groupId = normalizeGroupId(groupId);
96+
return getRegistryClient(config, auth).groups.byGroupId(groupId).rules.byRuleType(ruleType).get().then(v => v!);
97+
};
98+
99+
const createGroupRule = async (config: ConfigService, auth: AuthService, groupId: string|null, ruleType: string, configValue: string): Promise<Rule> => {
100+
groupId = normalizeGroupId(groupId);
101+
console.info("[GroupsService] Creating group rule:", ruleType);
102+
const body: CreateRule = {
103+
config: configValue,
104+
ruleType: ruleType as RuleType
105+
};
106+
return getRegistryClient(config, auth).groups.byGroupId(groupId).rules.post(body).then(v => v!);
107+
};
108+
109+
const updateGroupRule = async (config: ConfigService, auth: AuthService, groupId: string|null, ruleType: string, configValue: string): Promise<Rule> => {
110+
groupId = normalizeGroupId(groupId);
111+
console.info("[GroupsService] Updating group rule:", ruleType);
112+
const body: Rule = {
113+
config: configValue,
114+
ruleType: ruleType as RuleType
115+
};
116+
return getRegistryClient(config, auth).groups.byGroupId(groupId).rules.byRuleType(ruleType).put(body).then(v => v!);
117+
};
118+
119+
const deleteGroupRule = async (config: ConfigService, auth: AuthService, groupId: string|null, ruleType: string): Promise<void> => {
120+
groupId = normalizeGroupId(groupId);
121+
console.info("[GroupsService] Deleting group rule:", ruleType);
122+
return getRegistryClient(config, auth).groups.byGroupId(groupId).rules.byRuleType(ruleType).delete();
123+
};
124+
125+
85126
const createArtifact = async (config: ConfigService, auth: AuthService, groupId: string|null, data: CreateArtifact): Promise<CreateArtifactResponse> => {
86127
groupId = normalizeGroupId(groupId);
87128
return getRegistryClient(config, auth).groups.byGroupId(groupId).artifacts.post(data).then(v => v!);
@@ -333,6 +374,12 @@ export interface GroupsService {
333374
updateGroupOwner(groupId: string, newOwner: string): Promise<void>;
334375
deleteGroup(groupId: string): Promise<void>;
335376

377+
getGroupRules(groupId: string|null): Promise<Rule[]>;
378+
createGroupRule(groupId: string|null, ruleType: string, configValue: string): Promise<Rule>;
379+
getGroupRule(groupId: string|null, ruleType: string): Promise<Rule>;
380+
updateGroupRule(groupId: string|null, ruleType: string, configValue: string): Promise<Rule>;
381+
deleteGroupRule(groupId: string|null, ruleType: string): Promise<void>;
382+
336383
createArtifact(groupId: string|null, data: CreateArtifact): Promise<CreateArtifactResponse>;
337384
getArtifactMetaData(groupId: string|null, artifactId: string): Promise<ArtifactMetaData>;
338385
getArtifactReferences(globalId: number, refType: ReferenceType): Promise<ArtifactReference[]>;
@@ -394,6 +441,22 @@ export const useGroupsService: () => GroupsService = (): GroupsService => {
394441
return deleteGroup(config, auth, groupId);
395442
},
396443

444+
getGroupRules(groupId: string|null): Promise<Rule[]> {
445+
return getGroupRules(config, auth, groupId);
446+
},
447+
createGroupRule(groupId: string|null, ruleType: string, configValue: string): Promise<Rule> {
448+
return createGroupRule(config, auth, groupId, ruleType, configValue);
449+
},
450+
getGroupRule(groupId: string|null, ruleType: string): Promise<Rule> {
451+
return getGroupRule(config, auth, groupId, ruleType);
452+
},
453+
updateGroupRule(groupId: string|null, ruleType: string, configValue: string): Promise<Rule> {
454+
return updateGroupRule(config, auth, groupId, ruleType, configValue);
455+
},
456+
deleteGroupRule(groupId: string|null, ruleType: string): Promise<void> {
457+
return deleteGroupRule(config, auth, groupId, ruleType);
458+
},
459+
397460
createArtifact(groupId: string|null, data: CreateArtifact): Promise<CreateArtifactResponse> {
398461
return createArtifact(config, auth, groupId, data);
399462
},

0 commit comments

Comments
 (0)