diff --git a/ee/app/assets/javascripts/security_orchestration/components/policy_drawer/pipeline_execution/utils.js b/ee/app/assets/javascripts/security_orchestration/components/policy_drawer/pipeline_execution/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..7aeea8bd634d200db193896f6fdaad3db6ec3ab5 --- /dev/null +++ b/ee/app/assets/javascripts/security_orchestration/components/policy_drawer/pipeline_execution/utils.js @@ -0,0 +1,67 @@ +import { isObject, isString, isEmpty } from 'lodash'; +import { __ } from '~/locale'; + +const LOCAL = 'local'; +const REMOTE = 'remote'; + +const KEY_LABEL_MAP = { + file: __('Path'), + project: __('Project'), + ref: __('Reference'), + template: __('Template'), + [LOCAL]: __('Local'), + [REMOTE]: __('Remote'), +}; + +/** + * Convert string or object include item to sentence + * @param acc + * @param item + * @returns {{}} + */ +export const humanizeIncludeArrayItem = (acc = {}, item) => { + if (!item) return acc; + + const buildMessage = ({ label, content }) => `${label}: ${content}`; + + if (isString(item)) { + const key = LOCAL in acc ? REMOTE : LOCAL; + acc[key] = buildMessage({ label: KEY_LABEL_MAP[key], content: item }); + } + + if (isObject(item)) { + Object.keys(item).forEach((key) => { + const content = item[key]; + const label = KEY_LABEL_MAP[key]; + + if (content) { + acc[key] = buildMessage({ label, content }); + } + }); + } + + return acc; +}; + +/** + * Create readable sentence from array of strings and objects + * @param action + * @returns {{}} + */ +export const humanizeExternalFileAction = (action) => { + const source = action?.content || action || {}; + + const { include } = source; + + return Array.isArray(include) + ? include?.reduce(humanizeIncludeArrayItem, {}) + : humanizeIncludeArrayItem({}, include); +}; + +/** + * Convert object to humanly readable text + * @param actions + * @returns {*} + */ +export const humanizeActions = (actions = []) => + actions.map(humanizeExternalFileAction).filter((res) => !isEmpty(res)); diff --git a/ee/spec/frontend/security_orchestration/components/policy_drawer/pipeline_execution/utils_spec.js b/ee/spec/frontend/security_orchestration/components/policy_drawer/pipeline_execution/utils_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..03e01ec1e6bb558351bc76254f51d60e7d9b0c11 --- /dev/null +++ b/ee/spec/frontend/security_orchestration/components/policy_drawer/pipeline_execution/utils_spec.js @@ -0,0 +1,79 @@ +import { + humanizeActions, + humanizeExternalFileAction, +} from 'ee/security_orchestration/components/policy_drawer/pipeline_execution/utils'; + +const mockActions = [ + { + content: { + include: [{ project: 'gitlab-policies/js9', ref: 'main', file: 'README.md' }], + }, + }, + { + content: { include: [{ ref: 'main', file: 'README.md', template: 'Template.md' }] }, + }, + { + content: { include: { project: 'gitlab-policies/js9', file: 'README.md' } }, + }, + { content: { include: { file: 'README.md' } } }, + { content: { include: {} } }, + { content: {} }, + { content: undefined }, + { scan: 'invalid' }, + { include: ['/templates/.local.yml', '/templates/.remote.yml'] }, + { include: ['/templates/.local.yml'] }, + { + include: [ + '/templates/.local.yml', + '/templates/.remote.yml', + { template: 'Auto-DevOps.gitlab-ci.yml' }, + { project: 'my-group/my-project', ref: 'main', file: '/templates/.gitlab-ci-template.yml' }, + ], + }, + { content: { include: { file: '' } } }, +]; + +describe('humanizeExternalFileAction', () => { + it.each` + action | output + ${mockActions[0]} | ${{ file: 'Path: README.md', project: 'Project: gitlab-policies/js9', ref: 'Reference: main' }} + ${mockActions[1]} | ${{ file: 'Path: README.md', ref: 'Reference: main', template: 'Template: Template.md' }} + ${mockActions[2]} | ${{ file: 'Path: README.md', project: 'Project: gitlab-policies/js9' }} + ${mockActions[3]} | ${{ file: 'Path: README.md' }} + ${mockActions[4]} | ${{}} + ${mockActions[5]} | ${{}} + ${mockActions[6]} | ${{}} + ${mockActions[7]} | ${{}} + ${mockActions[8]} | ${{ local: 'Local: /templates/.local.yml', remote: 'Remote: /templates/.remote.yml' }} + ${mockActions[9]} | ${{ local: 'Local: /templates/.local.yml' }} + ${mockActions[10]} | ${{ local: 'Local: /templates/.local.yml', remote: 'Remote: /templates/.remote.yml', template: 'Template: Auto-DevOps.gitlab-ci.yml', project: 'Project: my-group/my-project', ref: 'Reference: main', file: 'Path: /templates/.gitlab-ci-template.yml' }} + ${mockActions[11]} | ${{}} + `('should parse action to messages', ({ action, output }) => { + expect(humanizeExternalFileAction(action)).toEqual(output); + }); +}); + +describe('humanizeActions', () => { + it('should parse action to messages', () => { + expect(humanizeActions(mockActions)).toEqual([ + { + file: 'Path: README.md', + project: 'Project: gitlab-policies/js9', + ref: 'Reference: main', + }, + { file: 'Path: README.md', ref: 'Reference: main', template: 'Template: Template.md' }, + { file: 'Path: README.md', project: 'Project: gitlab-policies/js9' }, + { file: 'Path: README.md' }, + { local: 'Local: /templates/.local.yml', remote: 'Remote: /templates/.remote.yml' }, + { local: 'Local: /templates/.local.yml' }, + { + local: 'Local: /templates/.local.yml', + remote: 'Remote: /templates/.remote.yml', + template: 'Template: Auto-DevOps.gitlab-ci.yml', + project: 'Project: my-group/my-project', + ref: 'Reference: main', + file: 'Path: /templates/.gitlab-ci-template.yml', + }, + ]); + }); +}); diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 71287b27e269e846f0bf39a44a55d3d559510b9f..5534f68d51621b743f9ab727856d3157b5da10fd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -30617,6 +30617,9 @@ msgstr "" msgid "Loading…" msgstr "" +msgid "Local" +msgstr "" + msgid "Localization" msgstr "" @@ -42409,6 +42412,9 @@ msgstr "" msgid "Remind later" msgstr "" +msgid "Remote" +msgstr "" + msgid "Remote object has no absolute path." msgstr ""