diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 15:07:43 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 15:07:43 +0300 |
commit | bdb1e64a7d620c203e5228717b7c464554b85f55 (patch) | |
tree | 3ce386266b207b23d1c1ea72f89af13f356cd4af /app/assets/javascripts | |
parent | f7830aeaa7fc0349492d1302e9459ec769978438 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
8 files changed, 169 insertions, 72 deletions
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json index 1c56327c03c..2d222903c0c 100644 --- a/app/assets/javascripts/editor/schema/ci.json +++ b/app/assets/javascripts/editor/schema/ci.json @@ -187,6 +187,21 @@ } ] }, + "coverage_report": { + "type": "object", + "description": "Used to collect coverage reports from the job.", + "properties": { + "coverage_format": { + "description": "Code coverage format used by the test framework.", + "enum": ["cobertura"] + }, + "path": { + "description": "Path to the coverage report file that should be parsed.", + "type": "string", + "minLength": 1 + } + } + }, "codequality": { "$ref": "#/definitions/string_file_list", "description": "Path to file or list of files with code quality report(s) (such as Code Climate)." diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js index 4ebb49b4756..3726743c032 100644 --- a/app/assets/javascripts/graphql_shared/constants.js +++ b/app/assets/javascripts/graphql_shared/constants.js @@ -19,3 +19,4 @@ export const TYPE_SCANNER_PROFILE = 'DastScannerProfile'; export const TYPE_SITE_PROFILE = 'DastSiteProfile'; export const TYPE_USER = 'User'; export const TYPE_VULNERABILITY = 'Vulnerability'; +export const TYPE_WORK_ITEM = 'WorkItem'; diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue index 0490728c6bc..78ef909c458 100644 --- a/app/assets/javascripts/issues/show/components/app.vue +++ b/app/assets/javascripts/issues/show/components/app.vue @@ -185,6 +185,11 @@ export default { required: false, default: false, }, + issueId: { + type: Number, + required: false, + default: null, + }, }, data() { const store = new Store({ @@ -534,6 +539,7 @@ export default { <component :is="descriptionComponent" + :issue-id="issueId" :can-update="canUpdate" :description-html="state.descriptionHtml" :description-text="state.descriptionText" @@ -545,6 +551,7 @@ export default { @taskListUpdateStarted="taskListUpdateStarted" @taskListUpdateSucceeded="taskListUpdateSucceeded" @taskListUpdateFailed="taskListUpdateFailed" + @updateDescription="state.descriptionHtml = $event" /> <edited-component diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index 68ed7bb4062..9f2d48ad82b 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -7,6 +7,8 @@ import { GlButton, } from '@gitlab/ui'; import $ from 'jquery'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_WORK_ITEM } from '~/graphql_shared/constants'; import createFlash from '~/flash'; import { __, sprintf } from '~/locale'; import TaskList from '~/task_list'; @@ -63,6 +65,11 @@ export default { required: false, default: 0, }, + issueId: { + type: Number, + required: false, + default: null, + }, }, data() { return { @@ -81,6 +88,9 @@ export default { workItemsEnabled() { return this.glFeatures.workItems; }, + issueGid() { + return this.issueId ? convertToGraphQLId(TYPE_WORK_ITEM, this.issueId) : null; + }, }, watch: { descriptionHtml(newDescription, oldDescription) { @@ -92,6 +102,9 @@ export default { this.$nextTick(() => { this.renderGFM(); + if (this.workItemsEnabled) { + this.renderTaskActions(); + } }); }, taskStatus() { @@ -168,9 +181,24 @@ export default { return; } + this.taskButtons = []; const taskListFields = this.$el.querySelectorAll('.task-list-item'); taskListFields.forEach((item, index) => { + const taskLink = item.querySelector('.gfm-issue'); + if (taskLink) { + const { issue, referenceType } = taskLink.dataset; + taskLink.addEventListener('click', (e) => { + e.preventDefault(); + this.workItemId = convertToGraphQLId(TYPE_WORK_ITEM, issue); + this.track('viewed_work_item_from_modal', { + category: 'workItems:show', + label: 'work_item_view', + property: `type_${referenceType}`, + }); + }); + return; + } const button = document.createElement('button'); button.classList.add( 'btn', @@ -195,7 +223,14 @@ export default { }); }, openCreateTaskModal(id) { - this.activeTask = { id, title: this.$el.querySelector(`#${id}`).parentElement.innerText }; + const { parentElement } = this.$el.querySelector(`#${id}`); + const lineNumbers = parentElement.getAttribute('data-sourcepos').match(/\b\d+(?=:)/g); + this.activeTask = { + id, + title: parentElement.innerText, + lineNumberStart: lineNumbers[0], + lineNumberEnd: lineNumbers[1], + }; this.$refs.modal.show(); }, closeCreateTaskModal() { @@ -207,38 +242,10 @@ export default { handleWorkItemDetailModalError(message) { createFlash({ message }); }, - handleCreateTask({ id, title, type }) { - const listItem = this.$el.querySelector(`#${this.activeTask.id}`).parentElement; - const taskBadge = document.createElement('span'); - taskBadge.innerHTML = ` - <svg data-testid="issue-open-m-icon" role="img" aria-hidden="true" class="gl-icon gl-fill-green-500 s12"> - <use href="${gon.sprite_icons}#issue-open-m"></use> - </svg> - <span class="badge badge-info badge-pill gl-badge sm gl-mr-1"> - ${__('Task')} - </span> - `; - const button = this.createWorkItemDetailButton(id, title, type); - taskBadge.append(button); - - listItem.insertBefore(taskBadge, listItem.lastChild); - listItem.removeChild(listItem.lastChild); + handleCreateTask(description) { + this.$emit('updateDescription', description); this.closeCreateTaskModal(); }, - createWorkItemDetailButton(id, title, type) { - const button = document.createElement('button'); - button.addEventListener('click', () => { - this.workItemId = id; - this.track('viewed_work_item_from_modal', { - category: 'workItems:show', - label: 'work_item_view', - property: `type_${type}`, - }); - }); - button.classList.add('btn-link'); - button.innerText = title; - return button; - }, focusButton() { this.$refs.convertButton[0].$el.focus(); }, @@ -287,6 +294,10 @@ export default { <create-work-item :is-modal="true" :initial-title="activeTask.title" + :issue-gid="issueGid" + :lock-version="lockVersion" + :line-number-start="activeTask.lineNumberStart" + :line-number-end="activeTask.lineNumberEnd" @closeModal="closeCreateTaskModal" @onCreate="handleCreateTask" /> diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index c9af5d9b4a7..4a5ebf9615b 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -102,7 +102,7 @@ export function initIssueApp(issueData, store) { isConfidential: this.getNoteableData?.confidential, isLocked: this.getNoteableData?.discussion_locked, issuableStatus: this.getNoteableData?.state, - id: this.getNoteableData?.id, + issueId: this.getNoteableData?.id, }, }); }, diff --git a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue index 942677bb937..d5687d26499 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail_modal.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail_modal.vue @@ -1,5 +1,5 @@ <script> -import { GlModal } from '@gitlab/ui'; +import { GlModal, GlLoadingIcon } from '@gitlab/ui'; import { s__ } from '~/locale'; import workItemQuery from '../graphql/work_item.query.graphql'; import ItemTitle from './item_title.vue'; @@ -7,6 +7,7 @@ import ItemTitle from './item_title.vue'; export default { components: { GlModal, + GlLoadingIcon, ItemTitle, }, props: { @@ -57,6 +58,7 @@ export default { <template> <gl-modal hide-footer modal-id="work-item-detail-modal" :visible="visible" @hide="$emit('close')"> - <item-title class="gl-m-0!" :initial-title="workItemTitle" /> + <gl-loading-icon v-if="$apollo.queries.workItem.loading" size="md" /> + <item-title v-else class="gl-m-0!" :initial-title="workItemTitle" /> </gl-modal> </template> diff --git a/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql b/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql new file mode 100644 index 00000000000..b25210f5c74 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/create_work_item_from_task.mutation.graphql @@ -0,0 +1,9 @@ +mutation workItemCreateFromTask($input: WorkItemCreateFromTaskInput!) { + workItemCreateFromTask(input: $input) { + workItem { + id + descriptionHtml + } + errors + } +} diff --git a/app/assets/javascripts/work_items/pages/create_work_item.vue b/app/assets/javascripts/work_items/pages/create_work_item.vue index cc90cedb110..bbbeecbeaeb 100644 --- a/app/assets/javascripts/work_items/pages/create_work_item.vue +++ b/app/assets/javascripts/work_items/pages/create_work_item.vue @@ -1,21 +1,25 @@ <script> -import { GlButton, GlAlert, GlLoadingIcon, GlDropdown, GlDropdownItem } from '@gitlab/ui'; +import { GlButton, GlAlert, GlLoadingIcon, GlFormSelect } from '@gitlab/ui'; import { s__ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import workItemQuery from '../graphql/work_item.query.graphql'; import createWorkItemMutation from '../graphql/create_work_item.mutation.graphql'; +import createWorkItemFromTaskMutation from '../graphql/create_work_item_from_task.mutation.graphql'; import projectWorkItemTypesQuery from '../graphql/project_work_item_types.query.graphql'; import ItemTitle from '../components/item_title.vue'; export default { + createErrorText: s__('WorkItem|Something went wrong when creating a work item. Please try again'), + fetchTypesErrorText: s__( + 'WorkItem|Something went wrong when fetching work item types. Please try again', + ), components: { GlButton, GlAlert, GlLoadingIcon, - GlDropdown, - GlDropdownItem, ItemTitle, + GlFormSelect, }, inject: ['fullPath'], props: { @@ -29,6 +33,26 @@ export default { required: false, default: '', }, + issueGid: { + type: String, + required: false, + default: '', + }, + lockVersion: { + type: Number, + required: false, + default: null, + }, + lineNumberStart: { + type: String, + required: false, + default: null, + }, + lineNumberEnd: { + type: String, + required: false, + default: null, + }, }, data() { return { @@ -36,6 +60,7 @@ export default { error: null, workItemTypes: [], selectedWorkItemType: null, + loading: false, }; }, apollo: { @@ -47,12 +72,13 @@ export default { }; }, update(data) { - return data.workspace?.workItemTypes?.nodes; + return data.workspace?.workItemTypes?.nodes.map((node) => ({ + value: node.id, + text: node.name, + })); }, error() { - this.error = s__( - 'WorkItem|Something went wrong when fetching work item types. Please try again', - ); + this.error = this.$options.fetchTypesErrorText; }, }, }, @@ -60,9 +86,27 @@ export default { dropdownButtonText() { return this.selectedWorkItemType?.name || s__('WorkItem|Type'); }, + formOptions() { + return [ + { value: null, text: s__('WorkItem|Please select work item type') }, + ...this.workItemTypes, + ]; + }, + isButtonDisabled() { + return this.title.trim().length === 0 || !this.selectedWorkItemType; + }, }, methods: { async createWorkItem() { + this.loading = true; + if (this.isModal) { + await this.createWorkItemFromTask(); + } else { + await this.createStandaloneWorkItem(); + } + this.loading = false; + }, + async createStandaloneWorkItem() { try { const response = await this.$apollo.mutate({ mutation: createWorkItemMutation, @@ -70,7 +114,7 @@ export default { input: { title: this.title, projectPath: this.fullPath, - workItemTypeId: this.selectedWorkItemType?.id, + workItemTypeId: this.selectedWorkItemType, }, }, update(store, { data: { workItemCreate } }) { @@ -96,23 +140,38 @@ export default { }); }, }); - const { data: { workItemCreate: { - workItem: { id, type }, + workItem: { id }, }, }, } = response; - if (!this.isModal) { - this.$router.push({ name: 'workItem', params: { id: `${getIdFromGraphQLId(id)}` } }); - } else { - this.$emit('onCreate', { id, title: this.title, type }); - } + this.$router.push({ name: 'workItem', params: { id: `${getIdFromGraphQLId(id)}` } }); + } catch { + this.error = this.$options.createErrorText; + } + }, + async createWorkItemFromTask() { + try { + const { data } = await this.$apollo.mutate({ + mutation: createWorkItemFromTaskMutation, + variables: { + input: { + id: this.issueGid, + workItemData: { + lockVersion: this.lockVersion, + title: this.title, + lineNumberStart: Number(this.lineNumberStart), + lineNumberEnd: Number(this.lineNumberEnd), + workItemTypeId: this.selectedWorkItemType, + }, + }, + }, + }); + this.$emit('onCreate', data.workItemCreateFromTask.workItem.descriptionHtml); } catch { - this.error = s__( - 'WorkItem|Something went wrong when creating a work item. Please try again', - ); + this.error = this.$options.createErrorText; } }, handleTitleInput(title) { @@ -125,9 +184,6 @@ export default { } this.$emit('closeModal'); }, - selectWorkItemType(type) { - this.selectedWorkItemType = type; - }, }, }; </script> @@ -142,22 +198,17 @@ export default { @title-input="handleTitleInput" /> <div> - <gl-dropdown :text="dropdownButtonText"> - <gl-loading-icon - v-if="$apollo.queries.workItemTypes.loading" - size="md" - data-testid="loading-types" - /> - <template v-else> - <gl-dropdown-item - v-for="type in workItemTypes" - :key="type.id" - @click="selectWorkItemType(type)" - > - {{ type.name }} - </gl-dropdown-item> - </template> - </gl-dropdown> + <gl-loading-icon + v-if="$apollo.queries.workItemTypes.loading" + size="md" + data-testid="loading-types" + /> + <gl-form-select + v-else + v-model="selectedWorkItemType" + :options="formOptions" + class="gl-max-w-26" + /> </div> </div> <div @@ -166,8 +217,9 @@ export default { > <gl-button variant="confirm" - :disabled="title.length === 0" + :disabled="isButtonDisabled" :class="{ 'gl-mr-3': !isModal }" + :loading="loading" data-testid="create-button" type="submit" > |