Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 13:34:06 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-02-18 13:34:06 +0300
commit859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch)
treed7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/assets/javascripts/projects
parent446d496a6d000c73a304be52587cd9bbc7493136 (diff)
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/assets/javascripts/projects')
-rw-r--r--app/assets/javascripts/projects/commit/components/branches_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/commit/components/form_modal.vue5
-rw-r--r--app/assets/javascripts/projects/commit/components/form_trigger.vue5
-rw-r--r--app/assets/javascripts/projects/commit/constants.js9
-rw-r--r--app/assets/javascripts/projects/commit/index.js11
-rw-r--r--app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js51
-rw-r--r--app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js20
-rw-r--r--app/assets/javascripts/projects/commit/init_revert_commit_modal.js4
-rw-r--r--app/assets/javascripts/projects/commit/init_revert_commit_trigger.js8
-rw-r--r--app/assets/javascripts/projects/commit/store/actions.js4
-rw-r--r--app/assets/javascripts/projects/commit_box/info/index.js4
-rw-r--r--app/assets/javascripts/projects/commits/components/author_select.vue6
-rw-r--r--app/assets/javascripts/projects/commits/store/actions.js8
-rw-r--r--app/assets/javascripts/projects/compare/components/app.vue89
-rw-r--r--app/assets/javascripts/projects/compare/components/revision_dropdown.vue145
-rw-r--r--app/assets/javascripts/projects/compare/index.js33
-rw-r--r--app/assets/javascripts/projects/components/project_delete_button.vue4
-rw-r--r--app/assets/javascripts/projects/components/shared/delete_button.vue4
-rw-r--r--app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue6
-rw-r--r--app/assets/javascripts/projects/experiment_new_project_creation/index.js2
-rw-r--r--app/assets/javascripts/projects/members/constants.js1
-rw-r--r--app/assets/javascripts/projects/members/utils.js8
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/app.vue203
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue50
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue233
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/constants.js2
-rw-r--r--app/assets/javascripts/projects/settings/access_dropdown.js87
-rw-r--r--app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue2
-rw-r--r--app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js2
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue67
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue20
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/event_hub.js3
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/index.js70
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js23
-rw-r--r--app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue6
35 files changed, 772 insertions, 425 deletions
diff --git a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
index 3ecc3f1d1d3..3527bcb04c6 100644
--- a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
+++ b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
@@ -68,6 +68,7 @@ export default {
autocomplete="off"
:debounce="250"
:placeholder="$options.i18n.searchPlaceholder"
+ data-testid="dropdown-search-box"
@input="searchTermChanged"
/>
<gl-dropdown-item
@@ -77,6 +78,7 @@ export default {
:name="branch"
:is-checked="isSelected(branch)"
is-check-item
+ data-testid="dropdown-item"
@click="selectBranch(branch)"
>
{{ branch }}
diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue
index 6411b1ca921..ed216a91ca0 100644
--- a/app/assets/javascripts/projects/commit/components/form_modal.vue
+++ b/app/assets/javascripts/projects/commit/components/form_modal.vue
@@ -1,8 +1,9 @@
<script>
import { GlModal, GlForm, GlFormCheckbox, GlSprintf, GlFormGroup } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
-import eventHub from '../event_hub';
+import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import csrf from '~/lib/utils/csrf';
+import eventHub from '../event_hub';
import BranchesDropdown from './branches_dropdown.vue';
export default {
@@ -67,7 +68,7 @@ export default {
methods: {
...mapActions(['clearModal', 'setBranch', 'setSelectedBranch']),
show() {
- this.$root.$emit('bv::show::modal', this.modalId);
+ this.$root.$emit(BV_SHOW_MODAL, this.modalId);
},
handlePrimary() {
this.$refs.form.$el.submit();
diff --git a/app/assets/javascripts/projects/commit/components/form_trigger.vue b/app/assets/javascripts/projects/commit/components/form_trigger.vue
index e92854c1ac3..3561b5c2473 100644
--- a/app/assets/javascripts/projects/commit/components/form_trigger.vue
+++ b/app/assets/javascripts/projects/commit/components/form_trigger.vue
@@ -10,6 +10,9 @@ export default {
displayText: {
default: '',
},
+ testId: {
+ default: '',
+ },
},
props: {
openModal: {
@@ -26,7 +29,7 @@ export default {
</script>
<template>
- <gl-link data-is-link="true" data-testid="revert-commit-link" @click="showModal">
+ <gl-link data-is-link="true" :data-testid="testId" @click="showModal">
{{ displayText }}
</gl-link>
</template>
diff --git a/app/assets/javascripts/projects/commit/constants.js b/app/assets/javascripts/projects/commit/constants.js
index 233f43d56b9..b47c744e5fb 100644
--- a/app/assets/javascripts/projects/commit/constants.js
+++ b/app/assets/javascripts/projects/commit/constants.js
@@ -2,6 +2,10 @@ import { s__, __ } from '~/locale';
export const OPEN_REVERT_MODAL = 'openRevertModal';
export const REVERT_MODAL_ID = 'revert-commit-modal';
+export const REVERT_LINK_TEST_ID = 'revert-commit-link';
+export const OPEN_CHERRY_PICK_MODAL = 'openCherryPickModal';
+export const CHERRY_PICK_MODAL_ID = 'cherry-pick-commit-modal';
+export const CHERRY_PICK_LINK_TEST_ID = 'cherry-pick-commit-link';
export const I18N_MODAL = {
startMergeRequest: s__('ChangeTypeAction|Start a %{newMergeRequest} with these changes'),
@@ -20,6 +24,11 @@ export const I18N_REVERT_MODAL = {
actionPrimaryText: s__('ChangeTypeAction|Revert'),
};
+export const I18N_CHERRY_PICK_MODAL = {
+ branchLabel: s__('ChangeTypeAction|Pick into branch'),
+ actionPrimaryText: s__('ChangeTypeAction|Cherry-pick'),
+};
+
export const PREPENDED_MODAL_TEXT = s__(
'ChangeTypeAction|This will create a new commit in order to revert the existing changes.',
);
diff --git a/app/assets/javascripts/projects/commit/index.js b/app/assets/javascripts/projects/commit/index.js
new file mode 100644
index 00000000000..b5fdfc25236
--- /dev/null
+++ b/app/assets/javascripts/projects/commit/index.js
@@ -0,0 +1,11 @@
+import initCherryPickCommitModal from './init_cherry_pick_commit_modal';
+import initCherryPickCommitTrigger from './init_cherry_pick_commit_trigger';
+import initRevertCommitModal from './init_revert_commit_modal';
+import initRevertCommitTrigger from './init_revert_commit_trigger';
+
+export default () => {
+ initRevertCommitModal();
+ initRevertCommitTrigger();
+ initCherryPickCommitModal();
+ initCherryPickCommitTrigger();
+};
diff --git a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js
new file mode 100644
index 00000000000..24baa27ff70
--- /dev/null
+++ b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js
@@ -0,0 +1,51 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import CommitFormModal from './components/form_modal.vue';
+import {
+ I18N_MODAL,
+ I18N_CHERRY_PICK_MODAL,
+ OPEN_CHERRY_PICK_MODAL,
+ CHERRY_PICK_MODAL_ID,
+} from './constants';
+import createStore from './store';
+
+export default function initInviteMembersModal() {
+ const el = document.querySelector('.js-cherry-pick-commit-modal');
+ if (!el) {
+ return false;
+ }
+
+ const {
+ title,
+ endpoint,
+ branch,
+ pushCode,
+ branchCollaboration,
+ existingBranch,
+ branchesEndpoint,
+ } = el.dataset;
+
+ const store = createStore({
+ endpoint,
+ branchesEndpoint,
+ branch,
+ pushCode: parseBoolean(pushCode),
+ branchCollaboration: parseBoolean(branchCollaboration),
+ defaultBranch: branch,
+ modalTitle: title,
+ existingBranch,
+ });
+
+ return new Vue({
+ el,
+ store,
+ render: (createElement) =>
+ createElement(CommitFormModal, {
+ props: {
+ i18n: { ...I18N_CHERRY_PICK_MODAL, ...I18N_MODAL },
+ openModal: OPEN_CHERRY_PICK_MODAL,
+ modalId: CHERRY_PICK_MODAL_ID,
+ },
+ }),
+ });
+}
diff --git a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js
new file mode 100644
index 00000000000..942451dc96a
--- /dev/null
+++ b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_trigger.js
@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import CommitFormTrigger from './components/form_trigger.vue';
+import { OPEN_CHERRY_PICK_MODAL, CHERRY_PICK_LINK_TEST_ID } from './constants';
+
+export default function initInviteMembersTrigger() {
+ const el = document.querySelector('.js-cherry-pick-commit-trigger');
+
+ if (!el) {
+ return false;
+ }
+
+ const { displayText } = el.dataset;
+
+ return new Vue({
+ el,
+ provide: { displayText, testId: CHERRY_PICK_LINK_TEST_ID },
+ render: (createElement) =>
+ createElement(CommitFormTrigger, { props: { openModal: OPEN_CHERRY_PICK_MODAL } }),
+ });
+}
diff --git a/app/assets/javascripts/projects/commit/init_revert_commit_modal.js b/app/assets/javascripts/projects/commit/init_revert_commit_modal.js
index ec0600cd25a..df26aa3c830 100644
--- a/app/assets/javascripts/projects/commit/init_revert_commit_modal.js
+++ b/app/assets/javascripts/projects/commit/init_revert_commit_modal.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
-import CommitFormModal from './components/form_modal.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
-import createStore from './store';
+import CommitFormModal from './components/form_modal.vue';
import {
I18N_MODAL,
I18N_REVERT_MODAL,
@@ -9,6 +8,7 @@ import {
OPEN_REVERT_MODAL,
REVERT_MODAL_ID,
} from './constants';
+import createStore from './store';
export default function initInviteMembersModal() {
const el = document.querySelector('.js-revert-commit-modal');
diff --git a/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js b/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js
index 0bb57f22663..dc5168524ca 100644
--- a/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js
+++ b/app/assets/javascripts/projects/commit/init_revert_commit_trigger.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import RevertCommitTrigger from './components/form_trigger.vue';
-import { OPEN_REVERT_MODAL } from './constants';
+import CommitFormTrigger from './components/form_trigger.vue';
+import { OPEN_REVERT_MODAL, REVERT_LINK_TEST_ID } from './constants';
export default function initInviteMembersTrigger() {
const el = document.querySelector('.js-revert-commit-trigger');
@@ -13,8 +13,8 @@ export default function initInviteMembersTrigger() {
return new Vue({
el,
- provide: { displayText },
+ provide: { displayText, testId: REVERT_LINK_TEST_ID },
render: (createElement) =>
- createElement(RevertCommitTrigger, { props: { openModal: OPEN_REVERT_MODAL } }),
+ createElement(CommitFormTrigger, { props: { openModal: OPEN_REVERT_MODAL } }),
});
}
diff --git a/app/assets/javascripts/projects/commit/store/actions.js b/app/assets/javascripts/projects/commit/store/actions.js
index 2ae0370d579..10135e55351 100644
--- a/app/assets/javascripts/projects/commit/store/actions.js
+++ b/app/assets/javascripts/projects/commit/store/actions.js
@@ -1,7 +1,7 @@
-import * as types from './mutation_types';
-import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import { PROJECT_BRANCHES_ERROR } from '../constants';
+import * as types from './mutation_types';
export const clearModal = ({ commit }) => {
commit(types.CLEAR_MODAL);
diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js
index 254d178f013..4bbdb5c2357 100644
--- a/app/assets/javascripts/projects/commit_box/info/index.js
+++ b/app/assets/javascripts/projects/commit_box/info/index.js
@@ -1,7 +1,7 @@
-import { loadBranches } from './load_branches';
-import { initDetailsButton } from './init_details_button';
import { fetchCommitMergeRequests } from '~/commit_merge_requests';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
+import { initDetailsButton } from './init_details_button';
+import { loadBranches } from './load_branches';
export const initCommitBoxInfo = (containerSelector = '.js-commit-box-info') => {
const containerEl = document.querySelector(containerSelector);
diff --git a/app/assets/javascripts/projects/commits/components/author_select.vue b/app/assets/javascripts/projects/commits/components/author_select.vue
index 752bb594794..1566232751d 100644
--- a/app/assets/javascripts/projects/commits/components/author_select.vue
+++ b/app/assets/javascripts/projects/commits/components/author_select.vue
@@ -1,6 +1,4 @@
<script>
-import { debounce } from 'lodash';
-import { mapState, mapActions } from 'vuex';
import {
GlDropdown,
GlDropdownSectionHeader,
@@ -9,8 +7,10 @@ import {
GlDropdownDivider,
GlTooltipDirective,
} from '@gitlab/ui';
-import { redirectTo } from '~/lib/utils/url_utility';
+import { debounce } from 'lodash';
+import { mapState, mapActions } from 'vuex';
import { urlParamsToObject } from '~/lib/utils/common_utils';
+import { redirectTo } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
const tooltipMessage = __('Searching by both author and message is currently not supported.');
diff --git a/app/assets/javascripts/projects/commits/store/actions.js b/app/assets/javascripts/projects/commits/store/actions.js
index 359d81f32f7..72d4f0c31e5 100644
--- a/app/assets/javascripts/projects/commits/store/actions.js
+++ b/app/assets/javascripts/projects/commits/store/actions.js
@@ -1,9 +1,9 @@
-import * as Sentry from '~/sentry/wrapper';
-import * as types from './mutation_types';
-import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
-import { __ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
import { joinPaths } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
+import * as Sentry from '~/sentry/wrapper';
+import * as types from './mutation_types';
export default {
setInitialData({ commit }, data) {
diff --git a/app/assets/javascripts/projects/compare/components/app.vue b/app/assets/javascripts/projects/compare/components/app.vue
new file mode 100644
index 00000000000..05bd0f1370b
--- /dev/null
+++ b/app/assets/javascripts/projects/compare/components/app.vue
@@ -0,0 +1,89 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import csrf from '~/lib/utils/csrf';
+import RevisionDropdown from './revision_dropdown.vue';
+
+export default {
+ csrf,
+ components: {
+ RevisionDropdown,
+ GlButton,
+ },
+ props: {
+ projectCompareIndexPath: {
+ type: String,
+ required: true,
+ },
+ refsProjectPath: {
+ type: String,
+ required: true,
+ },
+ paramsFrom: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ paramsTo: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ projectMergeRequestPath: {
+ type: String,
+ required: true,
+ },
+ createMrPath: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ onSubmit() {
+ this.$refs.form.submit();
+ },
+ },
+};
+</script>
+
+<template>
+ <form
+ ref="form"
+ class="form-inline js-requires-input js-signature-container"
+ method="POST"
+ :action="projectCompareIndexPath"
+ >
+ <input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
+ <revision-dropdown
+ :refs-project-path="refsProjectPath"
+ revision-text="Source"
+ params-name="to"
+ :params-branch="paramsTo"
+ />
+ <div class="compare-ellipsis gl-display-inline" data-testid="ellipsis">...</div>
+ <revision-dropdown
+ :refs-project-path="refsProjectPath"
+ revision-text="Target"
+ params-name="from"
+ :params-branch="paramsFrom"
+ />
+ <gl-button category="primary" variant="success" class="gl-ml-3" @click="onSubmit">
+ {{ s__('CompareRevisions|Compare') }}
+ </gl-button>
+ <a
+ v-if="projectMergeRequestPath"
+ :href="projectMergeRequestPath"
+ data-testid="projectMrButton"
+ class="btn btn-default gl-button gl-ml-3"
+ >
+ {{ s__('CompareRevisions|View open merge request') }}
+ </a>
+ <a
+ v-else-if="createMrPath"
+ :href="createMrPath"
+ data-testid="createMrButton"
+ class="btn btn-default gl-button gl-ml-3"
+ >
+ {{ s__('CompareRevisions|Create merge request') }}
+ </a>
+ </form>
+</template>
diff --git a/app/assets/javascripts/projects/compare/components/revision_dropdown.vue b/app/assets/javascripts/projects/compare/components/revision_dropdown.vue
new file mode 100644
index 00000000000..13d80b5ae0b
--- /dev/null
+++ b/app/assets/javascripts/projects/compare/components/revision_dropdown.vue
@@ -0,0 +1,145 @@
+<script>
+import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlDropdownSectionHeader } from '@gitlab/ui';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlDropdownSectionHeader,
+ GlSearchBoxByType,
+ },
+ props: {
+ refsProjectPath: {
+ type: String,
+ required: true,
+ },
+ revisionText: {
+ type: String,
+ required: true,
+ },
+ paramsName: {
+ type: String,
+ required: true,
+ },
+ paramsBranch: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+ data() {
+ return {
+ branches: [],
+ tags: [],
+ loading: true,
+ searchTerm: '',
+ selectedRevision: this.getDefaultBranch(),
+ };
+ },
+ computed: {
+ filteredBranches() {
+ return this.branches.filter((branch) =>
+ branch.toLowerCase().includes(this.searchTerm.toLowerCase()),
+ );
+ },
+ hasFilteredBranches() {
+ return this.filteredBranches.length;
+ },
+ filteredTags() {
+ return this.tags.filter((tag) => tag.toLowerCase().includes(this.searchTerm.toLowerCase()));
+ },
+ hasFilteredTags() {
+ return this.filteredTags.length;
+ },
+ },
+ mounted() {
+ this.fetchBranchesAndTags();
+ },
+ methods: {
+ fetchBranchesAndTags() {
+ const endpoint = this.refsProjectPath;
+
+ return axios
+ .get(endpoint)
+ .then(({ data }) => {
+ this.branches = data.Branches || [];
+ this.tags = data.Tags || [];
+ })
+ .catch(() => {
+ createFlash({
+ message: `${s__(
+ 'CompareRevisions|There was an error while updating the branch/tag list. Please try again.',
+ )}`,
+ });
+ })
+ .finally(() => {
+ this.loading = false;
+ });
+ },
+ getDefaultBranch() {
+ return this.paramsBranch || s__('CompareRevisions|Select branch/tag');
+ },
+ onClick(revision) {
+ this.selectedRevision = revision;
+ },
+ onSearchEnter() {
+ this.selectedRevision = this.searchTerm;
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="form-group compare-form-group" :class="`js-compare-${paramsName}-dropdown`">
+ <div class="input-group inline-input-group">
+ <span class="input-group-prepend">
+ <div class="input-group-text">
+ {{ revisionText }}
+ </div>
+ </span>
+ <input type="hidden" :name="paramsName" :value="selectedRevision" />
+ <gl-dropdown
+ class="gl-flex-grow-1 gl-flex-basis-0 gl-min-w-0 gl-font-monospace"
+ toggle-class="form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0 gl-rounded-top-left-none! gl-rounded-bottom-left-none!"
+ :text="selectedRevision"
+ header-text="Select Git revision"
+ :loading="loading"
+ >
+ <template #header>
+ <gl-search-box-by-type
+ v-model.trim="searchTerm"
+ :placeholder="s__('CompareRevisions|Filter by Git revision')"
+ @keyup.enter="onSearchEnter"
+ />
+ </template>
+ <gl-dropdown-section-header v-if="hasFilteredBranches">
+ {{ s__('CompareRevisions|Branches') }}
+ </gl-dropdown-section-header>
+ <gl-dropdown-item
+ v-for="(branch, index) in filteredBranches"
+ :key="`branch${index}`"
+ is-check-item
+ :is-checked="selectedRevision === branch"
+ @click="onClick(branch)"
+ >
+ {{ branch }}
+ </gl-dropdown-item>
+ <gl-dropdown-section-header v-if="hasFilteredTags">
+ {{ s__('CompareRevisions|Tags') }}
+ </gl-dropdown-section-header>
+ <gl-dropdown-item
+ v-for="(tag, index) in filteredTags"
+ :key="`tag${index}`"
+ is-check-item
+ :is-checked="selectedRevision === tag"
+ @click="onClick(tag)"
+ >
+ {{ tag }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/compare/index.js b/app/assets/javascripts/projects/compare/index.js
new file mode 100644
index 00000000000..4337eecb667
--- /dev/null
+++ b/app/assets/javascripts/projects/compare/index.js
@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import CompareApp from './components/app.vue';
+
+export default function init() {
+ const el = document.getElementById('js-compare-selector');
+ const {
+ refsProjectPath,
+ paramsFrom,
+ paramsTo,
+ projectCompareIndexPath,
+ projectMergeRequestPath,
+ createMrPath,
+ } = el.dataset;
+
+ return new Vue({
+ el,
+ components: {
+ CompareApp,
+ },
+ render(createElement) {
+ return createElement(CompareApp, {
+ props: {
+ refsProjectPath,
+ paramsFrom,
+ paramsTo,
+ projectCompareIndexPath,
+ projectMergeRequestPath,
+ createMrPath,
+ },
+ });
+ },
+ });
+}
diff --git a/app/assets/javascripts/projects/components/project_delete_button.vue b/app/assets/javascripts/projects/components/project_delete_button.vue
index 5429d51dae0..81d23a563e2 100644
--- a/app/assets/javascripts/projects/components/project_delete_button.vue
+++ b/app/assets/javascripts/projects/components/project_delete_button.vue
@@ -22,10 +22,10 @@ export default {
strings: {
alertTitle: __('You are about to permanently delete this project'),
alertBody: __(
- 'Once a project is permanently deleted it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd} including issues, merge requests etc.',
+ 'Once a project is permanently deleted, it %{strongStart}cannot be recovered%{strongEnd}. Permanently deleting this project will %{strongStart}immediately delete%{strongEnd} its repositories and %{strongStart}all related resources%{strongEnd}, including issues, merge requests etc.',
),
modalBody: __(
- "This action cannot be undone. You will lose this project's repository and all content: issues, merge requests, etc.",
+ "This action cannot be undone. You will lose this project's repository and all related resources, including issues, merge requests, etc.",
),
},
};
diff --git a/app/assets/javascripts/projects/components/shared/delete_button.vue b/app/assets/javascripts/projects/components/shared/delete_button.vue
index 051bfcb732a..2e46f437ace 100644
--- a/app/assets/javascripts/projects/components/shared/delete_button.vue
+++ b/app/assets/javascripts/projects/components/shared/delete_button.vue
@@ -1,8 +1,8 @@
<script>
-import { uniqueId } from 'lodash';
import { GlModal, GlModalDirective, GlFormInput, GlButton } from '@gitlab/ui';
-import { __ } from '~/locale';
+import { uniqueId } from 'lodash';
import csrf from '~/lib/utils/csrf';
+import { __ } from '~/locale';
export default {
components: {
diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue
index b54f7051806..ef61fba88fe 100644
--- a/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue
+++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/app.vue
@@ -1,14 +1,14 @@
<script>
/* eslint-disable vue/no-v-html */
import { GlBreadcrumb, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
-import WelcomePage from './welcome.vue';
-import LegacyContainer from './legacy_container.vue';
import { __, s__ } from '~/locale';
import blankProjectIllustration from '../illustrations/blank-project.svg';
+import ciCdProjectIllustration from '../illustrations/ci-cd-project.svg';
import createFromTemplateIllustration from '../illustrations/create-from-template.svg';
import importProjectIllustration from '../illustrations/import-project.svg';
-import ciCdProjectIllustration from '../illustrations/ci-cd-project.svg';
+import LegacyContainer from './legacy_container.vue';
+import WelcomePage from './welcome.vue';
const BLANK_PANEL = 'blank_project';
const CI_CD_PANEL = 'cicd_for_external_repo';
diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/index.js b/app/assets/javascripts/projects/experiment_new_project_creation/index.js
index 06920a5ab19..0414f7ef6a5 100644
--- a/app/assets/javascripts/projects/experiment_new_project_creation/index.js
+++ b/app/assets/javascripts/projects/experiment_new_project_creation/index.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import NewProjectCreationApp from './components/app.vue';
-export default function (el, props) {
+export default function initNewProjectCreation(el, props) {
return new Vue({
el,
components: {
diff --git a/app/assets/javascripts/projects/members/constants.js b/app/assets/javascripts/projects/members/constants.js
new file mode 100644
index 00000000000..a69a64fe882
--- /dev/null
+++ b/app/assets/javascripts/projects/members/constants.js
@@ -0,0 +1 @@
+export const PROJECT_MEMBER_BASE_PROPERTY_NAME = 'project_member';
diff --git a/app/assets/javascripts/projects/members/utils.js b/app/assets/javascripts/projects/members/utils.js
new file mode 100644
index 00000000000..a8a635e3ce8
--- /dev/null
+++ b/app/assets/javascripts/projects/members/utils.js
@@ -0,0 +1,8 @@
+import { MEMBER_ACCESS_LEVEL_PROPERTY_NAME } from '~/members/constants';
+import { baseRequestFormatter } from '~/members/utils';
+import { PROJECT_MEMBER_BASE_PROPERTY_NAME } from './constants';
+
+export const projectMemberRequestFormatter = baseRequestFormatter(
+ PROJECT_MEMBER_BASE_PROPERTY_NAME,
+ MEMBER_ACCESS_LEVEL_PROPERTY_NAME,
+);
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/app.vue b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
index 7bb62cf4a73..4a8e1424fa8 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/app.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/app.vue
@@ -1,44 +1,12 @@
<script>
-import { GlAlert, GlTabs, GlTab } from '@gitlab/ui';
-import { s__ } from '~/locale';
-import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql';
-import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql';
+import { GlTabs, GlTab } from '@gitlab/ui';
+import { mergeUrlParams, updateHistory, getParameterValues } from '~/lib/utils/url_utility';
import PipelineCharts from './pipeline_charts.vue';
-import {
- DEFAULT,
- LOAD_ANALYTICS_FAILURE,
- LOAD_PIPELINES_FAILURE,
- PARSE_FAILURE,
- UNSUPPORTED_DATA,
-} from '../constants';
-
-const defaultAnalyticsValues = {
- weekPipelinesTotals: [],
- weekPipelinesLabels: [],
- weekPipelinesSuccessful: [],
- monthPipelinesLabels: [],
- monthPipelinesTotals: [],
- monthPipelinesSuccessful: [],
- yearPipelinesLabels: [],
- yearPipelinesTotals: [],
- yearPipelinesSuccessful: [],
- pipelineTimesLabels: [],
- pipelineTimesValues: [],
-};
-
-const defaultCountValues = {
- totalPipelines: {
- count: 0,
- },
- successfulPipelines: {
- count: 0,
- },
-};
+const charts = ['pipelines', 'deployments'];
export default {
components: {
- GlAlert,
GlTabs,
GlTab,
PipelineCharts,
@@ -50,171 +18,42 @@ export default {
type: Boolean,
default: false,
},
- projectPath: {
- type: String,
- default: '',
- },
},
data() {
return {
- showFailureAlert: false,
- failureType: null,
- analytics: { ...defaultAnalyticsValues },
- counts: { ...defaultCountValues },
+ selectedTab: 0,
};
},
- apollo: {
- counts: {
- query: getPipelineCountByStatus,
- variables() {
- return {
- projectPath: this.projectPath,
- };
- },
- update(data) {
- return data?.project;
- },
- error() {
- this.reportFailure(LOAD_PIPELINES_FAILURE);
- },
- },
- analytics: {
- query: getProjectPipelineStatistics,
- variables() {
- return {
- projectPath: this.projectPath,
- };
- },
- update(data) {
- return data?.project?.pipelineAnalytics;
- },
- error() {
- this.reportFailure(LOAD_ANALYTICS_FAILURE);
- },
- },
- },
- computed: {
- failure() {
- switch (this.failureType) {
- case LOAD_ANALYTICS_FAILURE:
- return {
- text: this.$options.errorTexts[LOAD_ANALYTICS_FAILURE],
- variant: 'danger',
- };
- case PARSE_FAILURE:
- return {
- text: this.$options.errorTexts[PARSE_FAILURE],
- variant: 'danger',
- };
- case UNSUPPORTED_DATA:
- return {
- text: this.$options.errorTexts[UNSUPPORTED_DATA],
- variant: 'info',
- };
- default:
- return {
- text: this.$options.errorTexts[DEFAULT],
- variant: 'danger',
- };
- }
- },
- lastWeekChartData() {
- return {
- labels: this.analytics.weekPipelinesLabels,
- totals: this.analytics.weekPipelinesTotals,
- success: this.analytics.weekPipelinesSuccessful,
- };
- },
- lastMonthChartData() {
- return {
- labels: this.analytics.monthPipelinesLabels,
- totals: this.analytics.monthPipelinesTotals,
- success: this.analytics.monthPipelinesSuccessful,
- };
- },
- lastYearChartData() {
- return {
- labels: this.analytics.yearPipelinesLabels,
- totals: this.analytics.yearPipelinesTotals,
- success: this.analytics.yearPipelinesSuccessful,
- };
- },
- timesChartData() {
- return {
- labels: this.analytics.pipelineTimesLabels,
- values: this.analytics.pipelineTimesValues,
- };
- },
- successRatio() {
- const { successfulPipelines, failedPipelines } = this.counts;
- const successfulCount = successfulPipelines?.count;
- const failedCount = failedPipelines?.count;
- const ratio = (successfulCount / (successfulCount + failedCount)) * 100;
-
- return failedCount === 0 ? 100 : ratio;
- },
- formattedCounts() {
- const { totalPipelines, successfulPipelines, failedPipelines } = this.counts;
-
- return {
- total: totalPipelines?.count,
- success: successfulPipelines?.count,
- failed: failedPipelines?.count,
- successRatio: this.successRatio,
- };
- },
+ created() {
+ this.selectTab();
+ window.addEventListener('popstate', this.selectTab);
},
methods: {
- hideAlert() {
- this.showFailureAlert = false;
- },
- reportFailure(type) {
- this.showFailureAlert = true;
- this.failureType = type;
+ selectTab() {
+ const [chart] = getParameterValues('chart') || charts;
+ const tab = charts.indexOf(chart);
+ this.selectedTab = tab >= 0 ? tab : 0;
+ },
+ onTabChange(index) {
+ if (index !== this.selectedTab) {
+ this.selectedTab = index;
+ const path = mergeUrlParams({ chart: charts[index] }, window.location.pathname);
+ updateHistory({ url: path, title: window.title });
+ }
},
},
- errorTexts: {
- [LOAD_ANALYTICS_FAILURE]: s__(
- 'PipelineCharts|An error has ocurred when retrieving the analytics data',
- ),
- [LOAD_PIPELINES_FAILURE]: s__(
- 'PipelineCharts|An error has ocurred when retrieving the pipelines data',
- ),
- [PARSE_FAILURE]: s__('PipelineCharts|There was an error parsing the data for the charts.'),
- [DEFAULT]: s__('PipelineCharts|An unknown error occurred while processing CI/CD analytics.'),
- },
};
</script>
<template>
<div>
- <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="hideAlert">{{
- failure.text
- }}</gl-alert>
- <gl-tabs v-if="shouldRenderDeploymentFrequencyCharts">
+ <gl-tabs v-if="shouldRenderDeploymentFrequencyCharts" :value="selectedTab" @input="onTabChange">
<gl-tab :title="__('Pipelines')">
- <pipeline-charts
- :counts="formattedCounts"
- :last-week="lastWeekChartData"
- :last-month="lastMonthChartData"
- :last-year="lastYearChartData"
- :times-chart="timesChartData"
- :loading="$apollo.queries.counts.loading"
- @report-failure="reportFailure"
- />
+ <pipeline-charts />
</gl-tab>
<gl-tab :title="__('Deployments')">
<deployment-frequency-charts />
</gl-tab>
</gl-tabs>
- <pipeline-charts
- v-else
- :counts="formattedCounts"
- :last-week="lastWeekChartData"
- :last-month="lastMonthChartData"
- :last-year="lastYearChartData"
- :times-chart="timesChartData"
- :loading="$apollo.queries.counts.loading"
- @report-failure="reportFailure"
- />
+ <pipeline-charts v-else />
</div>
</template>
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue
new file mode 100644
index 00000000000..43b36da8b2c
--- /dev/null
+++ b/app/assets/javascripts/projects/pipelines/charts/components/ci_cd_analytics_charts.vue
@@ -0,0 +1,50 @@
+<script>
+import { GlSegmentedControl } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
+
+export default {
+ components: {
+ GlSegmentedControl,
+ CiCdAnalyticsAreaChart,
+ },
+ props: {
+ charts: {
+ required: true,
+ type: Array,
+ },
+ chartOptions: {
+ required: true,
+ type: Object,
+ },
+ },
+ data() {
+ return {
+ selectedChart: 0,
+ };
+ },
+ computed: {
+ chartRanges() {
+ return this.charts.map(({ title }, index) => ({ text: title, value: index }));
+ },
+ chart() {
+ return this.charts[this.selectedChart];
+ },
+ dateRange() {
+ return sprintf(s__('CiCdAnalytics|Date range: %{range}'), { range: this.chart.range });
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <gl-segmented-control v-model="selectedChart" :options="chartRanges" class="gl-mb-4" />
+ <ci-cd-analytics-area-chart
+ v-if="chart"
+ :chart-data="chart.data"
+ :area-chart-options="chartOptions"
+ >
+ {{ dateRange }}
+ </ci-cd-analytics-area-chart>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue
index bec4ab407f0..733f833d51a 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue
@@ -1,63 +1,184 @@
<script>
-import dateFormat from 'dateformat';
+import { GlAlert, GlSkeletonLoader } from '@gitlab/ui';
import { GlColumnChart } from '@gitlab/ui/dist/charts';
-import { GlSkeletonLoader } from '@gitlab/ui';
-import { __, s__, sprintf } from '~/locale';
+import dateFormat from 'dateformat';
import { getDateInPast } from '~/lib/utils/datetime_utility';
+import { __, s__, sprintf } from '~/locale';
import {
+ DEFAULT,
CHART_CONTAINER_HEIGHT,
CHART_DATE_FORMAT,
INNER_CHART_HEIGHT,
ONE_WEEK_AGO_DAYS,
ONE_MONTH_AGO_DAYS,
+ ONE_YEAR_AGO_DAYS,
X_AXIS_LABEL_ROTATION,
X_AXIS_TITLE_OFFSET,
PARSE_FAILURE,
+ LOAD_ANALYTICS_FAILURE,
+ LOAD_PIPELINES_FAILURE,
+ UNSUPPORTED_DATA,
} from '../constants';
+import getPipelineCountByStatus from '../graphql/queries/get_pipeline_count_by_status.query.graphql';
+import getProjectPipelineStatistics from '../graphql/queries/get_project_pipeline_statistics.query.graphql';
+import CiCdAnalyticsCharts from './ci_cd_analytics_charts.vue';
import StatisticsList from './statistics_list.vue';
-import CiCdAnalyticsAreaChart from './ci_cd_analytics_area_chart.vue';
+
+const defaultAnalyticsValues = {
+ weekPipelinesTotals: [],
+ weekPipelinesLabels: [],
+ weekPipelinesSuccessful: [],
+ monthPipelinesLabels: [],
+ monthPipelinesTotals: [],
+ monthPipelinesSuccessful: [],
+ yearPipelinesLabels: [],
+ yearPipelinesTotals: [],
+ yearPipelinesSuccessful: [],
+ pipelineTimesLabels: [],
+ pipelineTimesValues: [],
+};
+
+const defaultCountValues = {
+ totalPipelines: {
+ count: 0,
+ },
+ successfulPipelines: {
+ count: 0,
+ },
+};
export default {
components: {
+ GlAlert,
GlColumnChart,
GlSkeletonLoader,
StatisticsList,
- CiCdAnalyticsAreaChart,
+ CiCdAnalyticsCharts,
+ },
+ inject: {
+ projectPath: {
+ type: String,
+ default: '',
+ },
},
- props: {
+ data() {
+ return {
+ showFailureAlert: false,
+ failureType: null,
+ analytics: { ...defaultAnalyticsValues },
+ counts: { ...defaultCountValues },
+ };
+ },
+ apollo: {
counts: {
- required: true,
- type: Object,
+ query: getPipelineCountByStatus,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data?.project;
+ },
+ error() {
+ this.reportFailure(LOAD_PIPELINES_FAILURE);
+ },
+ },
+ analytics: {
+ query: getProjectPipelineStatistics,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ };
+ },
+ update(data) {
+ return data?.project?.pipelineAnalytics;
+ },
+ error() {
+ this.reportFailure(LOAD_ANALYTICS_FAILURE);
+ },
+ },
+ },
+ computed: {
+ loading() {
+ return this.$apollo.queries.counts.loading;
},
- loading: {
- required: false,
- default: false,
- type: Boolean,
+ failure() {
+ switch (this.failureType) {
+ case LOAD_ANALYTICS_FAILURE:
+ return {
+ text: this.$options.errorTexts[LOAD_ANALYTICS_FAILURE],
+ variant: 'danger',
+ };
+ case PARSE_FAILURE:
+ return {
+ text: this.$options.errorTexts[PARSE_FAILURE],
+ variant: 'danger',
+ };
+ case UNSUPPORTED_DATA:
+ return {
+ text: this.$options.errorTexts[UNSUPPORTED_DATA],
+ variant: 'info',
+ };
+ default:
+ return {
+ text: this.$options.errorTexts[DEFAULT],
+ variant: 'danger',
+ };
+ }
},
- lastWeek: {
- required: true,
- type: Object,
+ lastWeekChartData() {
+ return {
+ labels: this.analytics.weekPipelinesLabels,
+ totals: this.analytics.weekPipelinesTotals,
+ success: this.analytics.weekPipelinesSuccessful,
+ };
},
- lastMonth: {
- required: true,
- type: Object,
+ lastMonthChartData() {
+ return {
+ labels: this.analytics.monthPipelinesLabels,
+ totals: this.analytics.monthPipelinesTotals,
+ success: this.analytics.monthPipelinesSuccessful,
+ };
},
- lastYear: {
- required: true,
- type: Object,
+ lastYearChartData() {
+ return {
+ labels: this.analytics.yearPipelinesLabels,
+ totals: this.analytics.yearPipelinesTotals,
+ success: this.analytics.yearPipelinesSuccessful,
+ };
},
- timesChart: {
- required: true,
- type: Object,
+ timesChartData() {
+ return {
+ labels: this.analytics.pipelineTimesLabels,
+ values: this.analytics.pipelineTimesValues,
+ };
+ },
+ successRatio() {
+ const { successfulPipelines, failedPipelines } = this.counts;
+ const successfulCount = successfulPipelines?.count;
+ const failedCount = failedPipelines?.count;
+ const ratio = (successfulCount / (successfulCount + failedCount)) * 100;
+
+ return failedCount === 0 ? 100 : ratio;
+ },
+ formattedCounts() {
+ const { totalPipelines, successfulPipelines, failedPipelines } = this.counts;
+
+ return {
+ total: totalPipelines?.count,
+ success: successfulPipelines?.count,
+ failed: failedPipelines?.count,
+ successRatio: this.successRatio,
+ };
},
- },
- computed: {
areaCharts() {
const { lastWeek, lastMonth, lastYear } = this.$options.chartTitles;
+ const { lastWeekRange, lastMonthRange, lastYearRange } = this.$options.chartRanges;
const charts = [
- { title: lastWeek, data: this.lastWeek },
- { title: lastMonth, data: this.lastMonth },
- { title: lastYear, data: this.lastYear },
+ { title: lastWeek, range: lastWeekRange, data: this.lastWeekChartData },
+ { title: lastMonth, range: lastMonthRange, data: this.lastMonthChartData },
+ { title: lastYear, range: lastYearRange, data: this.lastYearChartData },
];
let areaChartsData = [];
@@ -65,7 +186,7 @@ export default {
areaChartsData = charts.map(this.buildAreaChartData);
} catch {
areaChartsData = [];
- this.vm.$emit('report-failure', PARSE_FAILURE);
+ this.reportFailure(PARSE_FAILURE);
}
return areaChartsData;
@@ -74,20 +195,28 @@ export default {
return [
{
name: 'full',
- data: this.mergeLabelsAndValues(this.timesChart.labels, this.timesChart.values),
+ data: this.mergeLabelsAndValues(this.timesChartData.labels, this.timesChartData.values),
},
];
},
},
methods: {
+ hideAlert() {
+ this.showFailureAlert = false;
+ },
+ reportFailure(type) {
+ this.showFailureAlert = true;
+ this.failureType = type;
+ },
mergeLabelsAndValues(labels, values) {
return labels.map((label, index) => [label, values[index]]);
},
- buildAreaChartData({ title, data }) {
+ buildAreaChartData({ title, data, range }) {
const { labels, totals, success } = data;
return {
title,
+ range,
data: [
{
name: 'all',
@@ -118,28 +247,50 @@ export default {
},
yAxis: {
name: s__('Pipeline|Pipelines'),
+ minInterval: 1,
},
},
- get chartTitles() {
+ errorTexts: {
+ [LOAD_ANALYTICS_FAILURE]: s__(
+ 'PipelineCharts|An error has ocurred when retrieving the analytics data',
+ ),
+ [LOAD_PIPELINES_FAILURE]: s__(
+ 'PipelineCharts|An error has ocurred when retrieving the pipelines data',
+ ),
+ [PARSE_FAILURE]: s__('PipelineCharts|There was an error parsing the data for the charts.'),
+ [DEFAULT]: s__('PipelineCharts|An unknown error occurred while processing CI/CD analytics.'),
+ },
+ chartTitles: {
+ lastWeek: __('Last week'),
+ lastMonth: __('Last month'),
+ lastYear: __('Last year'),
+ },
+ get chartRanges() {
const today = dateFormat(new Date(), CHART_DATE_FORMAT);
const pastDate = (timeScale) =>
dateFormat(getDateInPast(new Date(), timeScale), CHART_DATE_FORMAT);
return {
- lastWeek: sprintf(__('Pipelines for last week (%{oneWeekAgo} - %{today})'), {
+ lastWeekRange: sprintf(__('%{oneWeekAgo} - %{today}'), {
oneWeekAgo: pastDate(ONE_WEEK_AGO_DAYS),
today,
}),
- lastMonth: sprintf(__('Pipelines for last month (%{oneMonthAgo} - %{today})'), {
+ lastMonthRange: sprintf(__('%{oneMonthAgo} - %{today}'), {
oneMonthAgo: pastDate(ONE_MONTH_AGO_DAYS),
today,
}),
- lastYear: __('Pipelines for last year'),
+ lastYearRange: sprintf(__('%{oneYearAgo} - %{today}'), {
+ oneYearAgo: pastDate(ONE_YEAR_AGO_DAYS),
+ today,
+ }),
};
},
};
</script>
<template>
<div>
+ <gl-alert v-if="showFailureAlert" :variant="failure.variant" @dismiss="hideAlert">{{
+ failure.text
+ }}</gl-alert>
<div class="gl-mb-3">
<h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3>
</div>
@@ -147,7 +298,7 @@ export default {
<div class="row">
<div class="col-md-6">
<gl-skeleton-loader v-if="loading" :lines="5" />
- <statistics-list v-else :counts="counts" />
+ <statistics-list v-else :counts="formattedCounts" />
</div>
<div v-if="!loading" class="col-md-6">
<strong>{{ __('Duration for the last 30 commits') }}</strong>
@@ -164,13 +315,7 @@ export default {
<template v-if="!loading">
<hr />
<h4 class="gl-my-4">{{ __('Pipelines charts') }}</h4>
- <ci-cd-analytics-area-chart
- v-for="(chart, index) in areaCharts"
- :key="index"
- :chart-data="chart.data"
- :area-chart-options="$options.areaChartOptions"
- >{{ chart.title }}</ci-cd-analytics-area-chart
- >
+ <ci-cd-analytics-charts :charts="areaCharts" :chart-options="$options.areaChartOptions" />
</template>
</div>
</template>
diff --git a/app/assets/javascripts/projects/pipelines/charts/constants.js b/app/assets/javascripts/projects/pipelines/charts/constants.js
index 079e23943c1..41fe81f21ea 100644
--- a/app/assets/javascripts/projects/pipelines/charts/constants.js
+++ b/app/assets/javascripts/projects/pipelines/charts/constants.js
@@ -10,6 +10,8 @@ export const ONE_WEEK_AGO_DAYS = 7;
export const ONE_MONTH_AGO_DAYS = 31;
+export const ONE_YEAR_AGO_DAYS = 365;
+
export const CHART_DATE_FORMAT = 'dd mmm';
export const DEFAULT = 'default';
diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js
index a62b5d423de..a5e53ee3927 100644
--- a/app/assets/javascripts/projects/settings/access_dropdown.js
+++ b/app/assets/javascripts/projects/settings/access_dropdown.js
@@ -1,17 +1,16 @@
/* eslint-disable no-underscore-dangle, class-methods-use-this */
import { escape, find, countBy } from 'lodash';
-import axios from '~/lib/utils/axios_utils';
+import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
import { n__, s__, __, sprintf } from '~/locale';
import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVELS, ACCESS_LEVEL_NONE } from './constants';
-import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class AccessDropdown {
constructor(options) {
const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options;
this.options = options;
this.hasLicense = hasLicense;
- this.deployKeysOnProtectedBranchesEnabled = gon.features.deployKeysOnProtectedBranches;
this.groups = [];
this.accessLevel = accessLevel;
this.accessLevelsData = accessLevelsData.roles;
@@ -330,11 +329,7 @@ export default class AccessDropdown {
);
})
.catch(() => {
- if (this.deployKeysOnProtectedBranchesEnabled) {
- createFlash({ message: __('Failed to load groups, users and deploy keys.') });
- } else {
- createFlash({ message: __('Failed to load groups & users.') });
- }
+ createFlash({ message: __('Failed to load groups, users and deploy keys.') });
});
} else {
this.getDeployKeys(query)
@@ -445,35 +440,33 @@ export default class AccessDropdown {
}
}
- if (this.deployKeysOnProtectedBranchesEnabled) {
- const deployKeys = deployKeysResponse.map((response) => {
- const {
- id,
- fingerprint,
- title,
- owner: { avatar_url, name, username },
- } = response;
-
- const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`;
-
- return {
- id,
- title: title.concat(' ', shortFingerprint),
- avatar_url,
- fullname: name,
- username,
- type: LEVEL_TYPES.DEPLOY_KEY,
- };
- });
+ const deployKeys = deployKeysResponse.map((response) => {
+ const {
+ id,
+ fingerprint,
+ title,
+ owner: { avatar_url, name, username },
+ } = response;
+
+ const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`;
+
+ return {
+ id,
+ title: title.concat(' ', shortFingerprint),
+ avatar_url,
+ fullname: name,
+ username,
+ type: LEVEL_TYPES.DEPLOY_KEY,
+ };
+ });
- if (this.accessLevel === ACCESS_LEVELS.PUSH) {
- if (deployKeys.length) {
- consolidatedData = consolidatedData.concat(
- [{ type: 'divider' }],
- [{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }],
- deployKeys,
- );
- }
+ if (this.accessLevel === ACCESS_LEVELS.PUSH) {
+ if (deployKeys.length) {
+ consolidatedData = consolidatedData.concat(
+ [{ type: 'divider' }],
+ [{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }],
+ deployKeys,
+ );
}
}
@@ -501,19 +494,15 @@ export default class AccessDropdown {
}
getDeployKeys(query) {
- if (this.deployKeysOnProtectedBranchesEnabled) {
- return axios.get(this.buildUrl(gon.relative_url_root, this.deployKeysPath), {
- params: {
- search: query,
- per_page: 20,
- active: true,
- project_id: gon.current_project_id,
- push_code: true,
- },
- });
- }
-
- return Promise.resolve({ data: [] });
+ return axios.get(this.buildUrl(gon.relative_url_root, this.deployKeysPath), {
+ params: {
+ search: query,
+ per_page: 20,
+ active: true,
+ project_id: gon.current_project_id,
+ push_code: true,
+ },
+ });
}
buildUrl(urlRoot, url) {
diff --git a/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue b/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue
index 51281def7d0..0786a74f6b1 100644
--- a/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue
+++ b/app/assets/javascripts/projects/settings/components/shared_runners_toggle.vue
@@ -1,7 +1,7 @@
<script>
import { GlAlert, GlToggle, GlTooltip } from '@gitlab/ui';
-import { __ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
+import { __ } from '~/locale';
const DEFAULT_ERROR_MESSAGE = __('An error occurred while updating the configuration.');
diff --git a/app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js b/app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js
index c5d45fe6fed..eaeb5848b68 100644
--- a/app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js
+++ b/app/assets/javascripts/projects/settings/mount_shared_runners_toggle.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import SharedRunnersToggle from '~/projects/settings/components/shared_runners_toggle.vue';
import { parseBoolean } from '~/lib/utils/common_utils';
+import SharedRunnersToggle from '~/projects/settings/components/shared_runners_toggle.vue';
export default (containerId = 'toggle-shared-runners-form') => {
const containerEl = document.getElementById(containerId);
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
index 909f1afd9f6..9b3c0dd2755 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue
@@ -1,61 +1,43 @@
<script>
import { GlAlert } from '@gitlab/ui';
+import axios from '~/lib/utils/axios_utils';
import { __, sprintf } from '~/locale';
import ServiceDeskSetting from './service_desk_setting.vue';
-import ServiceDeskService from '../services/service_desk_service';
-import eventHub from '../event_hub';
export default {
- name: 'ServiceDeskRoot',
components: {
GlAlert,
ServiceDeskSetting,
},
- props: {
+ inject: {
initialIsEnabled: {
- type: Boolean,
- required: true,
+ default: false,
},
endpoint: {
- type: String,
- required: true,
+ default: '',
},
- incomingEmail: {
- type: String,
- required: false,
+ initialIncomingEmail: {
default: '',
},
customEmail: {
- type: String,
- required: false,
default: '',
},
customEmailEnabled: {
- type: Boolean,
- required: false,
+ default: false,
},
selectedTemplate: {
- type: String,
- required: false,
default: '',
},
outgoingName: {
- type: String,
- required: false,
default: '',
},
projectKey: {
- type: String,
- required: false,
default: '',
},
templates: {
- type: Array,
- required: false,
- default: () => [],
+ default: [],
},
},
-
data() {
return {
isEnabled: this.initialIsEnabled,
@@ -63,28 +45,21 @@ export default {
isAlertShowing: false,
alertVariant: 'danger',
alertMessage: '',
+ incomingEmail: this.initialIncomingEmail,
updatedCustomEmail: this.customEmail,
};
},
-
- created() {
- eventHub.$on('serviceDeskEnabledCheckboxToggled', this.onEnableToggled);
- eventHub.$on('serviceDeskTemplateSave', this.onSaveTemplate);
- this.service = new ServiceDeskService(this.endpoint);
- },
-
- beforeDestroy() {
- eventHub.$off('serviceDeskEnabledCheckboxToggled', this.onEnableToggled);
- eventHub.$off('serviceDeskTemplateSave', this.onSaveTemplate);
- },
-
methods: {
onEnableToggled(isChecked) {
this.isEnabled = isChecked;
this.incomingEmail = '';
- this.service
- .toggleServiceDesk(isChecked)
+ const body = {
+ service_desk_enabled: isChecked,
+ };
+
+ return axios
+ .put(this.endpoint, body)
.then(({ data }) => {
const email = data.service_desk_address;
if (isChecked && !email) {
@@ -104,8 +79,16 @@ export default {
onSaveTemplate({ selectedTemplate, outgoingName, projectKey }) {
this.isTemplateSaving = true;
- this.service
- .updateTemplate({ selectedTemplate, outgoingName, projectKey }, this.isEnabled)
+
+ const body = {
+ issue_template_key: selectedTemplate,
+ outgoing_name: outgoingName,
+ project_key: projectKey,
+ service_desk_enabled: this.isEnabled,
+ };
+
+ return axios
+ .put(this.endpoint, body)
.then(({ data }) => {
this.updatedCustomEmail = data?.service_desk_address;
this.showAlert(__('Changes saved.'), 'success');
@@ -150,6 +133,8 @@ export default {
:initial-project-key="projectKey"
:templates="templates"
:is-template-saving="isTemplateSaving"
+ @save="onSaveTemplate"
+ @toggle="onEnableToggled"
/>
</div>
</template>
diff --git a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
index a850374fc88..39d9a6a4239 100644
--- a/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
+++ b/app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue
@@ -1,12 +1,9 @@
<script>
import { GlButton, GlFormSelect, GlToggle, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import { __ } from '~/locale';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
-import eventHub from '../event_hub';
export default {
- name: 'ServiceDeskSetting',
components: {
ClipboardButton,
GlButton,
@@ -15,7 +12,6 @@ export default {
GlLoadingIcon,
GlSprintf,
},
- mixins: [glFeatureFlagsMixin()],
props: {
isEnabled: {
type: Boolean,
@@ -84,10 +80,10 @@ export default {
},
methods: {
onCheckboxToggle(isChecked) {
- eventHub.$emit('serviceDeskEnabledCheckboxToggled', isChecked);
+ this.$emit('toggle', isChecked);
},
onSaveTemplate() {
- eventHub.$emit('serviceDeskTemplateSave', {
+ this.$emit('save', {
selectedTemplate: this.selectedTemplate,
outgoingName: this.outgoingName,
projectKey: this.projectKey,
@@ -111,7 +107,11 @@ export default {
</label>
<div v-if="isEnabled" class="row mt-3">
<div class="col-md-9 mb-0">
- <strong id="incoming-email-describer" class="d-block mb-1">
+ <strong
+ id="incoming-email-describer"
+ class="gl-display-block gl-mb-1"
+ data-testid="incoming-email-describer"
+ >
{{ __('Email address to use for Support Desk') }}
</strong>
<template v-if="email">
@@ -128,11 +128,7 @@ export default {
disabled="true"
/>
<div class="input-group-append">
- <clipboard-button
- :title="__('Copy')"
- :text="email"
- css-class="input-group-text qa-clipboard-button"
- />
+ <clipboard-button :title="__('Copy')" :text="email" css-class="input-group-text" />
</div>
</div>
<span v-if="hasCustomEmail" class="form-text text-muted">
diff --git a/app/assets/javascripts/projects/settings_service_desk/event_hub.js b/app/assets/javascripts/projects/settings_service_desk/event_hub.js
deleted file mode 100644
index e31806ad199..00000000000
--- a/app/assets/javascripts/projects/settings_service_desk/event_hub.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import createEventHub from '~/helpers/event_hub_factory';
-
-export default createEventHub();
diff --git a/app/assets/javascripts/projects/settings_service_desk/index.js b/app/assets/javascripts/projects/settings_service_desk/index.js
index 8f9828dd73d..f842ffaaa2b 100644
--- a/app/assets/javascripts/projects/settings_service_desk/index.js
+++ b/app/assets/javascripts/projects/settings_service_desk/index.js
@@ -3,43 +3,37 @@ import { parseBoolean } from '~/lib/utils/common_utils';
import ServiceDeskRoot from './components/service_desk_root.vue';
export default () => {
- const serviceDeskRootElement = document.querySelector('.js-service-desk-setting-root');
- if (serviceDeskRootElement) {
- // eslint-disable-next-line no-new
- new Vue({
- el: serviceDeskRootElement,
- components: {
- ServiceDeskRoot,
- },
- data() {
- const { dataset } = serviceDeskRootElement;
- return {
- initialIsEnabled: parseBoolean(dataset.enabled),
- endpoint: dataset.endpoint,
- incomingEmail: dataset.incomingEmail,
- customEmail: dataset.customEmail,
- customEmailEnabled: parseBoolean(dataset.customEmailEnabled),
- selectedTemplate: dataset.selectedTemplate,
- outgoingName: dataset.outgoingName,
- projectKey: dataset.projectKey,
- templates: JSON.parse(dataset.templates),
- };
- },
- render(createElement) {
- return createElement('service-desk-root', {
- props: {
- initialIsEnabled: this.initialIsEnabled,
- endpoint: this.endpoint,
- incomingEmail: this.incomingEmail,
- customEmail: this.customEmail,
- customEmailEnabled: this.customEmailEnabled,
- selectedTemplate: this.selectedTemplate,
- outgoingName: this.outgoingName,
- projectKey: this.projectKey,
- templates: this.templates,
- },
- });
- },
- });
+ const el = document.querySelector('.js-service-desk-setting-root');
+
+ if (!el) {
+ return false;
}
+
+ const {
+ customEmail,
+ customEmailEnabled,
+ enabled,
+ endpoint,
+ incomingEmail,
+ outgoingName,
+ projectKey,
+ selectedTemplate,
+ templates,
+ } = el.dataset;
+
+ return new Vue({
+ el,
+ provide: {
+ customEmail,
+ customEmailEnabled: parseBoolean(customEmailEnabled),
+ endpoint,
+ initialIncomingEmail: incomingEmail,
+ initialIsEnabled: parseBoolean(enabled),
+ outgoingName,
+ projectKey,
+ selectedTemplate,
+ templates: JSON.parse(templates),
+ },
+ render: (createElement) => createElement(ServiceDeskRoot),
+ });
};
diff --git a/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js b/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js
deleted file mode 100644
index b68c5bb876f..00000000000
--- a/app/assets/javascripts/projects/settings_service_desk/services/service_desk_service.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import axios from '~/lib/utils/axios_utils';
-
-class ServiceDeskService {
- constructor(endpoint) {
- this.endpoint = endpoint;
- }
-
- toggleServiceDesk(enable) {
- return axios.put(this.endpoint, { service_desk_enabled: enable });
- }
-
- updateTemplate({ selectedTemplate, outgoingName, projectKey = '' }, isEnabled) {
- const body = {
- issue_template_key: selectedTemplate,
- outgoing_name: outgoingName,
- project_key: projectKey,
- service_desk_enabled: isEnabled,
- };
- return axios.put(this.endpoint, body);
- }
-}
-
-export default ServiceDeskService;
diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
index 0f01167988d..f3d12e0dd00 100644
--- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
+++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
@@ -1,10 +1,10 @@
<script>
-import Visibility from 'visibilityjs';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
-import ciIcon from '~/vue_shared/components/ci_icon.vue';
-import Poll from '~/lib/utils/poll';
+import Visibility from 'visibilityjs';
import { deprecatedCreateFlash as Flash } from '~/flash';
+import Poll from '~/lib/utils/poll';
import { __, s__, sprintf } from '~/locale';
+import ciIcon from '~/vue_shared/components/ci_icon.vue';
import CommitPipelineService from '../services/commit_pipeline_service';
export default {