diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-09 21:14:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-09 21:14:48 +0300 |
commit | f6115a0f2ce347bab74ff5951cf828196d715b66 (patch) | |
tree | 213cc1923cb8af7bc5a4f3e0c49cff3271544d6d /app/assets/javascripts/issues | |
parent | 750deb250143d48aba32fb437ba8838fbef3fde3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/issues')
10 files changed, 291 insertions, 2 deletions
diff --git a/app/assets/javascripts/issues/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/issues/related_merge_requests/components/related_merge_requests.vue new file mode 100644 index 00000000000..e8d0d538362 --- /dev/null +++ b/app/assets/javascripts/issues/related_merge_requests/components/related_merge_requests.vue @@ -0,0 +1,120 @@ +<script> +import { GlLink, GlLoadingIcon, GlIcon } from '@gitlab/ui'; +import { mapState, mapActions } from 'vuex'; +import { sprintf, __, n__ } from '~/locale'; +import RelatedIssuableItem from '~/vue_shared/components/issue/related_issuable_item.vue'; +import { parseIssuableData } from '~/issues/show/utils/parse_data'; + +export default { + name: 'RelatedMergeRequests', + components: { + GlIcon, + GlLink, + GlLoadingIcon, + RelatedIssuableItem, + }, + props: { + endpoint: { + type: String, + required: true, + }, + projectNamespace: { + type: String, + required: true, + }, + projectPath: { + type: String, + required: true, + }, + }, + computed: { + ...mapState(['isFetchingMergeRequests', 'mergeRequests', 'totalCount']), + closingMergeRequestsText() { + if (!this.hasClosingMergeRequest) { + return ''; + } + + const mrText = n__( + 'When this merge request is accepted', + 'When these merge requests are accepted', + this.totalCount, + ); + + return sprintf(__('%{mrText}, this issue will be closed automatically.'), { mrText }); + }, + }, + mounted() { + this.setInitialState({ apiEndpoint: this.endpoint }); + this.fetchMergeRequests(); + }, + created() { + this.hasClosingMergeRequest = parseIssuableData().hasClosingMergeRequest; + }, + methods: { + ...mapActions(['setInitialState', 'fetchMergeRequests']), + getAssignees(mr) { + if (mr.assignees) { + return mr.assignees; + } + + return mr.assignee ? [mr.assignee] : []; + }, + }, +}; +</script> + +<template> + <div v-if="isFetchingMergeRequests || (!isFetchingMergeRequests && totalCount)"> + <div class="card card-slim gl-mt-5"> + <div class="card-header"> + <div + class="card-title gl-relative gl-display-flex gl-align-items-center gl-line-height-20 gl-font-weight-bold gl-m-0" + > + <gl-link + class="anchor gl-absolute gl-text-decoration-none" + href="#related-merge-requests" + aria-labelledby="related-merge-requests" + /> + <h3 id="related-merge-requests" class="gl-font-base gl-m-0"> + {{ __('Related merge requests') }} + </h3> + <template v-if="totalCount"> + <gl-icon name="merge-request" class="gl-ml-5 gl-mr-2 gl-text-gray-500" /> + <span data-testid="count">{{ totalCount }}</span> + </template> + </div> + </div> + <gl-loading-icon + v-if="isFetchingMergeRequests" + size="sm" + label="Fetching related merge requests" + class="gl-py-3" + /> + <ul v-else class="content-list related-items-list"> + <li v-for="mr in mergeRequests" :key="mr.id" class="list-item gl-m-0! gl-p-0!"> + <related-issuable-item + :id-key="mr.id" + :display-reference="mr.reference" + :title="mr.title" + :milestone="mr.milestone" + :assignees="getAssignees(mr)" + :created-at="mr.created_at" + :closed-at="mr.closed_at" + :merged-at="mr.merged_at" + :path="mr.web_url" + :state="mr.state" + :is-merge-request="true" + :pipeline-status="mr.head_pipeline && mr.head_pipeline.detailed_status" + path-id-separator="!" + /> + </li> + </ul> + </div> + <div + v-if="hasClosingMergeRequest && !isFetchingMergeRequests" + class="issue-closed-by-widget second-block" + > + {{ closingMergeRequestsText }} + </div> + </div> +</template> diff --git a/app/assets/javascripts/issues/related_merge_requests/index.js b/app/assets/javascripts/issues/related_merge_requests/index.js new file mode 100644 index 00000000000..ce33cf7df1d --- /dev/null +++ b/app/assets/javascripts/issues/related_merge_requests/index.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import RelatedMergeRequests from './components/related_merge_requests.vue'; +import createStore from './store'; + +export default function initRelatedMergeRequests() { + const relatedMergeRequestsElement = document.querySelector('#js-related-merge-requests'); + + if (relatedMergeRequestsElement) { + const { endpoint, projectPath, projectNamespace } = relatedMergeRequestsElement.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el: relatedMergeRequestsElement, + components: { + RelatedMergeRequests, + }, + store: createStore(), + render: (createElement) => + createElement('related-merge-requests', { + props: { endpoint, projectNamespace, projectPath }, + }), + }); + } +} diff --git a/app/assets/javascripts/issues/related_merge_requests/store/actions.js b/app/assets/javascripts/issues/related_merge_requests/store/actions.js new file mode 100644 index 00000000000..94abb50de89 --- /dev/null +++ b/app/assets/javascripts/issues/related_merge_requests/store/actions.js @@ -0,0 +1,36 @@ +import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { normalizeHeaders } from '~/lib/utils/common_utils'; +import { __ } from '~/locale'; +import * as types from './mutation_types'; + +const REQUEST_PAGE_COUNT = 100; + +export const setInitialState = ({ commit }, props) => { + commit(types.SET_INITIAL_STATE, props); +}; + +export const requestData = ({ commit }) => commit(types.REQUEST_DATA); + +export const receiveDataSuccess = ({ commit }, data) => commit(types.RECEIVE_DATA_SUCCESS, data); + +export const receiveDataError = ({ commit }) => commit(types.RECEIVE_DATA_ERROR); + +export const fetchMergeRequests = ({ state, dispatch }) => { + dispatch('requestData'); + + return axios + .get(`${state.apiEndpoint}?per_page=${REQUEST_PAGE_COUNT}`) + .then((res) => { + const { headers, data } = res; + const total = Number(normalizeHeaders(headers)['X-TOTAL']) || 0; + + dispatch('receiveDataSuccess', { data, total }); + }) + .catch(() => { + dispatch('receiveDataError'); + createFlash({ + message: __('Something went wrong while fetching related merge requests.'), + }); + }); +}; diff --git a/app/assets/javascripts/issues/related_merge_requests/store/index.js b/app/assets/javascripts/issues/related_merge_requests/store/index.js new file mode 100644 index 00000000000..925cc36cd76 --- /dev/null +++ b/app/assets/javascripts/issues/related_merge_requests/store/index.js @@ -0,0 +1,14 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import * as actions from './actions'; +import mutations from './mutations'; +import createState from './state'; + +Vue.use(Vuex); + +export default () => + new Vuex.Store({ + state: createState(), + actions, + mutations, + }); diff --git a/app/assets/javascripts/issues/related_merge_requests/store/mutation_types.js b/app/assets/javascripts/issues/related_merge_requests/store/mutation_types.js new file mode 100644 index 00000000000..31d4fe032e1 --- /dev/null +++ b/app/assets/javascripts/issues/related_merge_requests/store/mutation_types.js @@ -0,0 +1,4 @@ +export const SET_INITIAL_STATE = 'SET_INITIAL_STATE'; +export const REQUEST_DATA = 'REQUEST_DATA'; +export const RECEIVE_DATA_SUCCESS = 'RECEIVE_DATA_SUCCESS'; +export const RECEIVE_DATA_ERROR = 'RECEIVE_DATA_ERROR'; diff --git a/app/assets/javascripts/issues/related_merge_requests/store/mutations.js b/app/assets/javascripts/issues/related_merge_requests/store/mutations.js new file mode 100644 index 00000000000..11ca28a5fb9 --- /dev/null +++ b/app/assets/javascripts/issues/related_merge_requests/store/mutations.js @@ -0,0 +1,19 @@ +import * as types from './mutation_types'; + +export default { + [types.SET_INITIAL_STATE](state, { apiEndpoint }) { + state.apiEndpoint = apiEndpoint; + }, + [types.REQUEST_DATA](state) { + state.isFetchingMergeRequests = true; + }, + [types.RECEIVE_DATA_SUCCESS](state, { data, total }) { + state.isFetchingMergeRequests = false; + state.mergeRequests = data; + state.totalCount = total; + }, + [types.RECEIVE_DATA_ERROR](state) { + state.isFetchingMergeRequests = false; + state.hasErrorFetchingMergeRequests = true; + }, +}; diff --git a/app/assets/javascripts/issues/related_merge_requests/store/state.js b/app/assets/javascripts/issues/related_merge_requests/store/state.js new file mode 100644 index 00000000000..bc3468a025b --- /dev/null +++ b/app/assets/javascripts/issues/related_merge_requests/store/state.js @@ -0,0 +1,7 @@ +export default () => ({ + apiEndpoint: '', + isFetchingMergeRequests: false, + hasErrorFetchingMergeRequests: false, + mergeRequests: [], + totalCount: 0, +}); diff --git a/app/assets/javascripts/issues/sentry_error_stack_trace/components/sentry_error_stack_trace.vue b/app/assets/javascripts/issues/sentry_error_stack_trace/components/sentry_error_stack_trace.vue new file mode 100644 index 00000000000..1530e9a15b5 --- /dev/null +++ b/app/assets/javascripts/issues/sentry_error_stack_trace/components/sentry_error_stack_trace.vue @@ -0,0 +1,43 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; +import { mapActions, mapState, mapGetters } from 'vuex'; +import Stacktrace from '~/error_tracking/components/stacktrace.vue'; + +export default { + name: 'SentryErrorStackTrace', + components: { + Stacktrace, + GlLoadingIcon, + }, + props: { + issueStackTracePath: { + type: String, + required: true, + }, + }, + computed: { + ...mapState('details', ['loadingStacktrace', 'stacktraceData']), + ...mapGetters('details', ['stacktrace']), + }, + mounted() { + this.startPollingStacktrace(this.issueStackTracePath); + }, + methods: { + ...mapActions('details', ['startPollingStacktrace']), + }, +}; +</script> + +<template> + <div> + <div :class="{ 'border-bottom-0': loadingStacktrace }" class="card card-slim mt-4 mb-0"> + <div class="card-header border-bottom-0"> + <h5 class="card-title my-1">{{ __('Stack trace') }}</h5> + </div> + </div> + <div v-if="loadingStacktrace" class="card"> + <gl-loading-icon class="py-2" label="Fetching stack trace" size="sm" /> + </div> + <stacktrace v-else :entries="stacktrace" /> + </div> +</template> diff --git a/app/assets/javascripts/issues/sentry_error_stack_trace/index.js b/app/assets/javascripts/issues/sentry_error_stack_trace/index.js new file mode 100644 index 00000000000..8e9ee25e7a8 --- /dev/null +++ b/app/assets/javascripts/issues/sentry_error_stack_trace/index.js @@ -0,0 +1,22 @@ +import Vue from 'vue'; +import store from '~/error_tracking/store'; +import SentryErrorStackTrace from './components/sentry_error_stack_trace.vue'; + +export default function initSentryErrorStacktrace() { + const sentryErrorStackTraceEl = document.querySelector('#js-sentry-error-stack-trace'); + if (sentryErrorStackTraceEl) { + const { issueStackTracePath } = sentryErrorStackTraceEl.dataset; + // eslint-disable-next-line no-new + new Vue({ + el: sentryErrorStackTraceEl, + components: { + SentryErrorStackTrace, + }, + store, + render: (createElement) => + createElement('sentry-error-stack-trace', { + props: { issueStackTracePath }, + }), + }); + } +} diff --git a/app/assets/javascripts/issues/show.js b/app/assets/javascripts/issues/show.js index 33b1c47b4fe..0c02d137f29 100644 --- a/app/assets/javascripts/issues/show.js +++ b/app/assets/javascripts/issues/show.js @@ -8,8 +8,8 @@ import { initIssuableApp, initIssueHeaderActions } from '~/issues/show/issue'; import { parseIssuableData } from '~/issues/show/utils/parse_data'; import initNotesApp from '~/notes'; import { store } from '~/notes/stores'; -import initRelatedMergeRequestsApp from '~/related_merge_requests'; -import initSentryErrorStackTraceApp from '~/sentry_error_stack_trace'; +import initRelatedMergeRequestsApp from '~/issues/related_merge_requests'; +import initSentryErrorStackTraceApp from '~/issues/sentry_error_stack_trace'; import initIssuableHeaderWarning from '~/issuable/init_issuable_header_warning'; import ZenMode from '~/zen_mode'; |