diff --git a/packages/go/chow/ingestvalidator/schema.go b/packages/go/chow/ingestvalidator/schema.go
index f47d6d7665c..bfa57099687 100644
--- a/packages/go/chow/ingestvalidator/schema.go
+++ b/packages/go/chow/ingestvalidator/schema.go
@@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/packages/go/openapi/doc/openapi.json b/packages/go/openapi/doc/openapi.json
index 6650236dbb3..606342f558a 100644
--- a/packages/go/openapi/doc/openapi.json
+++ b/packages/go/openapi/doc/openapi.json
@@ -22337,7 +22337,8 @@
"Groups",
"Data Quality",
"Datapipe",
- "Cypher"
+ "Cypher",
+ "OpenGraph"
]
},
{
diff --git a/packages/javascript/bh-shared-ui/src/components/index.ts b/packages/javascript/bh-shared-ui/src/components/index.ts
index c5d39e9c5d2..d82c283a010 100644
--- a/packages/javascript/bh-shared-ui/src/components/index.ts
+++ b/packages/javascript/bh-shared-ui/src/components/index.ts
@@ -33,9 +33,9 @@ export { default as CollectorCard } from './CollectorCard';
export * from './CollectorCardList';
export { default as CollectorCardList } from './CollectorCardList';
export * from './ColumnHeaders';
-export * from './ConditionalTooltip';
export * from './CommunityIcon';
export { default as CommunityIcon } from './CommunityIcon';
+export * from './ConditionalTooltip';
export * from './ConfirmationDialog';
export { default as ConfirmationDialog } from './ConfirmationDialog';
export * from './CreateMenu';
diff --git a/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/RuleForm/RuleForm.test.tsx b/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/RuleForm/RuleForm.test.tsx
index 9228d09fd82..68ac85c49c7 100644
--- a/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/RuleForm/RuleForm.test.tsx
+++ b/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/RuleForm/RuleForm.test.tsx
@@ -439,4 +439,38 @@ describe('Rule Form', () => {
'/ui/explore?searchType=cypher&exploreSearchTab=cypher&cypherSearch=aGVsbG8%2Bd29ybGQ%3D'
);
});
+
+ it('calls handleError with ruleType when creating a rule fails', async () => {
+ vi.mocked(useParams).mockReturnValue({ zoneId: '1', ruleId: undefined });
+
+ server.use(
+ rest.post('/api/v2/asset-group-tags/:tagId/selectors', (_, res, ctx) => {
+ return res(
+ ctx.status(400),
+ ctx.json({
+ errors: [{ message: 'seeds are required' }],
+ })
+ );
+ })
+ );
+
+ render();
+
+ const nameInput = await screen.findByLabelText('Name');
+ await user.click(nameInput);
+ await user.paste('test rule');
+
+ // Submit without adding any seeds — should trigger the "seeds are required" path
+ await user.click(await screen.findByRole('button', { name: /Create Rule/ }));
+
+ await waitFor(() => {
+ expect(handleErrorSpy).toHaveBeenCalledWith(
+ expect.anything(),
+ 'creating',
+ 'rule',
+ expect.any(Function),
+ expect.objectContaining({ ruleType: expect.any(Number) })
+ );
+ });
+ });
});
diff --git a/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/RuleForm/RuleForm.tsx b/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/RuleForm/RuleForm.tsx
index fa7a429e4a9..a936de50f2e 100644
--- a/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/RuleForm/RuleForm.tsx
+++ b/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/RuleForm/RuleForm.tsx
@@ -163,7 +163,7 @@ const RuleForm: FC = () => {
// In the API, PATCHing with an empty seeds array ignore the array.
if (Array.isArray(diffedValues.seeds) && diffedValues.seeds.length === 0) {
return addNotification(
- getErrorMessage('seeds are required', 'updating', 'rule'),
+ getErrorMessage('seeds are required', 'updating', 'rule', ruleType),
'privilege-zones_updating-rule',
{
anchorOrigin: { vertical: 'top', horizontal: 'right' },
@@ -190,9 +190,9 @@ const RuleForm: FC = () => {
navigate(-1);
} catch (error) {
- handleError(error, 'updating', 'rule', addNotification);
+ handleError(error, 'updating', 'rule', addNotification, { ruleType });
}
- }, [tagId, ruleId, patchRuleMutation, addNotification, navigate, ruleQuery.data, form, seeds]);
+ }, [tagId, ruleId, ruleType, patchRuleMutation, addNotification, navigate, ruleQuery.data, form, seeds]);
const handleCreateRule = useCallback(async () => {
try {
@@ -212,9 +212,9 @@ const RuleForm: FC = () => {
navigate(tagDetailsLink(tagId));
} catch (error) {
- handleError(error, 'creating', 'rule', addNotification);
+ handleError(error, 'creating', 'rule', addNotification, { ruleType });
}
- }, [tagId, form, seeds, createRuleMutation, addNotification, navigate, tagDetailsLink]);
+ }, [tagId, ruleType, form, seeds, createRuleMutation, addNotification, navigate, tagDetailsLink]);
const onSubmit: SubmitHandler = useCallback(() => {
if (ruleId !== '') {
diff --git a/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/utils.test.ts b/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/utils.test.ts
index 03bd297e1fc..bb0a0e2c76d 100644
--- a/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/utils.test.ts
+++ b/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/utils.test.ts
@@ -14,9 +14,10 @@
//
// SPDX-License-Identifier: Apache-2.0
+import { SeedTypeCypher, SeedTypeObjectId } from 'js-client-library';
import { cloneDeep } from 'lodash';
import { errorSilencer } from '../../../mocks/stderr';
-import { handleError } from './utils';
+import { getErrorMessage, handleError } from './utils';
const mockAxiosError = {
isAxiosError: true,
@@ -116,4 +117,93 @@ describe('handleError', () => {
notificationOptions
);
});
+
+ it('reports an Object ID-specific error when ruleType is SeedTypeObjectId and seeds are required', () => {
+ const expectedMessage = 'To create a rule using Object ID, add at least one object using the field below.';
+
+ const handleErrorSpy = vi.fn();
+ handleError(mockAxiosCypherError, 'creating', 'rule', handleErrorSpy, { ruleType: SeedTypeObjectId });
+ expect(handleErrorSpy).toHaveBeenCalledWith(
+ expectedMessage,
+ 'privilege-zones_creating-rule',
+ notificationOptions
+ );
+ });
+
+ it('reports a Cypher-specific error when ruleType is SeedTypeCypher and seeds are required', () => {
+ const expectedMessage =
+ 'To save a rule created using Cypher, the Cypher must be run first. Click "Run" to continue';
+
+ const handleErrorSpy = vi.fn();
+ handleError(mockAxiosCypherError, 'creating', 'rule', handleErrorSpy, { ruleType: SeedTypeCypher });
+ expect(handleErrorSpy).toHaveBeenCalledWith(
+ expectedMessage,
+ 'privilege-zones_creating-rule',
+ notificationOptions
+ );
+ });
+
+ it('falls back to the default Cypher message when ruleType is undefined and seeds are required', () => {
+ const expectedMessage =
+ 'To save a rule created using Cypher, the Cypher must be run first. Click "Run" to continue';
+
+ const handleErrorSpy = vi.fn();
+ handleError(mockAxiosCypherError, 'creating', 'rule', handleErrorSpy);
+ expect(handleErrorSpy).toHaveBeenCalledWith(
+ expectedMessage,
+ 'privilege-zones_creating-rule',
+ notificationOptions
+ );
+ });
+
+ it('passes ruleType through optionalParams with empty object', () => {
+ const handleErrorSpy = vi.fn();
+ handleError(mockAxiosCypherError, 'creating', 'rule', handleErrorSpy, {});
+ // No ruleType provided, should fall back to default Cypher message
+ expect(handleErrorSpy).toHaveBeenCalledWith(
+ 'To save a rule created using Cypher, the Cypher must be run first. Click "Run" to continue',
+ 'privilege-zones_creating-rule',
+ notificationOptions
+ );
+ });
+});
+
+describe('getErrorMessage', () => {
+ it('returns name uniqueness message for "name must be unique"', () => {
+ const result = getErrorMessage('name must be unique', 'creating', 'rule');
+ expect(result).toBe(
+ 'Error creating rule: rule names must be unique. Please provide a unique name for your new rule and try again.'
+ );
+ });
+
+ it('returns Object ID message when ruleType is SeedTypeObjectId and seeds are required', () => {
+ const result = getErrorMessage('seeds are required', 'creating', 'rule', SeedTypeObjectId);
+ expect(result).toContain('Object ID');
+ expect(result).toBe('To create a rule using Object ID, add at least one object using the field below.');
+ });
+
+ it('returns Cypher message when ruleType is SeedTypeCypher and seeds are required', () => {
+ const result = getErrorMessage('seeds are required', 'creating', 'rule', SeedTypeCypher);
+ expect(result).toContain('Cypher');
+ expect(result).toContain('Click "Run" to continue');
+ });
+
+ it('returns fallback Cypher message when ruleType is undefined and seeds are required', () => {
+ const result = getErrorMessage('seeds are required', 'creating', 'rule');
+ expect(result).toBe(
+ 'To save a rule created using Cypher, the Cypher must be run first. Click "Run" to continue'
+ );
+ });
+
+ it('returns default error message for unknown API messages', () => {
+ const result = getErrorMessage('something unexpected', 'updating', 'zone');
+ expect(result).toBe(
+ 'An unexpected error occurred while updating the zone. Message: something unexpected. Please try again.'
+ );
+ });
+
+ it('uses the correct entity name in the Object ID message', () => {
+ const result = getErrorMessage('seeds are required', 'updating', 'zone', SeedTypeObjectId);
+ expect(result).toBe('To create a zone using Object ID, add at least one object using the field below.');
+ });
});
diff --git a/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/utils.ts b/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/utils.ts
index 0c41d079d08..1e59fee7747 100644
--- a/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/utils.ts
+++ b/packages/javascript/bh-shared-ui/src/views/PrivilegeZones/Save/utils.ts
@@ -14,15 +14,20 @@
//
// SPDX-License-Identifier: Apache-2.0
-import { isAxiosError } from 'js-client-library';
+import { isAxiosError, SeedTypeCypher, SeedTypeObjectId, SeedTypes, SeedTypesMap } from 'js-client-library';
import { OptionsObject } from 'notistack';
-export const getErrorMessage = (apiMessage: string, action: string, entity: string) => {
+export const getErrorMessage = (apiMessage: string, action: string, entity: string, ruleType?: SeedTypes) => {
switch (apiMessage) {
case 'name must be unique':
return `Error ${action} ${entity}: ${entity} names must be unique. Please provide a unique name for your new ${entity} and try again.`;
case 'seeds are required':
+ if (ruleType === SeedTypeObjectId) {
+ return `To create a ${entity} using ${SeedTypesMap[SeedTypeObjectId]}, add at least one object using the field below.`;
+ } else if (ruleType === SeedTypeCypher) {
+ return `To save a ${entity} created using ${SeedTypesMap[SeedTypeCypher]}, the ${SeedTypesMap[SeedTypeCypher]} must be run first. Click "Run" to continue`;
+ }
return `To save a ${entity} created using Cypher, the Cypher must be run first. Click "Run" to continue`;
default:
@@ -34,7 +39,8 @@ export const handleError = (
error: unknown,
action: 'creating' | 'updating' | 'deleting',
entity: 'rule' | 'zone' | 'label',
- addNotification: (notification: string, key?: string, options?: OptionsObject) => void
+ addNotification: (notification: string, key?: string, options?: OptionsObject) => void,
+ optionalParams?: { ruleType?: SeedTypes }
) => {
console.error(error);
@@ -49,7 +55,7 @@ export const handleError = (
const apiMessage = errorsList.length ? errorsList[0].message : error.response?.statusText || undefined;
if (apiMessage) {
- message = getErrorMessage(apiMessage, action, entity);
+ message = getErrorMessage(apiMessage, action, entity, optionalParams?.ruleType);
}
}