diff --git a/x-pack/plugins/cases/common/constants/index.ts b/x-pack/plugins/cases/common/constants/index.ts index 83916f0cb679f..557899e322ae7 100644 --- a/x-pack/plugins/cases/common/constants/index.ts +++ b/x-pack/plugins/cases/common/constants/index.ts @@ -137,6 +137,8 @@ export const MAX_TEMPLATE_KEY_LENGTH = 36 as const; // uuidv4 length export const MAX_TEMPLATE_NAME_LENGTH = 50 as const; export const MAX_TEMPLATE_DESCRIPTION_LENGTH = 1000 as const; export const MAX_TEMPLATES_LENGTH = 10 as const; +export const MAX_TEMPLATE_TAG_LENGTH = 50 as const; +export const MAX_TAGS_PER_TEMPLATE = 10 as const; /** * Cases features diff --git a/x-pack/plugins/cases/common/types/api/configure/v1.test.ts b/x-pack/plugins/cases/common/types/api/configure/v1.test.ts index b8ecbe59224bf..1932398507dda 100644 --- a/x-pack/plugins/cases/common/types/api/configure/v1.test.ts +++ b/x-pack/plugins/cases/common/types/api/configure/v1.test.ts @@ -17,10 +17,12 @@ import { MAX_DESCRIPTION_LENGTH, MAX_LENGTH_PER_TAG, MAX_TAGS_PER_CASE, + MAX_TAGS_PER_TEMPLATE, MAX_TEMPLATES_LENGTH, MAX_TEMPLATE_DESCRIPTION_LENGTH, MAX_TEMPLATE_KEY_LENGTH, MAX_TEMPLATE_NAME_LENGTH, + MAX_TEMPLATE_TAG_LENGTH, MAX_TITLE_LENGTH, } from '../../../constants'; import { CaseSeverity } from '../../domain'; @@ -110,6 +112,7 @@ describe('configure', () => { key: 'template_key_1', name: 'Template 1', description: 'this is first template', + tags: ['foo', 'bar'], caseFields: { title: 'case using sample template', }, @@ -118,6 +121,7 @@ describe('configure', () => { key: 'template_key_2', name: 'Template 2', description: 'this is second template', + tags: [], caseFields: null, }, ], @@ -222,6 +226,7 @@ describe('configure', () => { key: 'template_key_1', name: 'Template 1', description: 'this is first template', + tags: ['foo', 'bar'], caseFields: { title: 'case using sample template', }, @@ -247,6 +252,7 @@ describe('configure', () => { key: 'template_key_1', name: 'Template 1', description: 'this is first template', + tags: [], caseFields: { title: 'case using sample template', }, @@ -511,6 +517,7 @@ describe('configure', () => { key: 'template_key_1', name: 'Template 1', description: 'this is first template', + tags: ['foo', 'bar'], caseFields: { title: 'case using sample template', }, @@ -596,6 +603,30 @@ describe('configure', () => { ).toContain('The length of the description is too long. The maximum length is 1000.'); }); + it(`throws an error when there are more than ${MAX_TAGS_PER_TEMPLATE} tags`, async () => { + const tags = Array(MAX_TAGS_PER_TEMPLATE + 1).fill('foobar'); + + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, tags })) + ).toContain( + `The length of the field template's tags is too long. Array must be of length <= 10.` + ); + }); + + it(`throws an error when the a tag is more than ${MAX_TEMPLATE_TAG_LENGTH} characters`, async () => { + const tag = 'a'.repeat(MAX_TEMPLATE_TAG_LENGTH + 1); + + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, tags: [tag] })) + ).toContain(`The length of the template's tag is too long. The maximum length is 50.`); + }); + + it(`throws an error when the a tag is empty string`, async () => { + expect( + PathReporter.report(TemplateConfigurationRt.decode({ ...defaultRequest, tags: [''] })) + ).toContain(`The template's tag field cannot be an empty string.`); + }); + describe('caseFields', () => { it('removes foo:bar attributes from caseFields', () => { const query = TemplateConfigurationRt.decode({ diff --git a/x-pack/plugins/cases/common/types/api/configure/v1.ts b/x-pack/plugins/cases/common/types/api/configure/v1.ts index db3ce3276c4c1..b8973f1ca9ab2 100644 --- a/x-pack/plugins/cases/common/types/api/configure/v1.ts +++ b/x-pack/plugins/cases/common/types/api/configure/v1.ts @@ -10,10 +10,12 @@ import { MAX_CUSTOM_FIELDS_PER_CASE, MAX_CUSTOM_FIELD_KEY_LENGTH, MAX_CUSTOM_FIELD_LABEL_LENGTH, + MAX_TAGS_PER_TEMPLATE, MAX_TEMPLATES_LENGTH, MAX_TEMPLATE_DESCRIPTION_LENGTH, MAX_TEMPLATE_KEY_LENGTH, MAX_TEMPLATE_NAME_LENGTH, + MAX_TEMPLATE_TAG_LENGTH, } from '../../../constants'; import { limitedArraySchema, limitedStringSchema, regexStringRt } from '../../../schema'; import { CustomFieldTextTypeRt, CustomFieldToggleTypeRt } from '../../domain'; @@ -69,32 +71,51 @@ export const CustomFieldsConfigurationRt = limitedArraySchema({ fieldName: 'customFields', }); -export const TemplateConfigurationRt = rt.strict({ - /** - * key of template - */ - key: regexStringRt({ - codec: limitedStringSchema({ fieldName: 'key', min: 1, max: MAX_TEMPLATE_KEY_LENGTH }), - pattern: '^[a-z0-9_-]+$', - message: `Key must be lower case, a-z, 0-9, '_', and '-' are allowed`, - }), - /** - * name of template - */ - name: limitedStringSchema({ fieldName: 'name', min: 1, max: MAX_TEMPLATE_NAME_LENGTH }), - /** - * description of templates - */ - description: limitedStringSchema({ - fieldName: 'description', - min: 1, - max: MAX_TEMPLATE_DESCRIPTION_LENGTH, +export const TemplateConfigurationRt = rt.intersection([ + rt.strict({ + /** + * key of template + */ + key: regexStringRt({ + codec: limitedStringSchema({ fieldName: 'key', min: 1, max: MAX_TEMPLATE_KEY_LENGTH }), + pattern: '^[a-z0-9_-]+$', + message: `Key must be lower case, a-z, 0-9, '_', and '-' are allowed`, + }), + /** + * name of template + */ + name: limitedStringSchema({ fieldName: 'name', min: 1, max: MAX_TEMPLATE_NAME_LENGTH }), + /** + * description of templates + */ + description: limitedStringSchema({ + fieldName: 'description', + min: 1, + max: MAX_TEMPLATE_DESCRIPTION_LENGTH, + }), + /** + * case fields + */ + caseFields: rt.union([rt.null, CaseBaseOptionalFieldsRequestRt]), }), - /** - * case fields - */ - caseFields: rt.union([rt.null, CaseBaseOptionalFieldsRequestRt]), -}); + rt.exact( + rt.partial({ + /** + * tags of templates + */ + tags: limitedArraySchema({ + codec: limitedStringSchema({ + fieldName: `template's tag`, + min: 1, + max: MAX_TEMPLATE_TAG_LENGTH, + }), + min: 0, + max: MAX_TAGS_PER_TEMPLATE, + fieldName: `template's tags`, + }), + }) + ), +]); export const TemplatesConfigurationRt = limitedArraySchema({ codec: TemplateConfigurationRt, diff --git a/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts b/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts index aa6b8a88d43ee..bab00c9c2fa2a 100644 --- a/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts +++ b/x-pack/plugins/cases/common/types/domain/configure/v1.test.ts @@ -51,6 +51,7 @@ describe('configure', () => { key: 'template_sample_1', name: 'Sample template 1', description: 'this is first sample template', + tags: ['foo', 'bar', 'foobar'], caseFields: { title: 'Case with sample template 1', description: 'case desc', @@ -81,6 +82,7 @@ describe('configure', () => { key: 'template_sample_2', name: 'Sample template 2', description: 'this is second sample template', + tags: [], caseFields: { title: 'Case with sample template 2', tags: ['sample-2'], diff --git a/x-pack/plugins/cases/common/types/domain/configure/v1.ts b/x-pack/plugins/cases/common/types/domain/configure/v1.ts index 86c966b03ca9b..7a516d68ccea6 100644 --- a/x-pack/plugins/cases/common/types/domain/configure/v1.ts +++ b/x-pack/plugins/cases/common/types/domain/configure/v1.ts @@ -58,24 +58,34 @@ export const CustomFieldConfigurationRt = rt.union([ export const CustomFieldsConfigurationRt = rt.array(CustomFieldConfigurationRt); -export const TemplateConfigurationRt = rt.strict({ - /** - * key of template - */ - key: rt.string, - /** - * name of template - */ - name: rt.string, - /** - * description of template - */ - description: rt.string, - /** - * case fields of template - */ - caseFields: rt.union([rt.null, CaseBaseOptionalFieldsRt]), -}); +export const TemplateConfigurationRt = rt.intersection([ + rt.strict({ + /** + * key of template + */ + key: rt.string, + /** + * name of template + */ + name: rt.string, + /** + * description of template + */ + description: rt.string, + /** + * case fields of template + */ + caseFields: rt.union([rt.null, CaseBaseOptionalFieldsRt]), + }), + rt.exact( + rt.partial({ + /** + * tags of template + */ + tags: rt.array(rt.string), + }) + ), +]); export const TemplatesConfigurationRt = rt.array(TemplateConfigurationRt); diff --git a/x-pack/plugins/cases/public/containers/mock.ts b/x-pack/plugins/cases/public/containers/mock.ts index 7d5096842173b..2fdd82129825e 100644 --- a/x-pack/plugins/cases/public/containers/mock.ts +++ b/x-pack/plugins/cases/public/containers/mock.ts @@ -1190,12 +1190,14 @@ export const templatesConfigurationMock: CasesConfigurationUITemplate[] = [ key: 'test_template_2', name: 'Second test template', description: 'This is a second test template', + tags: [], caseFields: {}, }, { key: 'test_template_3', name: 'Third test template', description: 'This is a third test template with few case fields', + tags: ['foo'], caseFields: { title: 'This is case title using a test template', severity: CaseSeverity.MEDIUM, @@ -1206,6 +1208,7 @@ export const templatesConfigurationMock: CasesConfigurationUITemplate[] = [ key: 'test_template_4', name: 'Fourth test template', description: 'This is a fourth test template', + tags: ['foo', 'bar'], caseFields: { title: 'Case with sample template 4', description: 'case desc', diff --git a/x-pack/plugins/cases/server/client/configure/client.test.ts b/x-pack/plugins/cases/server/client/configure/client.test.ts index 5bc8c4a19679a..f744b1b613edc 100644 --- a/x-pack/plugins/cases/server/client/configure/client.test.ts +++ b/x-pack/plugins/cases/server/client/configure/client.test.ts @@ -380,6 +380,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'test', + tags: ['foo', 'bar'], caseFields: { title: 'Case title', description: 'This is test desc', @@ -415,6 +416,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'test', + tags: ['foo', 'bar'], caseFields: { title: 'Case title', description: 'This is test desc', @@ -450,6 +452,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'test', + tags: ['foo', 'bar'], caseFields: { title: 'Case title', description: 'This is test desc', @@ -532,12 +535,14 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'test', + tags: ['foo', 'bar'], caseFields: null, }, { key: 'template_1', name: 'template 2', description: 'test', + tags: [], caseFields: { title: 'Case title', }, @@ -798,6 +803,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'this is test description', + tags: ['foo', 'bar'], caseFields: null, }, ], @@ -814,6 +820,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'this is test description', + tags: ['foo', 'bar'], caseFields: { assignees: [{ uid: '1' }], }, @@ -928,6 +935,7 @@ describe('client', () => { key: 'duplicated_key', name: 'template 2', description: 'test', + tags: [], caseFields: { title: 'Case title', }, @@ -957,6 +965,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'test', + tags: ['foo', 'bar'], caseFields: { customFields: [ { @@ -1043,6 +1052,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'this is test description', + tags: ['foo', 'bar'], caseFields: { customFields: [ { @@ -1081,6 +1091,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'test', + tags: ['foo', 'bar'], caseFields: { customFields: [ { @@ -1196,6 +1207,7 @@ describe('client', () => { key: 'template_1', name: 'template 1', description: 'this is test description', + tags: [], caseFields: { assignees: [{ uid: '1' }], }, diff --git a/x-pack/plugins/cases/server/common/types/configure.ts b/x-pack/plugins/cases/server/common/types/configure.ts index 3ecb13a6a9fc6..549e3c462f880 100644 --- a/x-pack/plugins/cases/server/common/types/configure.ts +++ b/x-pack/plugins/cases/server/common/types/configure.ts @@ -46,6 +46,7 @@ type PersistedTemplatesConfiguration = Array<{ key: string; name: string; description: string; + tags?: string[]; caseFields?: CaseFieldsAttributes | null; }>; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts index 212bd68f46a54..5ccf9015839c7 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/get_configure.ts @@ -91,12 +91,14 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_template_1', name: 'First test template', description: 'This is a first test template', + tags: [], caseFields: null, }, { key: 'test_template_2', name: 'Second test template', description: 'This is a second test template', + tags: ['foobar'], caseFields: { title: 'Case with sample template 2', description: 'case desc', diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts index 6e89a92b6cdb6..0b93aeb8c5ddd 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/patch_configure.ts @@ -152,6 +152,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_template_1', name: 'First test template', description: 'This is a first test template', + tags: ['foo', 'bar'], caseFields: null, }, { @@ -189,6 +190,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_template_3', name: 'Third test template', description: 'This is a third test template', + tags: [], caseFields: { title: 'Case with sample template 3', tags: ['sample-3'], diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts index 0f766525f03d3..8a81214f009d6 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/configure/post_configure.ts @@ -129,6 +129,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_template_2', name: 'Second test template', description: 'This is a second test template', + tags: ['foo', 'bar'], caseFields: { title: 'Case with sample template 2', description: 'case desc', @@ -160,6 +161,7 @@ export default ({ getService }: FtrProviderContext): void => { key: 'test_template_3', name: 'Third test template', description: 'This is a third test template', + tags: ['foobar'], caseFields: { title: 'Case with sample template 3', tags: ['sample-3'],