From 85dc423f7090da0a52c73eb66faf22ddb20efff9 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Sat, 19 Sep 2020 01:45:44 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-4-stable-ee --- .../components/add_issuable_form.vue | 207 +++++++++++++++++ .../related_issues/components/issue_token.vue | 115 ++++++++++ .../components/related_issuable_input.vue | 231 +++++++++++++++++++ .../components/related_issues_block.vue | 215 ++++++++++++++++++ .../components/related_issues_list.vue | 146 ++++++++++++ .../components/related_issues_root.vue | 247 +++++++++++++++++++++ app/assets/javascripts/related_issues/constants.js | 106 +++++++++ app/assets/javascripts/related_issues/index.js | 27 +++ .../services/related_issues_service.js | 34 +++ .../related_issues/stores/related_issues_store.js | 50 +++++ 10 files changed, 1378 insertions(+) create mode 100644 app/assets/javascripts/related_issues/components/add_issuable_form.vue create mode 100644 app/assets/javascripts/related_issues/components/issue_token.vue create mode 100644 app/assets/javascripts/related_issues/components/related_issuable_input.vue create mode 100644 app/assets/javascripts/related_issues/components/related_issues_block.vue create mode 100644 app/assets/javascripts/related_issues/components/related_issues_list.vue create mode 100644 app/assets/javascripts/related_issues/components/related_issues_root.vue create mode 100644 app/assets/javascripts/related_issues/constants.js create mode 100644 app/assets/javascripts/related_issues/index.js create mode 100644 app/assets/javascripts/related_issues/services/related_issues_service.js create mode 100644 app/assets/javascripts/related_issues/stores/related_issues_store.js (limited to 'app/assets/javascripts/related_issues') diff --git a/app/assets/javascripts/related_issues/components/add_issuable_form.vue b/app/assets/javascripts/related_issues/components/add_issuable_form.vue new file mode 100644 index 00000000000..63d61989cba --- /dev/null +++ b/app/assets/javascripts/related_issues/components/add_issuable_form.vue @@ -0,0 +1,207 @@ + + + diff --git a/app/assets/javascripts/related_issues/components/issue_token.vue b/app/assets/javascripts/related_issues/components/issue_token.vue new file mode 100644 index 00000000000..31d0c7dbbb0 --- /dev/null +++ b/app/assets/javascripts/related_issues/components/issue_token.vue @@ -0,0 +1,115 @@ + + + diff --git a/app/assets/javascripts/related_issues/components/related_issuable_input.vue b/app/assets/javascripts/related_issues/components/related_issuable_input.vue new file mode 100644 index 00000000000..1931cfb2c00 --- /dev/null +++ b/app/assets/javascripts/related_issues/components/related_issuable_input.vue @@ -0,0 +1,231 @@ + + + diff --git a/app/assets/javascripts/related_issues/components/related_issues_block.vue b/app/assets/javascripts/related_issues/components/related_issues_block.vue new file mode 100644 index 00000000000..f7a79c62716 --- /dev/null +++ b/app/assets/javascripts/related_issues/components/related_issues_block.vue @@ -0,0 +1,215 @@ + + + diff --git a/app/assets/javascripts/related_issues/components/related_issues_list.vue b/app/assets/javascripts/related_issues/components/related_issues_list.vue new file mode 100644 index 00000000000..a75fe4397bb --- /dev/null +++ b/app/assets/javascripts/related_issues/components/related_issues_list.vue @@ -0,0 +1,146 @@ + + + diff --git a/app/assets/javascripts/related_issues/components/related_issues_root.vue b/app/assets/javascripts/related_issues/components/related_issues_root.vue new file mode 100644 index 00000000000..6f68b25b6fb --- /dev/null +++ b/app/assets/javascripts/related_issues/components/related_issues_root.vue @@ -0,0 +1,247 @@ + + + diff --git a/app/assets/javascripts/related_issues/constants.js b/app/assets/javascripts/related_issues/constants.js new file mode 100644 index 00000000000..89eae069a24 --- /dev/null +++ b/app/assets/javascripts/related_issues/constants.js @@ -0,0 +1,106 @@ +import { __, sprintf } from '~/locale'; + +export const issuableTypesMap = { + ISSUE: 'issue', + EPIC: 'epic', + MERGE_REQUEST: 'merge_request', +}; + +export const linkedIssueTypesMap = { + BLOCKS: 'blocks', + IS_BLOCKED_BY: 'is_blocked_by', + RELATES_TO: 'relates_to', +}; + +export const linkedIssueTypesTextMap = { + [linkedIssueTypesMap.RELATES_TO]: __('Relates to'), + [linkedIssueTypesMap.BLOCKS]: __('Blocks'), + [linkedIssueTypesMap.IS_BLOCKED_BY]: __('Is blocked by'), +}; + +export const autoCompleteTextMap = { + true: { + [issuableTypesMap.ISSUE]: sprintf( + __(' or %{emphasisStart}#issue id%{emphasisEnd}'), + { emphasisStart: '<', emphasisEnd: '>' }, + false, + ), + [issuableTypesMap.EPIC]: sprintf( + __(' or %{emphasisStart}&epic id%{emphasisEnd}'), + { emphasisStart: '<', emphasisEnd: '>' }, + false, + ), + [issuableTypesMap.MERGE_REQUEST]: sprintf( + __(' or %{emphasisStart}!merge request id%{emphasisEnd}'), + { emphasisStart: '<', emphasisEnd: '>' }, + false, + ), + }, + false: { + [issuableTypesMap.ISSUE]: '', + [issuableTypesMap.EPIC]: '', + [issuableTypesMap.MERGE_REQUEST]: __(' or references (e.g. path/to/project!merge_request_id)'), + }, +}; + +export const inputPlaceholderTextMap = { + [issuableTypesMap.ISSUE]: __('Paste issue link'), + [issuableTypesMap.EPIC]: __('Paste epic link'), + [issuableTypesMap.MERGE_REQUEST]: __('Enter merge request URLs'), +}; + +export const inputPlaceholderConfidentialTextMap = { + [issuableTypesMap.ISSUE]: __('Paste confidential issue link'), + [issuableTypesMap.EPIC]: __('Paste confidential epic link'), + [issuableTypesMap.MERGE_REQUEST]: __('Enter merge request URLs'), +}; + +export const relatedIssuesRemoveErrorMap = { + [issuableTypesMap.ISSUE]: __('An error occurred while removing issues.'), + [issuableTypesMap.EPIC]: __('An error occurred while removing epics.'), +}; + +export const pathIndeterminateErrorMap = { + [issuableTypesMap.ISSUE]: __('We could not determine the path to remove the issue'), + [issuableTypesMap.EPIC]: __('We could not determine the path to remove the epic'), +}; + +export const itemAddFailureTypesMap = { + NOT_FOUND: 'not_found', + MAX_NUMBER_OF_CHILD_EPICS: 'conflict', +}; + +export const addRelatedIssueErrorMap = { + [issuableTypesMap.ISSUE]: __('Issue cannot be found.'), + [issuableTypesMap.EPIC]: __('Epic cannot be found.'), +}; + +export const addRelatedItemErrorMap = { + [itemAddFailureTypesMap.MAX_NUMBER_OF_CHILD_EPICS]: __( + 'This epic already has the maximum number of child epics.', + ), +}; + +/** + * These are used to map issuableType to the correct icon. + * Since these are never used for any display purposes, don't wrap + * them inside i18n functions. + */ +export const issuableIconMap = { + [issuableTypesMap.ISSUE]: 'issues', + [issuableTypesMap.EPIC]: 'epic', +}; + +/** + * These are used to map issuableType to the correct QA class. + * Since these are never used for any display purposes, don't wrap + * them inside i18n functions. + */ +export const issuableQaClassMap = { + [issuableTypesMap.EPIC]: 'qa-add-epics-button', +}; + +export const PathIdSeparator = { + Epic: '&', + Issue: '#', +}; diff --git a/app/assets/javascripts/related_issues/index.js b/app/assets/javascripts/related_issues/index.js new file mode 100644 index 00000000000..2e8626890cb --- /dev/null +++ b/app/assets/javascripts/related_issues/index.js @@ -0,0 +1,27 @@ +import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import RelatedIssuesRoot from './components/related_issues_root.vue'; + +export default function initRelatedIssues() { + const relatedIssuesRootElement = document.querySelector('.js-related-issues-root'); + if (relatedIssuesRootElement) { + // eslint-disable-next-line no-new + new Vue({ + el: relatedIssuesRootElement, + components: { + relatedIssuesRoot: RelatedIssuesRoot, + }, + render: createElement => + createElement('related-issues-root', { + props: { + endpoint: relatedIssuesRootElement.dataset.endpoint, + canAdmin: parseBoolean(relatedIssuesRootElement.dataset.canAddRelatedIssues), + helpPath: relatedIssuesRootElement.dataset.helpPath, + showCategorizedIssues: parseBoolean( + relatedIssuesRootElement.dataset.showCategorizedIssues, + ), + }, + }), + }); + } +} diff --git a/app/assets/javascripts/related_issues/services/related_issues_service.js b/app/assets/javascripts/related_issues/services/related_issues_service.js new file mode 100644 index 00000000000..3c19f63157e --- /dev/null +++ b/app/assets/javascripts/related_issues/services/related_issues_service.js @@ -0,0 +1,34 @@ +import axios from '~/lib/utils/axios_utils'; +import { linkedIssueTypesMap } from '../constants'; + +class RelatedIssuesService { + constructor(endpoint) { + this.endpoint = endpoint; + } + + fetchRelatedIssues() { + return axios.get(this.endpoint); + } + + addRelatedIssues(newIssueReferences, linkType = linkedIssueTypesMap.RELATES_TO) { + return axios.post(this.endpoint, { + issuable_references: newIssueReferences, + link_type: linkType, + }); + } + + static saveOrder({ endpoint, move_before_id, move_after_id }) { + return axios.put(endpoint, { + epic: { + move_before_id, + move_after_id, + }, + }); + } + + static remove(endpoint) { + return axios.delete(endpoint); + } +} + +export default RelatedIssuesService; diff --git a/app/assets/javascripts/related_issues/stores/related_issues_store.js b/app/assets/javascripts/related_issues/stores/related_issues_store.js new file mode 100644 index 00000000000..14d71628cad --- /dev/null +++ b/app/assets/javascripts/related_issues/stores/related_issues_store.js @@ -0,0 +1,50 @@ +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; + +class RelatedIssuesStore { + constructor() { + this.state = { + // Stores issue objects of the known related issues + relatedIssues: [], + // Stores references of the "staging area" related issues that are planned to be added + pendingReferences: [], + }; + } + + setRelatedIssues(issues = []) { + this.state.relatedIssues = convertObjectPropsToCamelCase(issues, { deep: true }); + } + + addRelatedIssues(issues = []) { + this.setRelatedIssues(this.state.relatedIssues.concat(issues)); + } + + removeRelatedIssue(issue) { + this.state.relatedIssues = this.state.relatedIssues.filter(x => x.id !== issue.id); + } + + updateIssueOrder(oldIndex, newIndex) { + if (this.state.relatedIssues.length > 0) { + const updatedIssue = this.state.relatedIssues.splice(oldIndex, 1)[0]; + this.state.relatedIssues.splice(newIndex, 0, updatedIssue); + } + } + + setPendingReferences(issues) { + // Remove duplicates but retain order. + // If you don't do this, Vue will be confused by duplicates and refuse to delete them all. + this.state.pendingReferences = issues.filter((ref, idx) => issues.indexOf(ref) === idx); + } + + addPendingReferences(references = []) { + const issues = this.state.pendingReferences.concat(references); + this.setPendingReferences(issues); + } + + removePendingRelatedIssue(indexToRemove) { + this.state.pendingReferences = this.state.pendingReferences.filter( + (reference, index) => index !== indexToRemove, + ); + } +} + +export default RelatedIssuesStore; -- cgit v1.2.3