diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-15 15:09:29 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-15 15:09:29 +0300 |
commit | 6986c1adc235859111e45593bb0bd61e70892d3c (patch) | |
tree | 99912b55cd4c39c7ce24120269dfc870ff8f3705 /app | |
parent | c5b1e86b43f136d8a43cab867ddc49a02751c45a (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
17 files changed, 178 insertions, 213 deletions
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 4e93bd8dcf1..4362263b0dd 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -2,21 +2,24 @@ import { GlButton, GlLoadingIcon, GlModal, GlLink } from '@gitlab/ui'; import { getParameterByName } from '~/lib/utils/common_utils'; import eventHub from '~/pipelines/event_hub'; -import pipelinesMixin from '~/pipelines/mixins/pipelines'; -import PipelinesPaginationApiMixin from '~/pipelines/mixins/pipelines_pagination_api_mixin'; +import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin'; import PipelinesService from '~/pipelines/services/pipelines_service'; import PipelineStore from '~/pipelines/stores/pipelines_store'; +import PipelinesTableComponent from '~/pipelines/components/pipelines_list/pipelines_table.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; +import SvgBlankState from '~/pipelines/components/pipelines_list/blank_state.vue'; export default { components: { - TablePagination, GlButton, + GlLink, GlLoadingIcon, GlModal, - GlLink, + PipelinesTableComponent, + TablePagination, + SvgBlankState, }, - mixins: [pipelinesMixin, PipelinesPaginationApiMixin], + mixins: [PipelinesMixin], props: { endpoint: { type: String, diff --git a/app/assets/javascripts/issuable_list/components/issuable_item.vue b/app/assets/javascripts/issuable_list/components/issuable_item.vue index b114f6e7278..39852eba71a 100644 --- a/app/assets/javascripts/issuable_list/components/issuable_item.vue +++ b/app/assets/javascripts/issuable_list/components/issuable_item.vue @@ -4,6 +4,7 @@ import { GlLink, GlIcon, GlLabel, GlFormCheckbox, GlTooltipDirective } from '@gi import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { isScopedLabel } from '~/lib/utils/common_utils'; import { getTimeago } from '~/lib/utils/datetime_utility'; +import { isExternal, setUrlFragment } from '~/lib/utils/url_utility'; import { __, sprintf } from '~/locale'; import IssuableAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; @@ -47,17 +48,14 @@ export default { author() { return this.issuable.author; }, + webUrl() { + return this.issuable.gitlabWebUrl || this.issuable.webUrl; + }, authorId() { return getIdFromGraphQLId(`${this.author.id}`); }, isIssuableUrlExternal() { - // Check if URL is relative, which means it is internal. - if (!/^https?:\/\//g.test(this.issuable.webUrl)) { - return false; - } - // In case URL is absolute, it may or may not be internal, - // hence use `gon.gitlab_url` which is current instance domain. - return !this.issuable.webUrl.includes(gon.gitlab_url); + return isExternal(this.webUrl); }, labels() { return this.issuable.labels?.nodes || this.issuable.labels || []; @@ -91,6 +89,9 @@ export default { this.hasSlotContents('status') || this.showDiscussions || this.issuable.assignees, ); }, + issuableNotesLink() { + return setUrlFragment(this.webUrl, 'notes'); + }, }, methods: { hasSlotContents(slotName) { @@ -144,7 +145,7 @@ export default { name="eye-slash" :title="__('Confidential')" /> - <gl-link :href="issuable.webUrl" v-bind="issuableTitleProps" + <gl-link :href="webUrl" v-bind="issuableTitleProps" >{{ issuable.title }}<gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2" /></gl-link> @@ -206,7 +207,7 @@ export default { <gl-link v-gl-tooltip:tooltipcontainer.top :title="__('Comments')" - :href="`${issuable.webUrl}#notes`" + :href="issuableNotesLink" :class="{ 'no-comments': !issuable.userDiscussionsCount }" class="gl-reset-color!" > diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue index 3a9d181066c..a45adc766e3 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue @@ -1,5 +1,5 @@ <script> -import { GlIcon } from '@gitlab/ui'; +import { GlIcon, GlLoadingIcon } from '@gitlab/ui'; import { isEqual } from 'lodash'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { getParameterByName } from '~/lib/utils/common_utils'; @@ -7,22 +7,28 @@ import { __, s__ } from '~/locale'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; import { ANY_TRIGGER_AUTHOR, RAW_TEXT_WARNING, FILTER_TAG_IDENTIFIER } from '../../constants'; -import pipelinesMixin from '../../mixins/pipelines'; -import PipelinesPaginationApiMixin from '../../mixins/pipelines_pagination_api_mixin'; +import PipelinesMixin from '../../mixins/pipelines_mixin'; import PipelinesService from '../../services/pipelines_service'; import { validateParams } from '../../utils'; +import EmptyState from './empty_state.vue'; import NavigationControls from './nav_controls.vue'; import PipelinesFilteredSearch from './pipelines_filtered_search.vue'; +import PipelinesTableComponent from './pipelines_table.vue'; +import SvgBlankState from './blank_state.vue'; export default { components: { - TablePagination, + EmptyState, + GlIcon, + GlLoadingIcon, NavigationTabs, NavigationControls, PipelinesFilteredSearch, - GlIcon, + PipelinesTableComponent, + SvgBlankState, + TablePagination, }, - mixins: [pipelinesMixin, PipelinesPaginationApiMixin], + mixins: [PipelinesMixin], props: { store: { type: Object, @@ -217,6 +223,20 @@ export default { this.requestData = { page: this.page, scope: this.scope, ...this.validatedParams }; }, methods: { + onChangeTab(scope) { + if (this.scope === scope) { + return; + } + + let params = { + scope, + page: '1', + }; + + params = this.onChangeWithFilter(params); + + this.updateContent(params); + }, successCallback(resp) { // Because we are polling & the user is interacting verify if the response received // matches the last request made diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines_mixin.js index bff986261ee..8444d43513b 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines_mixin.js @@ -1,21 +1,13 @@ -import { GlLoadingIcon } from '@gitlab/ui'; import Visibility from 'visibilityjs'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import Poll from '~/lib/utils/poll'; +import { historyPushState, buildUrlWithCurrentLocation } from '~/lib/utils/common_utils'; +import { validateParams } from '~/pipelines/utils'; import { __ } from '~/locale'; -import SvgBlankState from '../components/pipelines_list/blank_state.vue'; -import EmptyState from '../components/pipelines_list/empty_state.vue'; -import PipelinesTableComponent from '../components/pipelines_list/pipelines_table.vue'; import { CANCEL_REQUEST } from '../constants'; import eventHub from '../event_hub'; export default { - components: { - PipelinesTableComponent, - SvgBlankState, - EmptyState, - GlLoadingIcon, - }, data() { return { isLoading: false, @@ -76,6 +68,25 @@ export default { this.poll.stop(); }, methods: { + updateInternalState(parameters) { + this.poll.stop(); + + const queryString = Object.keys(parameters) + .map((parameter) => { + const value = parameters[parameter]; + // update internal state for UI + this[parameter] = value; + return `${parameter}=${encodeURIComponent(value)}`; + }) + .join('&'); + + // update polling parameters + this.requestData = parameters; + + historyPushState(buildUrlWithCurrentLocation(`?${queryString}`)); + + this.isLoading = true; + }, /** * Handles URL and query parameter changes. * When the user uses the pagination or the tabs, @@ -184,5 +195,23 @@ export default { }) .finally(() => this.store.toggleIsRunningPipeline(false)); }, + onChangePage(page) { + /* URLS parameters are strings, we need to parse to match types */ + let params = { + page: Number(page).toString(), + }; + + if (this.scope) { + params.scope = this.scope; + } + + params = this.onChangeWithFilter(params); + + this.updateContent(params); + }, + + onChangeWithFilter(params) { + return { ...params, ...validateParams(this.requestData) }; + }, }, }; diff --git a/app/assets/javascripts/pipelines/mixins/pipelines_pagination_api_mixin.js b/app/assets/javascripts/pipelines/mixins/pipelines_pagination_api_mixin.js deleted file mode 100644 index b62fe196a6f..00000000000 --- a/app/assets/javascripts/pipelines/mixins/pipelines_pagination_api_mixin.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * API callbacks for pagination and tabs - * - * Components need to have `scope`, `page` and `requestData` - */ -import { validateParams } from '~/pipelines/utils'; -import { historyPushState, buildUrlWithCurrentLocation } from '../../lib/utils/common_utils'; - -export default { - methods: { - onChangeTab(scope) { - if (this.scope === scope) { - return; - } - - let params = { - scope, - page: '1', - }; - - params = this.onChangeWithFilter(params); - - this.updateContent(params); - }, - - onChangePage(page) { - /* URLS parameters are strings, we need to parse to match types */ - let params = { - page: Number(page).toString(), - }; - - if (this.scope) { - params.scope = this.scope; - } - - params = this.onChangeWithFilter(params); - - this.updateContent(params); - }, - - onChangeWithFilter(params) { - return { ...params, ...validateParams(this.requestData) }; - }, - - updateInternalState(parameters) { - // stop polling - this.poll.stop(); - - const queryString = Object.keys(parameters) - .map((parameter) => { - const value = parameters[parameter]; - // update internal state for UI - this[parameter] = value; - return `${parameter}=${encodeURIComponent(value)}`; - }) - .join('&'); - - // update polling parameters - this.requestData = parameters; - - historyPushState(buildUrlWithCurrentLocation(`?${queryString}`)); - - this.isLoading = true; - }, - }, -}; diff --git a/app/assets/javascripts/terraform/components/states_table.vue b/app/assets/javascripts/terraform/components/states_table.vue index e4b5a66c3fc..2577664a5e8 100644 --- a/app/assets/javascripts/terraform/components/states_table.vue +++ b/app/assets/javascripts/terraform/components/states_table.vue @@ -80,6 +80,7 @@ export default { lockingState: s__('Terraform|Locking state'), name: s__('Terraform|Name'), pipeline: s__('Terraform|Pipeline'), + removing: s__('Terraform|Removing'), unknownUser: s__('Terraform|Unknown User'), unlockingState: s__('Terraform|Unlocking state'), updatedUser: s__('Terraform|%{user} updated %{timeAgo}'), @@ -141,6 +142,15 @@ export default { </p> </div> + <div v-else-if="item.loadingRemove" class="gl-mx-3"> + <p + class="gl-display-flex gl-justify-content-start gl-align-items-baseline gl-m-0 gl-text-red-500" + > + <gl-loading-icon class="gl-pr-1" /> + {{ $options.i18n.removing }} + </p> + </div> + <div v-else-if="item.lockedAt" :id="`terraformLockedBadgeContainer${item.name}`" diff --git a/app/assets/javascripts/terraform/components/states_table_actions.vue b/app/assets/javascripts/terraform/components/states_table_actions.vue index 65dcc430f9d..c4fd97188de 100644 --- a/app/assets/javascripts/terraform/components/states_table_actions.vue +++ b/app/assets/javascripts/terraform/components/states_table_actions.vue @@ -9,7 +9,7 @@ import { GlModal, GlSprintf, } from '@gitlab/ui'; -import { s__ } from '~/locale'; +import { s__, sprintf } from '~/locale'; import addDataToState from '../graphql/mutations/add_data_to_state.mutation.graphql'; import lockState from '../graphql/mutations/lock_state.mutation.graphql'; import removeState from '../graphql/mutations/remove_state.mutation.graphql'; @@ -52,6 +52,7 @@ export default { ), modalRemove: s__('Terraform|Remove'), remove: s__('Terraform|Remove state file and versions'), + removeSuccessful: s__('Terraform|%{name} successfully removed'), unlock: s__('Terraform|Unlock'), }, computed: { @@ -121,10 +122,13 @@ export default { loadingRemove: true, }); - this.stateActionMutation(removeState); + this.stateActionMutation( + removeState, + sprintf(this.$options.i18n.removeSuccessful, { name: this.state.name }), + ); } }, - stateActionMutation(mutation) { + stateActionMutation(mutation, successMessage = null) { let errorMessages = []; this.$apollo @@ -143,6 +147,10 @@ export default { data?.terraformStateLock?.errors || data?.terraformStateUnlock?.errors || []; + + if (errorMessages.length === 0 && successMessage) { + this.$toast.show(successMessage); + } }) .catch(() => { errorMessages = [this.$options.i18n.errorUpdate]; diff --git a/app/assets/javascripts/terraform/index.js b/app/assets/javascripts/terraform/index.js index f0a924d8a58..2288bab2fa8 100644 --- a/app/assets/javascripts/terraform/index.js +++ b/app/assets/javascripts/terraform/index.js @@ -1,10 +1,12 @@ import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; +import { GlToast } from '@gitlab/ui'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; import TerraformList from './components/terraform_list.vue'; import resolvers from './graphql/resolvers'; +Vue.use(GlToast); Vue.use(VueApollo); export default () => { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index 99aa8395f3d..d15794c71b1 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -159,7 +159,7 @@ export default { <div class="rebase-state-find-class-convention media media-body space-children"> <span v-if="rebaseInProgress || isMakingRequest" - class="gl-font-weight-bold gl-ml-0!" + class="gl-font-weight-bold" data-testid="rebase-message" >{{ __('Rebase in progress') }}</span > diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue deleted file mode 100644 index 861661d3519..00000000000 --- a/app/assets/javascripts/vue_shared/components/toggle_button.vue +++ /dev/null @@ -1,88 +0,0 @@ -<script> -import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; -import { s__ } from '../../locale'; - -const ICON_ON = 'status_success_borderless'; -const ICON_OFF = 'status_failed_borderless'; -const LABEL_ON = s__('ToggleButton|Toggle Status: ON'); -const LABEL_OFF = s__('ToggleButton|Toggle Status: OFF'); - -export default { - components: { - GlIcon, - GlLoadingIcon, - }, - - model: { - prop: 'value', - event: 'change', - }, - - props: { - name: { - type: String, - required: false, - default: null, - }, - value: { - type: Boolean, - required: false, - default: null, - }, - disabledInput: { - type: Boolean, - required: false, - default: false, - }, - isLoading: { - type: Boolean, - required: false, - default: false, - }, - }, - - computed: { - toggleIcon() { - return this.value ? ICON_ON : ICON_OFF; - }, - ariaLabel() { - return this.value ? LABEL_ON : LABEL_OFF; - }, - }, - - methods: { - toggleFeature() { - if (!this.disabledInput) this.$emit('change', !this.value); - }, - }, -}; -</script> - -<template> - <label class="gl-mt-2"> - <input v-if="name" :name="name" :value="value" type="hidden" /> - <button - type="button" - role="switch" - class="project-feature-toggle" - :aria-label="ariaLabel" - :aria-checked="value" - :class="{ - 'is-checked': value, - 'gl-blue-500': value, - 'is-disabled': disabledInput, - 'is-loading': isLoading, - }" - @click.prevent="toggleFeature" - > - <gl-loading-icon class="loading-icon" /> - <span class="toggle-icon"> - <gl-icon - :size="18" - :name="toggleIcon" - :class="value ? 'gl-text-blue-500' : 'gl-text-gray-400'" - /> - </span> - </button> - </label> -</template> diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 17450da6e68..bedd6891d02 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -177,7 +177,7 @@ class LabelsFinder < UnionFinder end if group? - @projects = if params[:include_subgroups] + @projects = if params[:include_descendant_groups] @projects.in_namespace(group.self_and_descendants.select(:id)) else @projects.in_namespace(group.id) diff --git a/app/graphql/resolvers/group_labels_resolver.rb b/app/graphql/resolvers/group_labels_resolver.rb new file mode 100644 index 00000000000..5c2f950bbc0 --- /dev/null +++ b/app/graphql/resolvers/group_labels_resolver.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Resolvers + class GroupLabelsResolver < LabelsResolver + type Types::LabelType.connection_type, null: true + + argument :include_descendant_groups, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Include labels from descendant groups.', + default_value: false + + argument :only_group_labels, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Include only group level labels.', + default_value: false + end +end diff --git a/app/graphql/resolvers/labels_resolver.rb b/app/graphql/resolvers/labels_resolver.rb new file mode 100644 index 00000000000..1b523b8a240 --- /dev/null +++ b/app/graphql/resolvers/labels_resolver.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module Resolvers + class LabelsResolver < BaseResolver + include Gitlab::Graphql::Authorize::AuthorizeResource + + authorize :read_label + + type Types::LabelType.connection_type, null: true + + argument :search_term, GraphQL::STRING_TYPE, + required: false, + description: 'A search term to find labels with.' + + argument :include_ancestor_groups, GraphQL::BOOLEAN_TYPE, + required: false, + description: 'Include labels from ancestor groups.', + default_value: false + + def resolve(**args) + return Label.none if parent.nil? + + authorize!(parent) + + # LabelsFinder uses `search` param, so we transform `search_term` into `search` + args[:search] = args.delete(:search_term) + LabelsFinder.new(current_user, parent_param.merge(args)).execute + end + + private + + def parent + object.respond_to?(:sync) ? object.sync : object + end + + def parent_param + key = case parent + when Group then :group + when Project then :project + else raise "Unexpected parent type: #{parent.class}" + end + + { "#{key}": parent } + end + end +end diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 42391ec1d98..0aaeb8d20df 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -107,17 +107,8 @@ module Types field :labels, Types::LabelType.connection_type, null: true, - description: 'Labels available on this group.' do - argument :search_term, GraphQL::STRING_TYPE, - required: false, - description: 'A search term to find labels with.' - end - - def labels(search_term: nil) - LabelsFinder - .new(current_user, group: group, search: search_term) - .execute - end + description: 'Labels available on this group.', + resolver: Resolvers::GroupLabelsResolver def avatar_url object.avatar_url(only_path: false) diff --git a/app/graphql/types/packages/package_type_enum.rb b/app/graphql/types/packages/package_type_enum.rb index 9713c9d49b1..e2b5cf3163e 100644 --- a/app/graphql/types/packages/package_type_enum.rb +++ b/app/graphql/types/packages/package_type_enum.rb @@ -5,7 +5,7 @@ module Types class PackageTypeEnum < BaseEnum PACKAGE_TYPE_NAMES = { pypi: 'PyPI', - npm: 'NPM' + npm: 'npm' }.freeze ::Packages::Package.package_types.keys.each do |package_type| diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 9ef451cbe3c..7205c615271 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -337,17 +337,8 @@ module Types field :labels, Types::LabelType.connection_type, null: true, - description: 'Labels available on this project.' do - argument :search_term, GraphQL::STRING_TYPE, - required: false, - description: 'A search term to find labels with.' - end - - def labels(search_term: nil) - LabelsFinder - .new(current_user, project: project, search: search_term) - .execute - end + description: 'Labels available on this project.', + resolver: Resolvers::LabelsResolver def avatar_url object.avatar_url(only_path: false) diff --git a/app/models/concerns/enums/ci/pipeline.rb b/app/models/concerns/enums/ci/pipeline.rb index e1cf579eefc..f8314d8b429 100644 --- a/app/models/concerns/enums/ci/pipeline.rb +++ b/app/models/concerns/enums/ci/pipeline.rb @@ -74,7 +74,8 @@ module Enums remote_source: 4, external_project_source: 5, bridge_source: 6, - parameter_source: 7 + parameter_source: 7, + compliance_source: 8 } end end |