diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-14 12:10:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-14 12:10:22 +0300 |
commit | 0ea7b5c8a3f7afaae6b03279af56cd880d538bd7 (patch) | |
tree | 92c0e70189cdde7a73fe3c1b19d7f0ee1c800297 /app | |
parent | 298ae510cefeae8a8ffb9a868fefd9eceeac4122 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
10 files changed, 275 insertions, 49 deletions
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 2b0ddbed7b3..90d8f6ebc58 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -3,7 +3,7 @@ import { GlButton } from '@gitlab/ui'; import { getMilestone } from 'ee_else_ce/boards/boards_util'; import ListIssue from 'ee_else_ce/boards/models/issue'; import eventHub from '../eventhub'; -import ProjectSelect from './project_select.vue'; +import ProjectSelect from './project_select_deprecated.vue'; import boardsStore from '../stores/boards_store'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; diff --git a/app/assets/javascripts/boards/components/board_new_issue_new.vue b/app/assets/javascripts/boards/components/board_new_issue_new.vue index 674a49e01ef..5a1b8a7672f 100644 --- a/app/assets/javascripts/boards/components/board_new_issue_new.vue +++ b/app/assets/javascripts/boards/components/board_new_issue_new.vue @@ -1,5 +1,5 @@ <script> -import { mapActions } from 'vuex'; +import { mapActions, mapState } from 'vuex'; import { GlButton } from '@gitlab/ui'; import { getMilestone } from 'ee_else_ce/boards/boards_util'; import eventHub from '../eventhub'; @@ -28,10 +28,10 @@ export default { data() { return { title: '', - selectedProject: {}, }; }, computed: { + ...mapState(['selectedProject']), disabled() { if (this.groupId) { return this.title === '' || !this.selectedProject.name; @@ -45,7 +45,6 @@ export default { }, mounted() { this.$refs.input.focus(); - eventHub.$on('setSelectedProject', this.setSelectedProject); }, methods: { ...mapActions(['addListNewIssue']), @@ -68,7 +67,7 @@ export default { labelIds: labels?.map((l) => l.id), assigneeIds: assignees?.map((a) => a?.id), milestoneId: milestone?.id, - projectPath: this.selectedProject.path, + projectPath: this.selectedProject.fullPath, weight: weight >= 0 ? weight : null, }, list: this.list, @@ -80,9 +79,6 @@ export default { this.title = ''; eventHub.$emit(`toggle-issue-form-${this.list.id}`); }, - setSelectedProject(selectedProject) { - this.selectedProject = selectedProject; - }, }, }; </script> diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index aecb2125e04..04699d0d3a4 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -1,14 +1,14 @@ <script> +import { mapActions, mapState } from 'vuex'; import { GlDropdown, GlDropdownItem, GlDropdownText, GlSearchBoxByType, + GlIntersectionObserver, GlLoadingIcon, } from '@gitlab/ui'; -import eventHub from '../eventhub'; import { s__ } from '~/locale'; -import Api from '../../api'; import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; import { ListType } from '../constants'; @@ -27,6 +27,7 @@ export default { order_by: 'similarity', }, components: { + GlIntersectionObserver, GlLoadingIcon, GlDropdown, GlDropdownItem, @@ -43,13 +44,12 @@ export default { data() { return { initialLoading: true, - isFetching: false, - projects: [], selectedProject: {}, searchTerm: '', }; }, computed: { + ...mapState(['groupProjects', 'groupProjectsFlags']), selectedProjectName() { return this.selectedProject.name || this.$options.i18n.dropdownText; }, @@ -65,47 +65,30 @@ export default { }; }, isFetchResultEmpty() { - return this.projects.length === 0; + return this.groupProjects.length === 0; + }, + hasNextPage() { + return this.groupProjectsFlags.pageInfo?.hasNextPage; }, }, watch: { searchTerm() { - this.fetchProjects(); + this.fetchGroupProjects({ search: this.searchTerm }); }, }, - async mounted() { - await this.fetchProjects(); + mounted() { + this.fetchGroupProjects({}); this.initialLoading = false; }, methods: { - async fetchProjects() { - this.isFetching = true; - try { - const projects = await Api.groupProjects(this.groupId, this.searchTerm, this.fetchOptions); - - this.projects = projects.map((project) => { - return { - id: project.id, - name: project.name, - namespacedName: project.name_with_namespace, - path: project.path_with_namespace, - }; - }); - } catch (err) { - /* Handled in Api.groupProjects */ - } finally { - this.isFetching = false; - } - }, + ...mapActions(['fetchGroupProjects', 'setSelectedProject']), selectProject(projectId) { - this.selectedProject = this.projects.find((project) => project.id === projectId); - - /* - TODO Remove eventhub, use Vuex for BoardNewIssue and GraphQL for BoardNewIssueNew - https://gitlab.com/gitlab-org/gitlab/-/issues/276173 - */ - eventHub.$emit('setSelectedProject', this.selectedProject); + this.selectedProject = this.groupProjects.find((project) => project.id === projectId); + this.setSelectedProject(this.selectedProject); + }, + loadMoreProjects() { + this.fetchGroupProjects({ search: this.searchTerm, fetchNext: true }); }, }, }; @@ -130,20 +113,29 @@ export default { :placeholder="$options.i18n.searchPlaceholder" /> <gl-dropdown-item - v-for="project in projects" - v-show="!isFetching" + v-for="project in groupProjects" + v-show="!groupProjectsFlags.isLoading" :key="project.id" :name="project.name" @click="selectProject(project.id)" > - {{ project.namespacedName }} + {{ project.nameWithNamespace }} </gl-dropdown-item> - <gl-dropdown-text v-show="isFetching" data-testid="dropdown-text-loading-icon"> + <gl-dropdown-text + v-show="groupProjectsFlags.isLoading" + data-testid="dropdown-text-loading-icon" + > <gl-loading-icon class="gl-mx-auto" /> </gl-dropdown-text> - <gl-dropdown-text v-if="isFetchResultEmpty && !isFetching" data-testid="empty-result-message"> + <gl-dropdown-text + v-if="isFetchResultEmpty && !groupProjectsFlags.isLoading" + data-testid="empty-result-message" + > <span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span> </gl-dropdown-text> + <gl-intersection-observer v-if="hasNextPage" @appear="loadMoreProjects"> + <gl-loading-icon v-if="groupProjectsFlags.isLoadingMore" size="md" /> + </gl-intersection-observer> </gl-dropdown> </div> </template> diff --git a/app/assets/javascripts/boards/components/project_select_deprecated.vue b/app/assets/javascripts/boards/components/project_select_deprecated.vue new file mode 100644 index 00000000000..f14db42d43c --- /dev/null +++ b/app/assets/javascripts/boards/components/project_select_deprecated.vue @@ -0,0 +1,145 @@ +<script> +import { + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, + GlLoadingIcon, +} from '@gitlab/ui'; +import eventHub from '../eventhub'; +import { s__ } from '~/locale'; +import Api from '../../api'; +import { featureAccessLevel } from '~/pages/projects/shared/permissions/constants'; +import { ListType } from '../constants'; + +export default { + name: 'ProjectSelect', + i18n: { + headerTitle: s__(`BoardNewIssue|Projects`), + dropdownText: s__(`BoardNewIssue|Select a project`), + searchPlaceholder: s__(`BoardNewIssue|Search projects`), + emptySearchResult: s__(`BoardNewIssue|No matching results`), + }, + defaultFetchOptions: { + with_issues_enabled: true, + with_shared: false, + include_subgroups: true, + order_by: 'similarity', + }, + components: { + GlLoadingIcon, + GlDropdown, + GlDropdownItem, + GlDropdownText, + GlSearchBoxByType, + }, + props: { + list: { + type: Object, + required: true, + }, + }, + inject: ['groupId'], + data() { + return { + initialLoading: true, + isFetching: false, + projects: [], + selectedProject: {}, + searchTerm: '', + }; + }, + computed: { + selectedProjectName() { + return this.selectedProject.name || this.$options.i18n.dropdownText; + }, + fetchOptions() { + const additionalAttrs = {}; + if (this.list.type && this.list.type !== ListType.backlog) { + additionalAttrs.min_access_level = featureAccessLevel.EVERYONE; + } + + return { + ...this.$options.defaultFetchOptions, + ...additionalAttrs, + }; + }, + isFetchResultEmpty() { + return this.projects.length === 0; + }, + }, + watch: { + searchTerm() { + this.fetchProjects(); + }, + }, + async mounted() { + await this.fetchProjects(); + + this.initialLoading = false; + }, + methods: { + async fetchProjects() { + this.isFetching = true; + try { + const projects = await Api.groupProjects(this.groupId, this.searchTerm, this.fetchOptions); + + this.projects = projects.map((project) => { + return { + id: project.id, + name: project.name, + namespacedName: project.name_with_namespace, + path: project.path_with_namespace, + }; + }); + } catch (err) { + /* Handled in Api.groupProjects */ + } finally { + this.isFetching = false; + } + }, + selectProject(projectId) { + this.selectedProject = this.projects.find((project) => project.id === projectId); + + eventHub.$emit('setSelectedProject', this.selectedProject); + }, + }, +}; +</script> + +<template> + <div> + <label class="gl-font-weight-bold gl-mt-3" data-testid="header-label">{{ + $options.i18n.headerTitle + }}</label> + <gl-dropdown + data-testid="project-select-dropdown" + :text="selectedProjectName" + :header-text="$options.i18n.headerTitle" + block + menu-class="gl-w-full!" + :loading="initialLoading" + > + <gl-search-box-by-type + v-model.trim="searchTerm" + debounce="250" + :placeholder="$options.i18n.searchPlaceholder" + /> + <gl-dropdown-item + v-for="project in projects" + v-show="!isFetching" + :key="project.id" + :name="project.name" + @click="selectProject(project.id)" + > + {{ project.namespacedName }} + </gl-dropdown-item> + <gl-dropdown-text v-show="isFetching" data-testid="dropdown-text-loading-icon"> + <gl-loading-icon class="gl-mx-auto" /> + </gl-dropdown-text> + <gl-dropdown-text v-if="isFetchResultEmpty && !isFetching" data-testid="empty-result-message"> + <span class="gl-text-gray-500">{{ $options.i18n.emptySearchResult }}</span> + </gl-dropdown-text> + </gl-dropdown> + </div> +</template> diff --git a/app/assets/javascripts/boards/graphql/group_projects.query.graphql b/app/assets/javascripts/boards/graphql/group_projects.query.graphql new file mode 100644 index 00000000000..1afa6e48547 --- /dev/null +++ b/app/assets/javascripts/boards/graphql/group_projects.query.graphql @@ -0,0 +1,17 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" + +query getGroupProjects($fullPath: ID!, $search: String, $after: String) { + group(fullPath: $fullPath) { + projects(search: $search, after: $after, first: 100) { + nodes { + id + name + fullPath + nameWithNamespace + } + pageInfo { + ...PageInfo + } + } + } +} diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 46d551eb50c..0fe6d669f86 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -29,6 +29,7 @@ import issueSetDueDateMutation from '../graphql/issue_set_due_date.mutation.grap import issueSetSubscriptionMutation from '../graphql/issue_set_subscription.mutation.graphql'; import issueSetMilestoneMutation from '../graphql/issue_set_milestone.mutation.graphql'; import issueSetTitleMutation from '../graphql/issue_set_title.mutation.graphql'; +import groupProjectsQuery from '../graphql/group_projects.query.graphql'; const notImplemented = () => { /* eslint-disable-next-line @gitlab/require-i18n-strings */ @@ -498,6 +499,37 @@ export default { }); }, + fetchGroupProjects: ({ commit, state }, { search = '', fetchNext = false }) => { + commit(types.REQUEST_GROUP_PROJECTS, fetchNext); + + const { fullPath } = state; + + const variables = { + fullPath, + search: search !== '' ? search : undefined, + after: fetchNext ? state.groupProjectsFlags.pageInfo.endCursor : undefined, + }; + + return gqlClient + .query({ + query: groupProjectsQuery, + variables, + }) + .then(({ data }) => { + const { projects } = data.group; + commit(types.RECEIVE_GROUP_PROJECTS_SUCCESS, { + projects: projects.nodes, + pageInfo: projects.pageInfo, + fetchNext, + }); + }) + .catch(() => commit(types.RECEIVE_GROUP_PROJECTS_FAILURE)); + }, + + setSelectedProject: ({ commit }, project) => { + commit(types.SET_SELECTED_PROJECT, project); + }, + fetchBacklog: () => { notImplemented(); }, diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js index 2b2c2bee51c..4697f39498a 100644 --- a/app/assets/javascripts/boards/stores/mutation_types.js +++ b/app/assets/javascripts/boards/stores/mutation_types.js @@ -36,3 +36,7 @@ export const SET_ACTIVE_ID = 'SET_ACTIVE_ID'; export const UPDATE_ISSUE_BY_ID = 'UPDATE_ISSUE_BY_ID'; export const SET_ASSIGNEE_LOADING = 'SET_ASSIGNEE_LOADING'; export const RESET_ISSUES = 'RESET_ISSUES'; +export const REQUEST_GROUP_PROJECTS = 'REQUEST_GROUP_PROJECTS'; +export const RECEIVE_GROUP_PROJECTS_SUCCESS = 'RECEIVE_GROUP_PROJECTS_SUCCESS'; +export const RECEIVE_GROUP_PROJECTS_FAILURE = 'RECEIVE_GROUP_PROJECTS_FAILURE'; +export const SET_SELECTED_PROJECT = 'SET_SELECTED_PROJECT'; diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index 62ac2ca1417..6c79b22d308 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -237,4 +237,25 @@ export default { [mutationTypes.TOGGLE_EMPTY_STATE]: () => { notImplemented(); }, + + [mutationTypes.REQUEST_GROUP_PROJECTS]: (state, fetchNext) => { + Vue.set(state, 'groupProjectsFlags', { + [fetchNext ? 'isLoadingMore' : 'isLoading']: true, + pageInfo: state.groupProjectsFlags.pageInfo, + }); + }, + + [mutationTypes.RECEIVE_GROUP_PROJECTS_SUCCESS]: (state, { projects, pageInfo, fetchNext }) => { + Vue.set(state, 'groupProjects', fetchNext ? [...state.groupProjects, ...projects] : projects); + Vue.set(state, 'groupProjectsFlags', { isLoading: false, isLoadingMore: false, pageInfo }); + }, + + [mutationTypes.RECEIVE_GROUP_PROJECTS_FAILURE]: (state) => { + state.error = s__('Boards|An error occurred while fetching group projects. Please try again.'); + Vue.set(state, 'groupProjectsFlags', { isLoading: false, isLoadingMore: false }); + }, + + [mutationTypes.SET_SELECTED_PROJECT]: (state, project) => { + state.selectedProject = project; + }, }; diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js index 573e98e56e0..aba7da373cf 100644 --- a/app/assets/javascripts/boards/stores/state.js +++ b/app/assets/javascripts/boards/stores/state.js @@ -1,7 +1,6 @@ import { inactiveId } from '~/boards/constants'; export default () => ({ - endpoints: {}, boardType: null, disabled: false, isShowingLabels: true, @@ -15,6 +14,13 @@ export default () => ({ issues: {}, filterParams: {}, boardConfig: {}, + groupProjects: [], + groupProjectsFlags: { + isLoading: false, + isLoadingMore: false, + pageInfo: {}, + }, + selectedProject: {}, error: undefined, // TODO: remove after ce/ee split of board_content.vue isShowingEpicsSwimlanes: false, diff --git a/app/services/packages/maven/find_or_create_package_service.rb b/app/services/packages/maven/find_or_create_package_service.rb index bce685cacdf..8ee449cbfdc 100644 --- a/app/services/packages/maven/find_or_create_package_service.rb +++ b/app/services/packages/maven/find_or_create_package_service.rb @@ -11,7 +11,12 @@ module Packages .execute unless Namespace::PackageSetting.duplicates_allowed?(package) - return ServiceResponse.error(message: 'Duplicate package is not allowed') + files = package&.package_files || [] + current_maven_files = files.map { |file| extname(file.file_name) } + + if current_maven_files.compact.include?(extname(params[:file_name])) + return ServiceResponse.error(message: 'Duplicate package is not allowed') + end end unless package @@ -54,6 +59,14 @@ module Packages ServiceResponse.success(payload: { package: package }) end + + private + + def extname(filename) + return if filename.blank? + + File.extname(filename) + end end end end |