diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 18:18:33 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 18:18:33 +0000 |
commit | f64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch) | |
tree | a2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /app/assets/javascripts/projects | |
parent | bfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff) | |
download | gitlab-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')
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); +}; |