summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/projects
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 18:18:33 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-16 18:18:33 +0000
commitf64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch)
treea2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /app/assets/javascripts/projects
parentbfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff)
downloadgitlab-ce-f64a639bcfa1fc2bc89ca7db268f594306edfd7c.tar.gz
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'app/assets/javascripts/projects')
-rw-r--r--app/assets/javascripts/projects/commit/components/branches_dropdown.vue23
-rw-r--r--app/assets/javascripts/projects/commit/components/form_modal.vue30
-rw-r--r--app/assets/javascripts/projects/commit/components/projects_dropdown.vue87
-rw-r--r--app/assets/javascripts/projects/commit/constants.js13
-rw-r--r--app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js8
-rw-r--r--app/assets/javascripts/projects/commit/store/actions.js20
-rw-r--r--app/assets/javascripts/projects/commit/store/getters.js2
-rw-r--r--app/assets/javascripts/projects/commit/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/projects/commit/store/mutations.js9
-rw-r--r--app/assets/javascripts/projects/commit/store/state.js3
-rw-r--r--app/assets/javascripts/projects/commit_box/info/index.js14
-rw-r--r--app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js26
-rw-r--r--app/assets/javascripts/projects/commits/store/actions.js2
-rw-r--r--app/assets/javascripts/projects/compare/components/app.vue79
-rw-r--r--app/assets/javascripts/projects/compare/components/app_legacy.vue89
-rw-r--r--app/assets/javascripts/projects/compare/components/repo_dropdown.vue93
-rw-r--r--app/assets/javascripts/projects/compare/components/revision_card.vue65
-rw-r--r--app/assets/javascripts/projects/compare/components/revision_dropdown.vue115
-rw-r--r--app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue145
-rw-r--r--app/assets/javascripts/projects/compare/index.js42
-rw-r--r--app/assets/javascripts/projects/details/upload_button.vue59
-rw-r--r--app/assets/javascripts/projects/experiment_new_project_creation/components/new_project_push_tip_popover.vue66
-rw-r--r--app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue14
-rw-r--r--app/assets/javascripts/projects/experiment_new_project_creation/index.js6
-rw-r--r--app/assets/javascripts/projects/feature_flags_user_lists/show/index.js23
-rw-r--r--app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue6
-rw-r--r--app/assets/javascripts/projects/project_new.js35
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_root.vue2
-rw-r--r--app/assets/javascripts/projects/settings_service_desk/components/service_desk_setting.vue12
-rw-r--r--app/assets/javascripts/projects/upload_file_experiment.js33
-rw-r--r--app/assets/javascripts/projects/upload_file_experiment_tracking.js9
31 files changed, 971 insertions, 162 deletions
diff --git a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
index 3527bcb04c6..cc5bc703994 100644
--- a/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
+++ b/app/assets/javascripts/projects/commit/components/branches_dropdown.vue
@@ -7,7 +7,11 @@ import {
GlLoadingIcon,
} from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex';
-import { I18N_DROPDOWN } from '../constants';
+import {
+ I18N_NO_RESULTS_MESSAGE,
+ I18N_BRANCH_HEADER,
+ I18N_BRANCH_SEARCH_PLACEHOLDER,
+} from '../constants';
export default {
name: 'BranchesDropdown',
@@ -25,7 +29,11 @@ export default {
default: '',
},
},
- i18n: I18N_DROPDOWN,
+ i18n: {
+ noResultsMessage: I18N_NO_RESULTS_MESSAGE,
+ branchHeaderTitle: I18N_BRANCH_HEADER,
+ branchSearchPlaceholder: I18N_BRANCH_SEARCH_PLACEHOLDER,
+ },
data() {
return {
searchTerm: this.value,
@@ -41,6 +49,13 @@ export default {
);
},
},
+ watch: {
+ // Parent component can set the branch value (e.g. when the user selects a different project)
+ // and we need to keep the search term in sync with the selected value
+ value(val) {
+ this.searchTermChanged(val);
+ },
+ },
mounted() {
this.fetchBranches(this.searchTerm);
},
@@ -61,13 +76,13 @@ export default {
};
</script>
<template>
- <gl-dropdown :text="value" :header-text="$options.i18n.headerTitle">
+ <gl-dropdown :text="value" :header-text="$options.i18n.branchHeaderTitle">
<gl-search-box-by-type
:value="searchTerm"
trim
autocomplete="off"
:debounce="250"
- :placeholder="$options.i18n.searchPlaceholder"
+ :placeholder="$options.i18n.branchSearchPlaceholder"
data-testid="dropdown-search-box"
@input="searchTermChanged"
/>
diff --git a/app/assets/javascripts/projects/commit/components/form_modal.vue b/app/assets/javascripts/projects/commit/components/form_modal.vue
index ed216a91ca0..30968d29cde 100644
--- a/app/assets/javascripts/projects/commit/components/form_modal.vue
+++ b/app/assets/javascripts/projects/commit/components/form_modal.vue
@@ -3,18 +3,22 @@ import { GlModal, GlForm, GlFormCheckbox, GlSprintf, GlFormGroup } from '@gitlab
import { mapActions, mapState } from 'vuex';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import csrf from '~/lib/utils/csrf';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../event_hub';
import BranchesDropdown from './branches_dropdown.vue';
+import ProjectsDropdown from './projects_dropdown.vue';
export default {
components: {
BranchesDropdown,
+ ProjectsDropdown,
GlModal,
GlForm,
GlFormCheckbox,
GlSprintf,
GlFormGroup,
},
+ mixins: [glFeatureFlagsMixin()],
inject: {
prependedText: {
default: '',
@@ -60,13 +64,17 @@ export default {
'modalTitle',
'existingBranch',
'prependedText',
+ 'targetProjectId',
+ 'targetProjectName',
+ 'branchesEndpoint',
]),
},
mounted() {
+ this.setSelectedProject(this.targetProjectId);
eventHub.$on(this.openModal, this.show);
},
methods: {
- ...mapActions(['clearModal', 'setBranch', 'setSelectedBranch']),
+ ...mapActions(['clearModal', 'setBranch', 'setSelectedBranch', 'setSelectedProject']),
show() {
this.$root.$emit(BV_SHOW_MODAL, this.modalId);
},
@@ -102,6 +110,26 @@ export default {
<input type="hidden" name="authenticity_token" :value="$options.csrf.token" />
<gl-form-group
+ v-if="glFeatures.pickIntoProject"
+ :label="i18n.projectLabel"
+ label-for="start_project"
+ data-testid="dropdown-group"
+ >
+ <input
+ id="target_project_id"
+ type="hidden"
+ name="target_project_id"
+ :value="targetProjectId"
+ />
+
+ <projects-dropdown
+ class="gl-w-half"
+ :value="targetProjectName"
+ @selectProject="setSelectedProject"
+ />
+ </gl-form-group>
+
+ <gl-form-group
:label="i18n.branchLabel"
label-for="start_branch"
data-testid="dropdown-group"
diff --git a/app/assets/javascripts/projects/commit/components/projects_dropdown.vue b/app/assets/javascripts/projects/commit/components/projects_dropdown.vue
new file mode 100644
index 00000000000..6288bcdaad0
--- /dev/null
+++ b/app/assets/javascripts/projects/commit/components/projects_dropdown.vue
@@ -0,0 +1,87 @@
+<script>
+import { GlDropdown, GlSearchBoxByType, GlDropdownItem, GlDropdownText } from '@gitlab/ui';
+import { mapGetters, mapState } from 'vuex';
+import {
+ I18N_NO_RESULTS_MESSAGE,
+ I18N_PROJECT_HEADER,
+ I18N_PROJECT_SEARCH_PLACEHOLDER,
+} from '../constants';
+
+export default {
+ name: 'ProjectsDropdown',
+ components: {
+ GlDropdown,
+ GlSearchBoxByType,
+ GlDropdownItem,
+ GlDropdownText,
+ },
+ props: {
+ value: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ i18n: {
+ noResultsMessage: I18N_NO_RESULTS_MESSAGE,
+ projectHeaderTitle: I18N_PROJECT_HEADER,
+ projectSearchPlaceholder: I18N_PROJECT_SEARCH_PLACEHOLDER,
+ },
+ data() {
+ return {
+ filterTerm: this.value,
+ };
+ },
+ computed: {
+ ...mapGetters(['sortedProjects']),
+ ...mapState(['targetProjectId']),
+ filteredResults() {
+ const lowerCasedFilterTerm = this.filterTerm.toLowerCase();
+ return this.sortedProjects.filter((project) =>
+ project.name.toLowerCase().includes(lowerCasedFilterTerm),
+ );
+ },
+ selectedProject() {
+ return this.sortedProjects.find((project) => project.id === this.targetProjectId) || {};
+ },
+ },
+ methods: {
+ selectProject(project) {
+ this.$emit('selectProject', project.id);
+ this.filterTerm = project.name; // when we select a project, we want the dropdown to filter to the selected project
+ },
+ isSelected(selectedProject) {
+ return selectedProject === this.selectedProject;
+ },
+ filterTermChanged(value) {
+ this.filterTerm = value;
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown :text="selectedProject.name" :header-text="$options.i18n.projectHeaderTitle">
+ <gl-search-box-by-type
+ :value="filterTerm"
+ trim
+ autocomplete="off"
+ :placeholder="$options.i18n.projectSearchPlaceholder"
+ data-testid="dropdown-search-box"
+ @input="filterTermChanged"
+ />
+ <gl-dropdown-item
+ v-for="project in filteredResults"
+ :key="project.name"
+ :name="project.name"
+ :is-checked="isSelected(project)"
+ is-check-item
+ data-testid="dropdown-item"
+ @click="selectProject(project)"
+ >
+ {{ project.name }}
+ </gl-dropdown-item>
+ <gl-dropdown-text v-if="!filteredResults.length" data-testid="empty-result-message">
+ <span class="gl-text-gray-500">{{ $options.i18n.noResultsMessage }}</span>
+ </gl-dropdown-text>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/projects/commit/constants.js b/app/assets/javascripts/projects/commit/constants.js
index b47c744e5fb..d6bb4e9483f 100644
--- a/app/assets/javascripts/projects/commit/constants.js
+++ b/app/assets/javascripts/projects/commit/constants.js
@@ -26,6 +26,7 @@ export const I18N_REVERT_MODAL = {
export const I18N_CHERRY_PICK_MODAL = {
branchLabel: s__('ChangeTypeAction|Pick into branch'),
+ projectLabel: s__('ChangeTypeAction|Pick into project'),
actionPrimaryText: s__('ChangeTypeAction|Cherry-pick'),
};
@@ -33,10 +34,12 @@ export const PREPENDED_MODAL_TEXT = s__(
'ChangeTypeAction|This will create a new commit in order to revert the existing changes.',
);
-export const I18N_DROPDOWN = {
- noResultsMessage: __('No matching results'),
- headerTitle: s__('ChangeTypeAction|Switch branch'),
- searchPlaceholder: s__('ChangeTypeAction|Search branches'),
-};
+export const I18N_NO_RESULTS_MESSAGE = __('No matching results');
+
+export const I18N_PROJECT_HEADER = s__('ChangeTypeAction|Switch project');
+export const I18N_PROJECT_SEARCH_PLACEHOLDER = s__('ChangeTypeAction|Search projects');
+
+export const I18N_BRANCH_HEADER = s__('ChangeTypeAction|Switch branch');
+export const I18N_BRANCH_SEARCH_PLACEHOLDER = s__('ChangeTypeAction|Search branches');
export const PROJECT_BRANCHES_ERROR = __('Something went wrong while fetching branches');
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
index 24baa27ff70..ad31ad14b2a 100644
--- a/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js
+++ b/app/assets/javascripts/projects/commit/init_cherry_pick_commit_modal.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import CommitFormModal from './components/form_modal.vue';
import {
I18N_MODAL,
@@ -19,21 +19,27 @@ export default function initInviteMembersModal() {
title,
endpoint,
branch,
+ targetProjectId,
+ targetProjectName,
pushCode,
branchCollaboration,
existingBranch,
branchesEndpoint,
+ projects,
} = el.dataset;
const store = createStore({
endpoint,
branchesEndpoint,
branch,
+ targetProjectId,
+ targetProjectName,
pushCode: parseBoolean(pushCode),
branchCollaboration: parseBoolean(branchCollaboration),
defaultBranch: branch,
modalTitle: title,
existingBranch,
+ projects: convertObjectPropsToCamelCase(JSON.parse(projects), { deep: true }),
});
return new Vue({
diff --git a/app/assets/javascripts/projects/commit/store/actions.js b/app/assets/javascripts/projects/commit/store/actions.js
index 10135e55351..c72704303ca 100644
--- a/app/assets/javascripts/projects/commit/store/actions.js
+++ b/app/assets/javascripts/projects/commit/store/actions.js
@@ -11,6 +11,10 @@ export const requestBranches = ({ commit }) => {
commit(types.REQUEST_BRANCHES);
};
+export const setBranchesEndpoint = ({ commit }, endpoint) => {
+ commit(types.SET_BRANCHES_ENDPOINT, endpoint);
+};
+
export const fetchBranches = ({ commit, dispatch, state }, query) => {
dispatch('requestBranches');
@@ -18,8 +22,8 @@ export const fetchBranches = ({ commit, dispatch, state }, query) => {
.get(state.branchesEndpoint, {
params: { search: query },
})
- .then((res) => {
- commit(types.RECEIVE_BRANCHES_SUCCESS, res.data);
+ .then(({ data }) => {
+ commit(types.RECEIVE_BRANCHES_SUCCESS, data.Branches || []);
})
.catch(() => {
createFlash({ message: PROJECT_BRANCHES_ERROR });
@@ -34,3 +38,15 @@ export const setBranch = ({ commit, dispatch }, branch) => {
export const setSelectedBranch = ({ commit }, branch) => {
commit(types.SET_SELECTED_BRANCH, branch);
};
+
+export const setSelectedProject = ({ commit, dispatch, state }, id) => {
+ let { branchesEndpoint } = state;
+
+ if (state.projects?.length) {
+ branchesEndpoint = state.projects.find((p) => p.id === id).refsUrl;
+ }
+
+ commit(types.SET_SELECTED_PROJECT, id);
+ dispatch('setBranchesEndpoint', branchesEndpoint);
+ dispatch('fetchBranches');
+};
diff --git a/app/assets/javascripts/projects/commit/store/getters.js b/app/assets/javascripts/projects/commit/store/getters.js
index 664eaca32cf..e0c36df8a75 100644
--- a/app/assets/javascripts/projects/commit/store/getters.js
+++ b/app/assets/javascripts/projects/commit/store/getters.js
@@ -3,3 +3,5 @@ import { uniq } from 'lodash';
export const joinedBranches = (state) => {
return uniq(state.branches).sort();
};
+
+export const sortedProjects = (state) => uniq(state.projects).sort();
diff --git a/app/assets/javascripts/projects/commit/store/mutation_types.js b/app/assets/javascripts/projects/commit/store/mutation_types.js
index de0bb47e18d..d0cf9f25ee9 100644
--- a/app/assets/javascripts/projects/commit/store/mutation_types.js
+++ b/app/assets/javascripts/projects/commit/store/mutation_types.js
@@ -1,6 +1,9 @@
export const CLEAR_MODAL = 'CLEAR_MODAL';
+export const SET_BRANCHES_ENDPOINT = 'SET_BRANCHES_ENDPOINT';
export const REQUEST_BRANCHES = 'REQUEST_BRANCHES';
export const RECEIVE_BRANCHES_SUCCESS = 'RECEIVE_BRANCHES_SUCCESS';
export const SET_BRANCH = 'SET_BRANCH';
export const SET_SELECTED_BRANCH = 'SET_SELECTED_BRANCH';
+
+export const SET_SELECTED_PROJECT = 'SET_SELECTED_PROJECT';
diff --git a/app/assets/javascripts/projects/commit/store/mutations.js b/app/assets/javascripts/projects/commit/store/mutations.js
index 6add00deadb..43f8f173014 100644
--- a/app/assets/javascripts/projects/commit/store/mutations.js
+++ b/app/assets/javascripts/projects/commit/store/mutations.js
@@ -1,6 +1,10 @@
import * as types from './mutation_types';
export default {
+ [types.SET_BRANCHES_ENDPOINT](state, endpoint) {
+ state.branchesEndpoint = endpoint;
+ },
+
[types.REQUEST_BRANCHES](state) {
state.isFetching = true;
},
@@ -22,4 +26,9 @@ export default {
[types.SET_SELECTED_BRANCH](state, branch) {
state.selectedBranch = branch;
},
+
+ [types.SET_SELECTED_PROJECT](state, projectId) {
+ state.targetProjectId = projectId;
+ state.branch = state.defaultBranch;
+ },
};
diff --git a/app/assets/javascripts/projects/commit/store/state.js b/app/assets/javascripts/projects/commit/store/state.js
index 78c294324df..660fd2ef17c 100644
--- a/app/assets/javascripts/projects/commit/store/state.js
+++ b/app/assets/javascripts/projects/commit/store/state.js
@@ -3,6 +3,7 @@ export default () => ({
branchesEndpoint: null,
isFetching: false,
branches: [],
+ projects: [],
selectedBranch: '',
pushCode: false,
branchCollaboration: false,
@@ -10,4 +11,6 @@ export default () => ({
existingBranch: '',
defaultBranch: '',
branch: '',
+ targetProjectId: '',
+ targetProjectName: '',
});
diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js
index 4bbdb5c2357..17c63ecf66b 100644
--- a/app/assets/javascripts/projects/commit_box/info/index.js
+++ b/app/assets/javascripts/projects/commit_box/info/index.js
@@ -1,5 +1,6 @@
import { fetchCommitMergeRequests } from '~/commit_merge_requests';
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
+import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph';
import { initDetailsButton } from './init_details_button';
import { loadBranches } from './load_branches';
@@ -12,10 +13,15 @@ export const initCommitBoxInfo = (containerSelector = '.js-commit-box-info') =>
// Related merge requests to this commit
fetchCommitMergeRequests();
- // Display pipeline info for this commit
- new MiniPipelineGraph({
- container: '.js-commit-pipeline-graph',
- }).bindEvents();
+ // Display pipeline mini graph for this commit
+ // Feature flag ci_commit_pipeline_mini_graph_vue
+ if (gon.features.ciCommitPipelineMiniGraphVue) {
+ initCommitPipelineMiniGraph();
+ } else {
+ new MiniPipelineGraph({
+ container: '.js-commit-pipeline-graph',
+ }).bindEvents();
+ }
initDetailsButton();
};
diff --git a/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js
new file mode 100644
index 00000000000..9173f5c771f
--- /dev/null
+++ b/app/assets/javascripts/projects/commit_box/info/init_commit_pipeline_mini_graph.js
@@ -0,0 +1,26 @@
+import Vue from 'vue';
+
+export const initCommitPipelineMiniGraph = async (selector = '.js-commit-pipeline-mini-graph') => {
+ const el = document.querySelector(selector);
+ if (!el) {
+ return;
+ }
+
+ // Some commits have no pipeline, code splitting to load the pipeline optionally
+ const { stages } = el.dataset;
+ const { default: PipelineMiniGraph } = await import(
+ /* webpackChunkName: 'pipelineMiniGraph' */ '~/pipelines/components/pipelines_list/pipeline_mini_graph.vue'
+ );
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el,
+ render(createElement) {
+ return createElement(PipelineMiniGraph, {
+ props: {
+ stages: JSON.parse(stages),
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/projects/commits/store/actions.js b/app/assets/javascripts/projects/commits/store/actions.js
index 72d4f0c31e5..741dc20b1f1 100644
--- a/app/assets/javascripts/projects/commits/store/actions.js
+++ b/app/assets/javascripts/projects/commits/store/actions.js
@@ -1,8 +1,8 @@
+import * as Sentry from '@sentry/browser';
import { deprecatedCreateFlash as createFlash } from '~/flash';
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 {
diff --git a/app/assets/javascripts/projects/compare/components/app.vue b/app/assets/javascripts/projects/compare/components/app.vue
index 05bd0f1370b..d2fb524489e 100644
--- a/app/assets/javascripts/projects/compare/components/app.vue
+++ b/app/assets/javascripts/projects/compare/components/app.vue
@@ -1,12 +1,12 @@
<script>
import { GlButton } from '@gitlab/ui';
import csrf from '~/lib/utils/csrf';
-import RevisionDropdown from './revision_dropdown.vue';
+import RevisionCard from './revision_card.vue';
export default {
csrf,
components: {
- RevisionDropdown,
+ RevisionCard,
GlButton,
},
props: {
@@ -48,42 +48,53 @@ export default {
<template>
<form
ref="form"
- class="form-inline js-requires-input js-signature-container"
+ class="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"
+ <div
+ class="gl-lg-flex-direction-row gl-lg-display-flex gl-align-items-center compare-revision-cards"
>
- {{ 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>
+ <revision-card
+ :refs-project-path="refsProjectPath"
+ revision-text="Source"
+ params-name="to"
+ :params-branch="paramsTo"
+ />
+ <div
+ class="compare-ellipsis gl-display-flex gl-justify-content-center gl-align-items-center gl-my-4 gl-md-my-0"
+ data-testid="ellipsis"
+ >
+ ...
+ </div>
+ <revision-card
+ :refs-project-path="refsProjectPath"
+ revision-text="Target"
+ params-name="from"
+ :params-branch="paramsFrom"
+ />
+ </div>
+ <div class="gl-mt-4">
+ <gl-button category="primary" variant="success" @click="onSubmit">
+ {{ s__('CompareRevisions|Compare') }}
+ </gl-button>
+ <gl-button
+ v-if="projectMergeRequestPath"
+ :href="projectMergeRequestPath"
+ data-testid="projectMrButton"
+ class="btn btn-default gl-button"
+ >
+ {{ s__('CompareRevisions|View open merge request') }}
+ </gl-button>
+ <gl-button
+ v-else-if="createMrPath"
+ :href="createMrPath"
+ data-testid="createMrButton"
+ class="btn btn-default gl-button"
+ >
+ {{ s__('CompareRevisions|Create merge request') }}
+ </gl-button>
+ </div>
</form>
</template>
diff --git a/app/assets/javascripts/projects/compare/components/app_legacy.vue b/app/assets/javascripts/projects/compare/components/app_legacy.vue
new file mode 100644
index 00000000000..c0ff58ee074
--- /dev/null
+++ b/app/assets/javascripts/projects/compare/components/app_legacy.vue
@@ -0,0 +1,89 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import csrf from '~/lib/utils/csrf';
+import RevisionDropdown from './revision_dropdown_legacy.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>
+ <gl-button
+ v-if="projectMergeRequestPath"
+ :href="projectMergeRequestPath"
+ data-testid="projectMrButton"
+ class="btn btn-default gl-button gl-ml-3"
+ >
+ {{ s__('CompareRevisions|View open merge request') }}
+ </gl-button>
+ <gl-button
+ v-else-if="createMrPath"
+ :href="createMrPath"
+ data-testid="createMrButton"
+ class="btn btn-default gl-button gl-ml-3"
+ >
+ {{ s__('CompareRevisions|Create merge request') }}
+ </gl-button>
+ </form>
+</template>
diff --git a/app/assets/javascripts/projects/compare/components/repo_dropdown.vue b/app/assets/javascripts/projects/compare/components/repo_dropdown.vue
new file mode 100644
index 00000000000..822dfc09d81
--- /dev/null
+++ b/app/assets/javascripts/projects/compare/components/repo_dropdown.vue
@@ -0,0 +1,93 @@
+<script>
+import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
+
+const SOURCE_PARAM_NAME = 'to';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlSearchBoxByType,
+ },
+ inject: ['projectTo', 'projectsFrom'],
+ props: {
+ paramsName: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ searchTerm: '',
+ selectedRepo: {},
+ };
+ },
+ computed: {
+ filteredRepos() {
+ const lowerCaseSearchTerm = this.searchTerm.toLowerCase();
+
+ return this?.projectsFrom.filter(({ name }) =>
+ name.toLowerCase().includes(lowerCaseSearchTerm),
+ );
+ },
+ isSourceRevision() {
+ return this.paramsName === SOURCE_PARAM_NAME;
+ },
+ inputName() {
+ return `${this.paramsName}_project_id`;
+ },
+ },
+ mounted() {
+ this.setDefaultRepo();
+ },
+ methods: {
+ onClick(repo) {
+ this.selectedRepo = repo;
+ this.emitTargetProject(repo.name);
+ },
+ setDefaultRepo() {
+ if (this.isSourceRevision) {
+ this.selectedRepo = this.projectTo;
+ return;
+ }
+
+ const [defaultTargetProject] = this.projectsFrom;
+ this.emitTargetProject(defaultTargetProject.name);
+ this.selectedRepo = defaultTargetProject;
+ },
+ emitTargetProject(name) {
+ if (!this.isSourceRevision) {
+ this.$emit('changeTargetProject', name);
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <input type="hidden" :name="inputName" :value="selectedRepo.id" />
+ <gl-dropdown
+ :text="selectedRepo.name"
+ :header-text="s__(`CompareRevisions|Select target project`)"
+ class="gl-w-full gl-font-monospace gl-sm-pr-3"
+ toggle-class="gl-min-w-0"
+ :disabled="isSourceRevision"
+ >
+ <template #header>
+ <gl-search-box-by-type v-if="!isSourceRevision" v-model.trim="searchTerm" />
+ </template>
+ <template v-if="!isSourceRevision">
+ <gl-dropdown-item
+ v-for="repo in filteredRepos"
+ :key="repo.id"
+ is-check-item
+ :is-checked="selectedRepo.id === repo.id"
+ @click="onClick(repo)"
+ >
+ {{ repo.name }}
+ </gl-dropdown-item>
+ </template>
+ </gl-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/projects/compare/components/revision_card.vue b/app/assets/javascripts/projects/compare/components/revision_card.vue
new file mode 100644
index 00000000000..15d24792310
--- /dev/null
+++ b/app/assets/javascripts/projects/compare/components/revision_card.vue
@@ -0,0 +1,65 @@
+<script>
+import { GlCard } from '@gitlab/ui';
+import RepoDropdown from './repo_dropdown.vue';
+import RevisionDropdown from './revision_dropdown.vue';
+
+export default {
+ components: {
+ RepoDropdown,
+ RevisionDropdown,
+ GlCard,
+ },
+ 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 {
+ selectedRefsProjectPath: this.refsProjectPath,
+ };
+ },
+ methods: {
+ onChangeTargetProject(targetProjectName) {
+ if (this.paramsName === 'from') {
+ this.selectedRefsProjectPath = `/${targetProjectName}/refs`;
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-card header-class="gl-py-2 gl-px-3 gl-font-weight-bold" body-class="gl-px-3">
+ <template #header>
+ {{ s__(`CompareRevisions|${revisionText}`) }}
+ </template>
+ <div class="gl-sm-display-flex gl-align-items-center">
+ <repo-dropdown
+ class="gl-sm-w-half"
+ :params-name="paramsName"
+ @changeTargetProject="onChangeTargetProject"
+ />
+ <revision-dropdown
+ class="gl-sm-w-half gl-mt-3 gl-sm-mt-0"
+ :refs-project-path="selectedRefsProjectPath"
+ :params-name="paramsName"
+ :params-branch="paramsBranch"
+ />
+ </div>
+ </gl-card>
+</template>
diff --git a/app/assets/javascripts/projects/compare/components/revision_dropdown.vue b/app/assets/javascripts/projects/compare/components/revision_dropdown.vue
index 13d80b5ae0b..a175af2f32e 100644
--- a/app/assets/javascripts/projects/compare/components/revision_dropdown.vue
+++ b/app/assets/javascripts/projects/compare/components/revision_dropdown.vue
@@ -4,6 +4,8 @@ import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
+const emptyDropdownText = s__('CompareRevisions|Select branch/tag');
+
export default {
components: {
GlDropdown,
@@ -16,10 +18,6 @@ export default {
type: String,
required: true,
},
- revisionText: {
- type: String,
- required: true,
- },
paramsName: {
type: String,
required: true,
@@ -55,12 +53,24 @@ export default {
return this.filteredTags.length;
},
},
+ watch: {
+ refsProjectPath(newRefsProjectPath, oldRefsProjectPath) {
+ if (newRefsProjectPath !== oldRefsProjectPath) {
+ this.fetchBranchesAndTags(true);
+ }
+ },
+ },
mounted() {
this.fetchBranchesAndTags();
},
methods: {
- fetchBranchesAndTags() {
+ fetchBranchesAndTags(reset = false) {
const endpoint = this.refsProjectPath;
+ this.loading = true;
+
+ if (reset) {
+ this.selectedRevision = this.getDefaultBranch();
+ }
return axios
.get(endpoint)
@@ -70,9 +80,9 @@ export default {
})
.catch(() => {
createFlash({
- message: `${s__(
- 'CompareRevisions|There was an error while updating the branch/tag list. Please try again.',
- )}`,
+ message: s__(
+ 'CompareRevisions|There was an error while loading the branch/tag list. Please try again.',
+ ),
});
})
.finally(() => {
@@ -80,7 +90,7 @@ export default {
});
},
getDefaultBranch() {
- return this.paramsBranch || s__('CompareRevisions|Select branch/tag');
+ return this.paramsBranch || emptyDropdownText;
},
onClick(revision) {
this.selectedRevision = revision;
@@ -93,53 +103,46 @@ export default {
</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"
+ <div :class="`js-compare-${paramsName}-dropdown`">
+ <input type="hidden" :name="paramsName" :value="selectedRevision" />
+ <gl-dropdown
+ class="gl-w-full gl-font-monospace"
+ toggle-class="form-control compare-dropdown-toggle js-compare-dropdown gl-min-w-0"
+ :text="selectedRevision"
+ :header-text="s__('CompareRevisions|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)"
>
- <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>
+ {{ tag }}
+ </gl-dropdown-item>
+ </gl-dropdown>
</div>
</template>
diff --git a/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue b/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.vue
new file mode 100644
index 00000000000..13d80b5ae0b
--- /dev/null
+++ b/app/assets/javascripts/projects/compare/components/revision_dropdown_legacy.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
index 4337eecb667..4ba4e308cd4 100644
--- a/app/assets/javascripts/projects/compare/index.js
+++ b/app/assets/javascripts/projects/compare/index.js
@@ -1,8 +1,46 @@
import Vue from 'vue';
import CompareApp from './components/app.vue';
+import CompareAppLegacy from './components/app_legacy.vue';
export default function init() {
const el = document.getElementById('js-compare-selector');
+
+ if (gon.features?.compareRepoDropdown) {
+ const {
+ refsProjectPath,
+ paramsFrom,
+ paramsTo,
+ projectCompareIndexPath,
+ projectMergeRequestPath,
+ createMrPath,
+ projectTo,
+ projectsFrom,
+ } = el.dataset;
+
+ return new Vue({
+ el,
+ components: {
+ CompareApp,
+ },
+ provide: {
+ projectTo: JSON.parse(projectTo),
+ projectsFrom: JSON.parse(projectsFrom),
+ },
+ render(createElement) {
+ return createElement(CompareApp, {
+ props: {
+ refsProjectPath,
+ paramsFrom,
+ paramsTo,
+ projectCompareIndexPath,
+ projectMergeRequestPath,
+ createMrPath,
+ },
+ });
+ },
+ });
+ }
+
const {
refsProjectPath,
paramsFrom,
@@ -15,10 +53,10 @@ export default function init() {
return new Vue({
el,
components: {
- CompareApp,
+ CompareAppLegacy,
},
render(createElement) {
- return createElement(CompareApp, {
+ return createElement(CompareAppLegacy, {
props: {
refsProjectPath,
paramsFrom,
diff --git a/app/assets/javascripts/projects/details/upload_button.vue b/app/assets/javascripts/projects/details/upload_button.vue
new file mode 100644
index 00000000000..5b19f15c233
--- /dev/null
+++ b/app/assets/javascripts/projects/details/upload_button.vue
@@ -0,0 +1,59 @@
+<script>
+import { GlButton, GlModalDirective } from '@gitlab/ui';
+import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
+import { trackFileUploadEvent } from '../upload_file_experiment_tracking';
+
+const UPLOAD_BLOB_MODAL_ID = 'details-modal-upload-blob';
+
+export default {
+ components: {
+ GlButton,
+ UploadBlobModal,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ },
+ inject: {
+ targetBranch: {
+ default: '',
+ },
+ originalBranch: {
+ default: '',
+ },
+ canPushCode: {
+ default: false,
+ },
+ path: {
+ default: '',
+ },
+ projectPath: {
+ default: '',
+ },
+ },
+ methods: {
+ trackOpenModal() {
+ trackFileUploadEvent('click_upload_modal_trigger');
+ },
+ },
+ uploadBlobModalId: UPLOAD_BLOB_MODAL_ID,
+};
+</script>
+<template>
+ <span>
+ <gl-button
+ v-gl-modal="$options.uploadBlobModalId"
+ icon="upload"
+ data-testid="upload-file-button"
+ @click="trackOpenModal"
+ >{{ __('Upload File') }}</gl-button
+ >
+ <upload-blob-modal
+ :modal-id="$options.uploadBlobModalId"
+ :commit-message="__('Upload New File')"
+ :target-branch="targetBranch"
+ :original-branch="originalBranch"
+ :can-push-code="canPushCode"
+ :path="path"
+ />
+ </span>
+</template>
diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/new_project_push_tip_popover.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/new_project_push_tip_popover.vue
new file mode 100644
index 00000000000..e42d9154866
--- /dev/null
+++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/new_project_push_tip_popover.vue
@@ -0,0 +1,66 @@
+<script>
+import { GlPopover, GlFormInputGroup } from '@gitlab/ui';
+import { __ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+export default {
+ components: {
+ GlPopover,
+ GlFormInputGroup,
+ ClipboardButton,
+ },
+ inject: ['pushToCreateProjectCommand', 'workingWithProjectsHelpPath'],
+ props: {
+ target: {
+ type: [Function, HTMLElement],
+ required: true,
+ },
+ },
+ i18n: {
+ clipboardButtonTitle: __('Copy command'),
+ commandInputAriaLabel: __('Push project from command line'),
+ helpLinkText: __('What does this command do?'),
+ labelText: __('Private projects can be created in your personal namespace with:'),
+ popoverTitle: __('Push to create a project'),
+ },
+};
+</script>
+<template>
+ <gl-popover
+ :target="target"
+ :title="$options.i18n.popoverTitle"
+ triggers="click blur"
+ placement="top"
+ >
+ <p>
+ <label for="push-to-create-tip" class="gl-font-weight-normal">
+ {{ $options.i18n.labelText }}
+ </label>
+ </p>
+ <p>
+ <gl-form-input-group
+ id="push-to-create-tip"
+ :value="pushToCreateProjectCommand"
+ readonly
+ select-on-click
+ :aria-label="$options.i18n.commandInputAriaLabel"
+ >
+ <template #append>
+ <clipboard-button
+ :text="pushToCreateProjectCommand"
+ :title="$options.i18n.clipboardButtonTitle"
+ tooltip-placement="right"
+ />
+ </template>
+ </gl-form-input-group>
+ </p>
+ <p>
+ <a
+ :href="`${workingWithProjectsHelpPath}#push-to-create-a-new-project`"
+ class="gl-font-sm"
+ target="_blank"
+ >{{ $options.i18n.helpLinkText }}</a
+ >
+ </p>
+ </gl-popover>
+</template>
diff --git a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue
index 63a65975fff..ed82a635b1f 100644
--- a/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue
+++ b/app/assets/javascripts/projects/experiment_new_project_creation/components/welcome.vue
@@ -1,15 +1,13 @@
<script>
/* eslint-disable vue/no-v-html */
-import { GlPopover } from '@gitlab/ui';
import Tracking from '~/tracking';
-import LegacyContainer from './legacy_container.vue';
+import NewProjectPushTipPopover from './new_project_push_tip_popover.vue';
const trackingMixin = Tracking.mixin(gon.tracking_data);
export default {
components: {
- GlPopover,
- LegacyContainer,
+ NewProjectPushTipPopover,
},
mixins: [trackingMixin],
props: {
@@ -52,19 +50,15 @@ export default {
<p>
{{ __('You can also create a project from the command line.') }}
<a
- id="cli-tip"
+ ref="clipTip"
href="#"
click.prevent
class="push-new-project-tip"
- data-title="Push to create a project"
rel="noopener noreferrer"
>
{{ __('Show command') }}
</a>
-
- <gl-popover target="cli-tip" triggers="click blur" placement="top">
- <legacy-container selector=".push-new-project-tip-template" />
- </gl-popover>
+ <new-project-push-tip-popover :target="() => $refs.clipTip" />
</p>
</div>
</div>
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 0414f7ef6a5..ea686d4e1e8 100644
--- a/app/assets/javascripts/projects/experiment_new_project_creation/index.js
+++ b/app/assets/javascripts/projects/experiment_new_project_creation/index.js
@@ -2,11 +2,17 @@ import Vue from 'vue';
import NewProjectCreationApp from './components/app.vue';
export default function initNewProjectCreation(el, props) {
+ const { pushToCreateProjectCommand, workingWithProjectsHelpPath } = el.dataset;
+
return new Vue({
el,
components: {
NewProjectCreationApp,
},
+ provide: {
+ workingWithProjectsHelpPath,
+ pushToCreateProjectCommand,
+ },
render(h) {
return h(NewProjectCreationApp, { props });
},
diff --git a/app/assets/javascripts/projects/feature_flags_user_lists/show/index.js b/app/assets/javascripts/projects/feature_flags_user_lists/show/index.js
new file mode 100644
index 00000000000..2bd3e57322d
--- /dev/null
+++ b/app/assets/javascripts/projects/feature_flags_user_lists/show/index.js
@@ -0,0 +1,23 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import UserList from '~/user_lists/components/user_list.vue';
+import createStore from '~/user_lists/store/show';
+
+Vue.use(Vuex);
+
+export default function featureFlagsUserListInit() {
+ const el = document.getElementById('js-edit-user-list');
+
+ if (!el) {
+ return null;
+ }
+
+ return new Vue({
+ el,
+ store: createStore(el.dataset),
+ render(h) {
+ const { emptyStatePath } = el.dataset;
+ return h(UserList, { props: { emptyStatePath } });
+ },
+ });
+}
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 733f833d51a..6a963616224 100644
--- a/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue
+++ b/app/assets/javascripts/projects/pipelines/charts/components/pipeline_charts.vue
@@ -252,10 +252,10 @@ export default {
},
errorTexts: {
[LOAD_ANALYTICS_FAILURE]: s__(
- 'PipelineCharts|An error has ocurred when retrieving the analytics data',
+ 'PipelineCharts|An error has occurred when retrieving the analytics data',
),
[LOAD_PIPELINES_FAILURE]: s__(
- 'PipelineCharts|An error has ocurred when retrieving the pipelines data',
+ 'PipelineCharts|An error has occurred 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.'),
@@ -292,7 +292,7 @@ export default {
failure.text
}}</gl-alert>
<div class="gl-mb-3">
- <h3>{{ s__('PipelineCharts|CI / CD Analytics') }}</h3>
+ <h3>{{ s__('PipelineCharts|CI/CD Analytics') }}</h3>
</div>
<h4 class="gl-my-4">{{ s__('PipelineCharts|Overall statistics') }}</h4>
<div class="row">
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index e3ba84102a8..04ea6f760f6 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -1,6 +1,5 @@
import $ from 'jquery';
import DEFAULT_PROJECT_TEMPLATES from 'ee_else_ce/projects/default_project_templates';
-import { addSelectOnFocusBehaviour } from '../lib/utils/common_utils';
import {
convertToTitleCase,
humanize,
@@ -81,7 +80,6 @@ const bindEvents = () => {
const $selectedTemplateText = $('.selected-template');
const $changeTemplateBtn = $('.change-template');
const $selectedIcon = $('.selected-icon');
- const $pushNewProjectTipTrigger = $('.push-new-project-tip');
const $projectTemplateButtons = $('.project-templates-buttons');
const $projectName = $('.tab-pane.active #project_name');
@@ -108,39 +106,6 @@ const bindEvents = () => {
);
});
- if ($pushNewProjectTipTrigger) {
- $pushNewProjectTipTrigger
- .removeAttr('rel')
- .removeAttr('target')
- .on('click', (e) => {
- e.preventDefault();
- })
- .popover({
- title: $pushNewProjectTipTrigger.data('title'),
- placement: 'bottom',
- html: true,
- content: $('.push-new-project-tip-template').html(),
- })
- .on('shown.bs.popover', () => {
- $(document).on('click.popover touchstart.popover', (event) => {
- if ($(event.target).closest('.popover').length === 0) {
- $pushNewProjectTipTrigger.trigger('click');
- }
- });
-
- const target = $(`#${$pushNewProjectTipTrigger.attr('aria-describedby')}`).find(
- '.js-select-on-focus',
- );
- addSelectOnFocusBehaviour(target);
-
- target.focus();
- })
- .on('hide.bs.popover', () => {
- // eslint-disable-next-line @gitlab/no-global-event-off
- $(document).off('click.popover touchstart.popover');
- });
- }
-
function chooseTemplate() {
$projectTemplateButtons.addClass('hidden');
$projectFieldsForm.addClass('selected');
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 9b3c0dd2755..fb00f58abae 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
@@ -95,7 +95,7 @@ export default {
})
.catch((err) => {
this.showAlert(
- sprintf(__('An error occured while saving changes: %{error}'), {
+ sprintf(__('An error occurred while saving changes: %{error}'), {
error: err?.response?.data?.message,
}),
);
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 39d9a6a4239..3294a37c26a 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
@@ -4,6 +4,9 @@ import { __ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
export default {
+ i18n: {
+ toggleLabel: __('Activate Service Desk'),
+ },
components: {
ClipboardButton,
GlButton,
@@ -99,11 +102,12 @@ export default {
id="service-desk-checkbox"
:value="isEnabled"
class="d-inline-block align-middle mr-1"
- label-position="left"
+ :label="$options.i18n.toggleLabel"
+ label-position="hidden"
@change="onCheckboxToggle"
/>
- <label class="align-middle" for="service-desk-checkbox">
- {{ __('Activate Service Desk') }}
+ <label class="align-middle">
+ {{ $options.i18n.toggleLabel }}
</label>
<div v-if="isEnabled" class="row mt-3">
<div class="col-md-9 mb-0">
@@ -162,6 +166,7 @@ export default {
<gl-form-select
id="service-desk-template-select"
v-model="selectedTemplate"
+ data-qa-selector="service_desk_template_dropdown"
:options="templateOptions"
/>
<label for="service-desk-email-from-name" class="mt-3">
@@ -175,6 +180,7 @@ export default {
<gl-button
variant="success"
class="gl-mt-5"
+ data-qa-selector="save_service_desk_settings_button"
:disabled="isTemplateSaving"
@click="onSaveTemplate"
>
diff --git a/app/assets/javascripts/projects/upload_file_experiment.js b/app/assets/javascripts/projects/upload_file_experiment.js
new file mode 100644
index 00000000000..a7519f2bce8
--- /dev/null
+++ b/app/assets/javascripts/projects/upload_file_experiment.js
@@ -0,0 +1,33 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import createRouter from '~/repository/router';
+import UploadButton from './details/upload_button.vue';
+
+export const initUploadFileTrigger = () => {
+ const uploadFileTriggerEl = document.querySelector('.js-upload-file-experiment-trigger');
+
+ if (!uploadFileTriggerEl) return false;
+
+ const {
+ targetBranch,
+ originalBranch,
+ canPushCode,
+ path,
+ projectPath,
+ } = uploadFileTriggerEl.dataset;
+
+ return new Vue({
+ el: uploadFileTriggerEl,
+ router: createRouter(projectPath, originalBranch),
+ provide: {
+ targetBranch,
+ originalBranch,
+ canPushCode: parseBoolean(canPushCode),
+ path,
+ projectPath,
+ },
+ render(h) {
+ return h(UploadButton);
+ },
+ });
+};
diff --git a/app/assets/javascripts/projects/upload_file_experiment_tracking.js b/app/assets/javascripts/projects/upload_file_experiment_tracking.js
new file mode 100644
index 00000000000..c5e93f19b32
--- /dev/null
+++ b/app/assets/javascripts/projects/upload_file_experiment_tracking.js
@@ -0,0 +1,9 @@
+import ExperimentTracking from '~/experimentation/experiment_tracking';
+
+export const trackFileUploadEvent = (eventName) => {
+ const isEmpty = Boolean(document.querySelector('.project-home-panel.empty-project'));
+ const property = isEmpty ? 'empty' : 'nonempty';
+ const label = 'blob-upload-modal';
+ const FileUploadTracking = new ExperimentTracking('empty_repo_upload', { label, property });
+ FileUploadTracking.event(eventName);
+};