diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 23:02:30 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-18 23:02:30 +0300 |
commit | 41fe97390ceddf945f3d967b8fdb3de4c66b7dea (patch) | |
tree | 9c8d89a8624828992f06d892cd2f43818ff5dcc8 /app/assets/javascripts/issues | |
parent | 0804d2dc31052fb45a1efecedc8e06ce9bc32862 (diff) |
Add latest changes from gitlab-org/gitlab@14-9-stable-eev14.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/issues')
14 files changed, 122 insertions, 29 deletions
diff --git a/app/assets/javascripts/issues/create_merge_request_dropdown.js b/app/assets/javascripts/issues/create_merge_request_dropdown.js index a3752c7043c..247f8dd0bd6 100644 --- a/app/assets/javascripts/issues/create_merge_request_dropdown.js +++ b/app/assets/javascripts/issues/create_merge_request_dropdown.js @@ -10,6 +10,7 @@ import ISetter from '~/filtered_search/droplab/plugins/input_setter'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __, sprintf } from '~/locale'; +import { mergeUrlParams } from '~/lib/utils/url_utility'; // Todo: Remove this when fixing issue in input_setter plugin const InputSetter = { ...ISetter }; @@ -171,12 +172,21 @@ export default class CreateMergeRequestDropdown { this.isCreatingMergeRequest = true; return this.createBranch().then(() => { - window.location.href = canCreateConfidentialMergeRequest() + let path = canCreateConfidentialMergeRequest() ? this.createMrPath.replace( this.projectPath, confidentialMergeRequestState.selectedProject.pathWithNamespace, ) : this.createMrPath; + path = mergeUrlParams( + { + 'merge_request[target_branch]': this.refInput.value, + 'merge_request[source_branch]': this.branchInput.value, + }, + path, + ); + + window.location.href = path; }); }); } diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index 3866a7b3305..a532fa5b771 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -39,8 +39,11 @@ import { IssuableListTabs, IssuableStates } from '~/vue_shared/issuable/list/con import { CREATED_DESC, i18n, + ISSUE_REFERENCE, MAX_LIST_SIZE, PAGE_SIZE, + PARAM_PAGE_AFTER, + PARAM_PAGE_BEFORE, PARAM_STATE, RELATIVE_POSITION_ASC, TOKEN_TYPE_ASSIGNEE, @@ -134,6 +137,8 @@ export default { }, }, data() { + const pageAfter = getParameterByName(PARAM_PAGE_AFTER); + const pageBefore = getParameterByName(PARAM_PAGE_BEFORE); const state = getParameterByName(PARAM_STATE); const defaultSortKey = state === IssuableStates.Closed ? UPDATED_DESC : CREATED_DESC; const dashboardSortKey = getSortKey(this.initialSort); @@ -165,7 +170,7 @@ export default { issuesCounts: {}, issuesError: null, pageInfo: {}, - pageParams: getInitialPageParams(sortKey), + pageParams: getInitialPageParams(sortKey, pageAfter, pageBefore), showBulkEditSidebar: false, sortKey, state: state || IssuableStates.Opened, @@ -219,11 +224,13 @@ export default { }, computed: { queryVariables() { + const isIidSearch = ISSUE_REFERENCE.test(this.searchQuery); return { fullPath: this.fullPath, + iid: isIidSearch ? this.searchQuery.slice(1) : undefined, isProject: this.isProject, isSignedIn: this.isSignedIn, - search: this.searchQuery, + search: isIidSearch ? undefined : this.searchQuery, sort: this.sortKey, state: this.state, ...this.pageParams, @@ -234,7 +241,12 @@ export default { return this.isProject ? ITEM_TYPE.PROJECT : ITEM_TYPE.GROUP; }, hasSearch() { - return this.searchQuery || Object.keys(this.urlFilterParams).length; + return ( + this.searchQuery || + Object.keys(this.urlFilterParams).length || + this.pageParams.afterCursor || + this.pageParams.beforeCursor + ); }, isBulkEditButtonDisabled() { return this.showBulkEditSidebar || !this.issues.length; @@ -391,6 +403,8 @@ export default { }, urlParams() { return { + page_after: this.pageParams.afterCursor, + page_before: this.pageParams.beforeCursor, search: this.searchQuery, sort: urlSortParams[this.sortKey], state: this.state, diff --git a/app/assets/javascripts/issues/list/constants.js b/app/assets/javascripts/issues/list/constants.js index 284167a933f..4b07a078512 100644 --- a/app/assets/javascripts/issues/list/constants.js +++ b/app/assets/javascripts/issues/list/constants.js @@ -52,20 +52,15 @@ export const i18n = { upvotes: __('Upvotes'), }; +export const ISSUE_REFERENCE = /^#\d+$/; export const MAX_LIST_SIZE = 10; export const PAGE_SIZE = 20; export const PAGE_SIZE_MANUAL = 100; +export const PARAM_PAGE_AFTER = 'page_after'; +export const PARAM_PAGE_BEFORE = 'page_before'; export const PARAM_STATE = 'state'; export const RELATIVE_POSITION = 'relative_position'; -export const defaultPageSizeParams = { - firstPageSize: PAGE_SIZE, -}; - -export const largePageSizeParams = { - firstPageSize: PAGE_SIZE_MANUAL, -}; - export const BLOCKING_ISSUES_ASC = 'BLOCKING_ISSUES_ASC'; export const BLOCKING_ISSUES_DESC = 'BLOCKING_ISSUES_DESC'; export const CREATED_ASC = 'CREATED_ASC'; diff --git a/app/assets/javascripts/issues/list/queries/get_issues.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql index be8deb3fe97..529262d2162 100644 --- a/app/assets/javascripts/issues/list/queries/get_issues.query.graphql +++ b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql @@ -5,6 +5,7 @@ query getIssues( $isProject: Boolean = false $isSignedIn: Boolean = false $fullPath: ID! + $iid: String $search: String $sort: IssueSort $state: IssuableState @@ -29,6 +30,7 @@ query getIssues( id issues( includeSubgroups: true + iid: $iid search: $search sort: $sort state: $state @@ -59,6 +61,7 @@ query getIssues( project(fullPath: $fullPath) @include(if: $isProject) { id issues( + iid: $iid search: $search sort: $sort state: $state diff --git a/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql index 1a345fd2877..58e7ce32e7c 100644 --- a/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql +++ b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql @@ -1,6 +1,7 @@ query getIssuesCount( $isProject: Boolean = false $fullPath: ID! + $iid: String $search: String $assigneeId: String $assigneeUsernames: [String!] @@ -20,6 +21,7 @@ query getIssuesCount( openedIssues: issues( includeSubgroups: true state: opened + iid: $iid search: $search assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames @@ -37,6 +39,7 @@ query getIssuesCount( closedIssues: issues( includeSubgroups: true state: closed + iid: $iid search: $search assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames @@ -54,6 +57,7 @@ query getIssuesCount( allIssues: issues( includeSubgroups: true state: all + iid: $iid search: $search assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames @@ -73,6 +77,7 @@ query getIssuesCount( id openedIssues: issues( state: opened + iid: $iid search: $search assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames @@ -91,6 +96,7 @@ query getIssuesCount( } closedIssues: issues( state: closed + iid: $iid search: $search assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames @@ -109,6 +115,7 @@ query getIssuesCount( } allIssues: issues( state: all + iid: $iid search: $search assigneeId: $assigneeId assigneeUsernames: $assigneeUsernames diff --git a/app/assets/javascripts/issues/list/queries/search_users.query.graphql b/app/assets/javascripts/issues/list/queries/search_users.query.graphql index 92517ad35d0..46b48e4e41c 100644 --- a/app/assets/javascripts/issues/list/queries/search_users.query.graphql +++ b/app/assets/javascripts/issues/list/queries/search_users.query.graphql @@ -3,7 +3,7 @@ query searchUsers($fullPath: ID!, $search: String, $isProject: Boolean = false) { group(fullPath: $fullPath) @skip(if: $isProject) { id - groupMembers(search: $search) { + groupMembers(search: $search, relations: [DIRECT, INHERITED, SHARED_FROM_GROUPS]) { nodes { id user { @@ -14,7 +14,7 @@ query searchUsers($fullPath: ID!, $search: String, $isProject: Boolean = false) } project(fullPath: $fullPath) @include(if: $isProject) { id - projectMembers(search: $search) { + projectMembers(search: $search, relations: [DIRECT, INHERITED, INVITED_GROUPS]) { nodes { id user { diff --git a/app/assets/javascripts/issues/list/utils.js b/app/assets/javascripts/issues/list/utils.js index 6322968b3f0..4b77bd9bc5f 100644 --- a/app/assets/javascripts/issues/list/utils.js +++ b/app/assets/javascripts/issues/list/utils.js @@ -10,16 +10,16 @@ import { BLOCKING_ISSUES_DESC, CREATED_ASC, CREATED_DESC, - defaultPageSizeParams, DUE_DATE_ASC, DUE_DATE_DESC, filters, LABEL_PRIORITY_ASC, LABEL_PRIORITY_DESC, - largePageSizeParams, MILESTONE_DUE_ASC, MILESTONE_DUE_DESC, NORMAL_FILTER, + PAGE_SIZE, + PAGE_SIZE_MANUAL, POPULARITY_ASC, POPULARITY_DESC, PRIORITY_ASC, @@ -43,8 +43,11 @@ import { WEIGHT_DESC, } from './constants'; -export const getInitialPageParams = (sortKey) => - sortKey === RELATIVE_POSITION_ASC ? largePageSizeParams : defaultPageSizeParams; +export const getInitialPageParams = (sortKey, afterCursor, beforeCursor) => ({ + firstPageSize: sortKey === RELATIVE_POSITION_ASC ? PAGE_SIZE_MANUAL : PAGE_SIZE, + afterCursor, + beforeCursor, +}); export const getSortKey = (sort) => Object.keys(urlSortParams).find((key) => urlSortParams[key] === sort); diff --git a/app/assets/javascripts/issues/show/components/delete_issue_modal.vue b/app/assets/javascripts/issues/show/components/delete_issue_modal.vue index 26862346b86..47b09bd6aa0 100644 --- a/app/assets/javascripts/issues/show/components/delete_issue_modal.vue +++ b/app/assets/javascripts/issues/show/components/delete_issue_modal.vue @@ -31,7 +31,10 @@ export default { computed: { actionPrimary() { return { - attributes: { variant: 'danger' }, + attributes: { + variant: 'danger', + 'data-qa-selector': 'confirm_delete_issue_button', + }, text: this.title, }; }, diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue index eeccf886b65..68ed7bb4062 100644 --- a/app/assets/javascripts/issues/show/components/description.vue +++ b/app/assets/javascripts/issues/show/components/description.vue @@ -10,7 +10,9 @@ import $ from 'jquery'; import createFlash from '~/flash'; import { __, sprintf } from '~/locale'; import TaskList from '~/task_list'; +import Tracking from '~/tracking'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import WorkItemDetailModal from '~/work_items/components/work_item_detail_modal.vue'; import CreateWorkItem from '~/work_items/pages/create_work_item.vue'; import animateMixin from '../mixins/animate'; @@ -24,8 +26,9 @@ export default { GlPopover, CreateWorkItem, GlButton, + WorkItemDetailModal, }, - mixins: [animateMixin, glFeatureFlagMixin()], + mixins: [animateMixin, glFeatureFlagMixin(), Tracking.mixin()], props: { canUpdate: { type: Boolean, @@ -68,9 +71,13 @@ export default { initialUpdate: true, taskButtons: [], activeTask: {}, + workItemId: null, }; }, computed: { + showWorkItemDetailModal() { + return Boolean(this.workItemId); + }, workItemsEnabled() { return this.glFeatures.workItems; }, @@ -194,7 +201,13 @@ export default { closeCreateTaskModal() { this.$refs.modal.hide(); }, - handleCreateTask(title) { + closeWorkItemDetailModal() { + this.workItemId = null; + }, + handleWorkItemDetailModalError(message) { + createFlash({ message }); + }, + handleCreateTask({ id, title, type }) { const listItem = this.$el.querySelector(`#${this.activeTask.id}`).parentElement; const taskBadge = document.createElement('span'); taskBadge.innerHTML = ` @@ -204,12 +217,28 @@ export default { <span class="badge badge-info badge-pill gl-badge sm gl-mr-1"> ${__('Task')} </span> - <a href="#">${title}</a> `; + const button = this.createWorkItemDetailButton(id, title, type); + taskBadge.append(button); + listItem.insertBefore(taskBadge, listItem.lastChild); listItem.removeChild(listItem.lastChild); 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(); }, @@ -262,6 +291,12 @@ export default { @onCreate="handleCreateTask" /> </gl-modal> + <work-item-detail-modal + :visible="showWorkItemDetailModal" + :work-item-id="workItemId" + @close="closeWorkItemDetailModal" + @error="handleWorkItemDetailModalError" + /> <template v-if="workItemsEnabled"> <gl-popover v-for="item in taskButtons" diff --git a/app/assets/javascripts/issues/show/components/fields/description_template.vue b/app/assets/javascripts/issues/show/components/fields/description_template.vue index 9ce49b65a1a..d528641dcb6 100644 --- a/app/assets/javascripts/issues/show/components/fields/description_template.vue +++ b/app/assets/javascripts/issues/show/components/fields/description_template.vue @@ -68,7 +68,10 @@ export default { data-toggle="dropdown" > <span class="dropdown-toggle-text">{{ __('Choose a template') }}</span> - <gl-icon name="chevron-down" class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500" /> + <gl-icon + name="chevron-down" + class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500 dropdown-menu-toggle-icon" + /> </button> <div class="dropdown-menu dropdown-select"> <div class="dropdown-title gl-display-flex gl-justify-content-center"> diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue index 8ba08472ea0..adf449aca7b 100644 --- a/app/assets/javascripts/issues/show/components/header_actions.vue +++ b/app/assets/javascripts/issues/show/components/header_actions.vue @@ -128,13 +128,21 @@ export default { }); }, newIssueTypeText() { - return sprintf(__('New %{issueType}'), { issueType: this.issueType }); + return sprintf(__('New related %{issueType}'), { issueType: this.issueType }); }, showToggleIssueStateButton() { const canClose = !this.isClosed && this.canUpdateIssue; const canReopen = this.isClosed && this.canReopenIssue; return canClose || canReopen; }, + hasDesktopDropdown() { + return ( + this.canCreateIssue || this.canPromoteToEpic || !this.isIssueAuthor || this.canReportSpam + ); + }, + hasMobileDropdown() { + return this.hasDesktopDropdown || this.showToggleIssueStateButton; + }, }, created() { eventHub.$on('toggle.issuable.state', this.toggleIssueState); @@ -223,10 +231,12 @@ export default { <template> <div class="detail-page-header-actions gl-display-flex"> <gl-dropdown + v-if="hasMobileDropdown" class="gl-sm-display-none! w-100" block :text="dropdownText" data-qa-selector="issue_actions_dropdown" + data-testid="mobile-dropdown" :loading="isToggleStateButtonLoading" > <gl-dropdown-item @@ -276,11 +286,14 @@ export default { </gl-button> <gl-dropdown + v-if="hasDesktopDropdown" class="gl-display-none gl-sm-display-inline-flex! gl-ml-3" icon="ellipsis_v" category="tertiary" + data-qa-selector="issue_actions_ellipsis_dropdown" :text="dropdownText" :text-sr-only="true" + data-testid="desktop-dropdown" no-caret right > @@ -311,6 +324,7 @@ export default { <gl-dropdown-item v-gl-modal="$options.deleteModalId" variant="danger" + data-qa-selector="delete_issue_button" @click="track('click_dropdown')" > {{ deleteButtonText }} diff --git a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue index 4790062ab7d..04ddc7f3501 100644 --- a/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue +++ b/app/assets/javascripts/issues/show/components/incidents/incident_tabs.vue @@ -5,6 +5,7 @@ import { trackIncidentDetailsViewsOptions } from '~/incidents/constants'; import { s__ } from '~/locale'; import Tracking from '~/tracking'; import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import DescriptionComponent from '../description.vue'; import getAlert from './graphql/queries/get_alert.graphql'; import HighlightBar from './highlight_bar.vue'; @@ -17,7 +18,10 @@ export default { GlTabs, HighlightBar, MetricsTab: () => import('ee_component/issues/show/components/incidents/metrics_tab.vue'), + TimelineTab: () => + import('ee_component/issues/show/components/incidents/timeline_events_tab.vue'), }, + mixins: [glFeatureFlagsMixin()], inject: ['fullPath', 'iid', 'uploadMetricsFeatureAvailable'], apollo: { alert: { @@ -47,6 +51,9 @@ export default { loading() { return this.$apollo.queries.alert.loading; }, + incidentTabEnabled() { + return this.glFeatures.incidentTimelineEvents && this.glFeatures.incidentTimelineEventTab; + }, }, mounted() { this.trackPageViews(); @@ -76,6 +83,7 @@ export default { > <alert-details-table :alert="alert" :loading="loading" /> </gl-tab> + <timeline-tab v-if="incidentTabEnabled" data-testid="timeline-events-tab" /> </gl-tabs> </div> </template> diff --git a/app/assets/javascripts/issues/show/components/title.vue b/app/assets/javascripts/issues/show/components/title.vue index 5e92211685a..1982147e454 100644 --- a/app/assets/javascripts/issues/show/components/title.vue +++ b/app/assets/javascripts/issues/show/components/title.vue @@ -68,7 +68,7 @@ export default { <template> <div class="title-container"> - <h2 + <h1 v-safe-html="titleHtml" :class="{ 'issue-realtime-pre-pulse': preAnimation, @@ -76,7 +76,7 @@ export default { }" class="title qa-title" dir="auto" - ></h2> + ></h1> <gl-button v-if="showInlineEditButton && canUpdate" v-gl-tooltip.bottom diff --git a/app/assets/javascripts/issues/show/index.js b/app/assets/javascripts/issues/show/index.js index f5c71f9691f..c9af5d9b4a7 100644 --- a/app/assets/javascripts/issues/show/index.js +++ b/app/assets/javascripts/issues/show/index.js @@ -77,9 +77,7 @@ export function initIssueApp(issueData, store) { const { fullPath } = el.dataset; - if (gon?.features?.fixCommentScroll) { - scrollToTargetOnResize(); - } + scrollToTargetOnResize(); bootstrapApollo({ ...issueState, issueType: el.dataset.issueType }); |