diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-22 21:07:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-22 21:07:44 +0300 |
commit | d0f16d56f3716d4a60027eb261f12080094f8db3 (patch) | |
tree | f9ecf9f4bda6d761f612bc4a5efb701b7c6b3d2d /app | |
parent | 68aa32736b50c3609348f3bf740b81a2dfd1fb25 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
21 files changed, 259 insertions, 172 deletions
diff --git a/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue b/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue index b580d58b21b..36cbe9ce693 100644 --- a/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue +++ b/app/assets/javascripts/search/sidebar/components/checkbox_filter.vue @@ -1,6 +1,6 @@ <script> import { GlFormCheckboxGroup, GlFormCheckbox } from '@gitlab/ui'; -import { mapState, mapActions } from 'vuex'; +import { mapState, mapActions, mapGetters } from 'vuex'; import { intersection } from 'lodash'; import { NAV_LINK_COUNT_DEFAULT_CLASSES, LABEL_DEFAULT_CLASSES } from '../constants'; import { formatSearchResultCount } from '../../store/utils'; @@ -12,31 +12,29 @@ export default { GlFormCheckbox, }, props: { - filterData: { + filtersData: { type: Object, required: true, }, }, computed: { ...mapState(['query']), + ...mapGetters(['queryLangugageFilters']), scope() { return this.query.scope; }, - queryFilters() { - return this.query[this.filterData?.filterParam] || []; - }, dataFilters() { - return Object.values(this.filterData?.filters || []); + return Object.values(this.filtersData?.filters || []); }, flatDataFilterValues() { return this.dataFilters.map(({ value }) => value); }, selectedFilter: { get() { - return intersection(this.flatDataFilterValues, this.queryFilters); + return intersection(this.flatDataFilterValues, this.queryLangugageFilters); }, set(value) { - this.setQuery({ key: this.filterData?.filterParam, value }); + this.setQuery({ key: this.filtersData?.filterParam, value }); }, }, labelCountClasses() { @@ -56,7 +54,7 @@ export default { <template> <div class="gl-mx-5"> - <h5 class="gl-mt-0">{{ filterData.header }}</h5> + <h5 class="gl-mt-0">{{ filtersData.header }}</h5> <gl-form-checkbox-group v-model="selectedFilter"> <gl-form-checkbox v-for="f in dataFilters" diff --git a/app/assets/javascripts/search/sidebar/components/language_filter.vue b/app/assets/javascripts/search/sidebar/components/language_filter.vue index 26ce204cb5c..e5306cd7b79 100644 --- a/app/assets/javascripts/search/sidebar/components/language_filter.vue +++ b/app/assets/javascripts/search/sidebar/components/language_filter.vue @@ -27,10 +27,15 @@ export default { apply: __('Apply'), showingMax: sprintf(s__('GlobalSearch|Showing top %{maxItems}'), { maxItems: MAX_ITEM_LENGTH }), loadError: s__('GlobalSearch|Aggregations load error.'), + reset: s__('GlobalSearch|Reset filters'), }, computed: { ...mapState(['aggregations', 'sidebarDirty']), - ...mapGetters(['langugageAggregationBuckets']), + ...mapGetters([ + 'langugageAggregationBuckets', + 'currentUrlQueryHasLanguageFilters', + 'queryLangugageFilters', + ]), hasBuckets() { return this.langugageAggregationBuckets.length > 0; }, @@ -55,18 +60,33 @@ export default { dividerClasses() { return [...HR_DEFAULT_CLASSES, ...ONLY_SHOW_MD]; }, + hasQueryFilters() { + return this.queryLangugageFilters.length > 0; + }, }, async created() { await this.fetchLanguageAggregation(); }, methods: { - ...mapActions(['applyQuery', 'fetchLanguageAggregation']), + ...mapActions([ + 'applyQuery', + 'resetLanguageQuery', + 'resetLanguageQueryWithRedirect', + 'fetchLanguageAggregation', + ]), onShowMore() { this.showAll = true; }, trimBuckets(length) { return this.langugageAggregationBuckets.slice(0, length); }, + cleanResetFilters() { + if (this.currentUrlQueryHasLanguageFilters) { + return this.resetLanguageQueryWithRedirect(); + } + this.showAll = false; + return this.resetLanguageQuery(); + }, }, HR_DEFAULT_CLASSES, }; @@ -84,7 +104,7 @@ export default { class="gl-overflow-x-hidden gl-overflow-y-auto" :class="{ 'language-filter-max-height': showAll }" > - <checkbox-filter class="gl-px-5" :filter-data="filtersData" /> + <checkbox-filter :filters-data="filtersData" /> <span v-if="showAll && hasOverMax" data-testid="has-over-max-text">{{ $options.i18n.showingMax }}</span> @@ -106,7 +126,9 @@ export default { </div> <div v-if="!aggregations.error"> <hr :class="$options.HR_DEFAULT_CLASSES" /> - <div class="gl-display-flex gl-align-items-center gl-mt-4 gl-mx-5 gl-px-5"> + <div + class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-mt-4 gl-mx-5" + > <gl-button category="primary" variant="confirm" @@ -116,6 +138,16 @@ export default { > {{ $options.i18n.apply }} </gl-button> + <gl-button + category="tertiary" + variant="link" + size="small" + :disabled="!hasQueryFilters && !sidebarDirty" + data-testid="reset-button" + @click="cleanResetFilters" + > + {{ $options.i18n.reset }} + </gl-button> </div> </div> </gl-form> diff --git a/app/assets/javascripts/search/sidebar/utils.js b/app/assets/javascripts/search/sidebar/utils.js index 5c08ad2f959..4357d6202df 100644 --- a/app/assets/javascripts/search/sidebar/utils.js +++ b/app/assets/javascripts/search/sidebar/utils.js @@ -1,20 +1,17 @@ import { languageFilterData } from '~/search/sidebar/constants/language_filter_data'; -export const convertFiltersData = (rawBuckets) => { - return rawBuckets.reduce( - (acc, bucket) => { - return { - ...acc, - filters: { - ...acc.filters, - [bucket.key.toUpperCase()]: { - label: bucket.key, - value: bucket.key, - count: bucket.count, - }, +export const convertFiltersData = (rawBuckets) => + rawBuckets.reduce( + (acc, bucket) => ({ + ...acc, + filters: { + ...acc.filters, + [bucket.key.toUpperCase()]: { + label: bucket.key, + value: bucket.key, + count: bucket.count, }, - }; - }, + }, + }), { ...languageFilterData, filters: {} }, ); -}; diff --git a/app/assets/javascripts/search/store/actions.js b/app/assets/javascripts/search/store/actions.js index fc0817be882..667c56cc5ad 100644 --- a/app/assets/javascripts/search/store/actions.js +++ b/app/assets/javascripts/search/store/actions.js @@ -4,6 +4,7 @@ import axios from '~/lib/utils/axios_utils'; import { visitUrl, setUrlParams } from '~/lib/utils/url_utility'; import { logError } from '~/lib/logger'; import { __ } from '~/locale'; +import { languageFilterData } from '~/search/sidebar/constants/language_filter_data'; import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY, SIDEBAR_PARAMS } from './constants'; import * as types from './mutation_types'; import { @@ -105,7 +106,17 @@ export const applyQuery = ({ state }) => { }; export const resetQuery = ({ state }) => { - visitUrl(setUrlParams({ ...state.query, page: null, state: null, confidential: null })); + visitUrl( + setUrlParams({ ...state.query, page: null, state: null, confidential: null }, undefined, true), + ); +}; + +export const resetLanguageQueryWithRedirect = ({ state }) => { + visitUrl(setUrlParams({ ...state.query, language: null }, undefined, true)); +}; + +export const resetLanguageQuery = ({ commit }) => { + commit(types.SET_QUERY, { key: languageFilterData?.filterParam, value: [] }); }; export const fetchSidebarCount = ({ commit, state }) => { diff --git a/app/assets/javascripts/search/store/getters.js b/app/assets/javascripts/search/store/getters.js index 0278239c144..853c83b07ee 100644 --- a/app/assets/javascripts/search/store/getters.js +++ b/app/assets/javascripts/search/store/getters.js @@ -1,4 +1,6 @@ +import { has } from 'lodash'; import { languageFilterData } from '~/search/sidebar/constants/language_filter_data'; + import { GROUPS_LOCAL_STORAGE_KEY, PROJECTS_LOCAL_STORAGE_KEY } from './constants'; export const frequentGroups = (state) => { @@ -16,3 +18,11 @@ export const langugageAggregationBuckets = (state) => { )?.buckets || [] ); }; + +export const queryLangugageFilters = (state) => { + return state.query[languageFilterData.filterParam] || []; +}; + +export const currentUrlQueryHasLanguageFilters = (state) => + has(state.urlQuery, languageFilterData.filterParam) && + state.urlQuery[languageFilterData.filterParam]?.length > 0; diff --git a/app/assets/javascripts/search/store/mutations.js b/app/assets/javascripts/search/store/mutations.js index f9fd69d2211..b2f9f5ab225 100644 --- a/app/assets/javascripts/search/store/mutations.js +++ b/app/assets/javascripts/search/store/mutations.js @@ -24,7 +24,7 @@ export default { state.projects = []; }, [types.SET_QUERY](state, { key, value }) { - state.query[key] = value; + state.query = { ...state.query, [key]: value }; }, [types.SET_SIDEBAR_DIRTY](state, value) { state.sidebarDirty = value; diff --git a/app/assets/javascripts/search/store/utils.js b/app/assets/javascripts/search/store/utils.js index acb99c60426..1e6619ca6d5 100644 --- a/app/assets/javascripts/search/store/utils.js +++ b/app/assets/javascripts/search/store/utils.js @@ -1,3 +1,4 @@ +import { isEqual } from 'lodash'; import AccessorUtilities from '~/lib/utils/accessor'; import { formatNumber } from '~/locale'; import { joinPaths } from '~/lib/utils/url_utility'; @@ -94,6 +95,10 @@ export const isSidebarDirty = (currentQuery, urlQuery) => { const userAddedParam = !urlQuery[param] && currentQuery[param]; const userChangedExistingParam = urlQuery[param] && urlQuery[param] !== currentQuery[param]; + if (Array.isArray(currentQuery[param]) || Array.isArray(urlQuery[param])) { + return !isEqual(currentQuery[param], urlQuery[param]); + } + return userAddedParam || userChangedExistingParam; }); }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue index 4b65d6fd9ac..17fb2daaf17 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue @@ -5,6 +5,7 @@ import { BV_SHOW_MODAL } from '~/lib/utils/constants'; import { HTTP_STATUS_UNAUTHORIZED } from '~/lib/utils/http_status'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { s__, __ } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import eventHub from '../../event_hub'; import approvalsMixin from '../../mixins/approvals'; import MrWidgetContainer from '../mr_widget_container.vue'; @@ -12,7 +13,7 @@ import MrWidgetIcon from '../mr_widget_icon.vue'; import { INVALID_RULES_DOCS_PATH } from '../../constants'; import ApprovalsSummary from './approvals_summary.vue'; import ApprovalsSummaryOptional from './approvals_summary_optional.vue'; -import { FETCH_LOADING, FETCH_ERROR, APPROVE_ERROR, UNAPPROVE_ERROR } from './messages'; +import { FETCH_LOADING, APPROVE_ERROR, UNAPPROVE_ERROR } from './messages'; import { humanizeInvalidApproversRules } from './humanized_text'; export default { @@ -59,10 +60,8 @@ export default { }, data() { return { - fetchingApprovals: true, hasApprovalAuthError: false, isApproving: false, - updatedCount: 0, }; }, computed: { @@ -70,7 +69,7 @@ export default { return this.mr.approvalsWidgetType === 'base'; }, isApproved() { - return Boolean(this.approvals.approved); + return Boolean(this.approvals.approved || this.approvedBy.length); }, isOptional() { return this.isOptionalDefault !== null ? this.isOptionalDefault : !this.approvedBy.length; @@ -78,26 +77,25 @@ export default { hasAction() { return Boolean(this.action); }, - approvals() { - return this.mr.approvals || {}; - }, invalidRules() { - return this.approvals.invalid_approvers_rules || []; + return this.approvals.approvalState?.invalidApproversRules || []; }, hasInvalidRules() { - return this.approvals.merge_request_approvers_available && this.invalidRules.length; + return this.mr.mergeRequestApproversAvailable && this.invalidRules.length; }, invalidRulesText() { return humanizeInvalidApproversRules(this.invalidRules); }, approvedBy() { - return this.approvals.approved_by ? this.approvals.approved_by.map((x) => x.user) : []; + return this.approvals.approvedBy?.nodes || []; }, userHasApproved() { - return Boolean(this.approvals.user_has_approved); + return this.approvedBy.some( + (approver) => getIdFromGraphQLId(approver.id) === gon.current_user_id, + ); }, userCanApprove() { - return Boolean(this.approvals.user_can_approve); + return Boolean(this.approvals.userPermissions.canApprove); }, showApprove() { return !this.userHasApproved && this.userCanApprove && this.mr.isOpen; @@ -135,19 +133,6 @@ export default { : this.$options.i18n.invalidRuleSingular; }, }, - created() { - this.refreshApprovals() - .then(() => { - this.fetchingApprovals = false; - }) - .catch(() => - this.alerts.push( - createAlert({ - message: FETCH_ERROR, - }), - ), - ); - }, methods: { approve() { if (this.requirePasswordToApprove) { @@ -196,16 +181,14 @@ export default { this.isApproving = true; this.clearError(); return serviceFn() - .then((data) => { - this.mr.setApprovals(data); - this.updatedCount += 1; - + .then(() => { if (!window.gon?.features?.realtimeMrStatusChange) { eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('ApprovalUpdated'); } - this.$emit('updated'); + // TODO: Remove this line when we move to Apollo subscriptions + this.$apollo.queries.approvals.refetch(); }) .catch(errFn) .then(() => { @@ -230,7 +213,7 @@ export default { <mr-widget-container> <div class="js-mr-approvals d-flex align-items-start align-items-md-center"> <mr-widget-icon name="approval" /> - <div v-if="fetchingApprovals">{{ $options.FETCH_LOADING }}</div> + <div v-if="$apollo.queries.approvals.loading">{{ $options.FETCH_LOADING }}</div> <template v-else> <div class="gl-display-flex gl-flex-direction-column"> <div class="gl-display-flex gl-flex-direction-row gl-align-items-center"> @@ -252,9 +235,7 @@ export default { /> <approvals-summary v-else - :project-path="mr.targetProjectFullPath" - :iid="`${mr.iid}`" - :updated-count="updatedCount" + :approval-state="approvals" :multiple-approval-rules-available="mr.multipleApprovalRulesAvailable" /> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue index 697d953874c..2af033bb80f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals_summary.vue @@ -1,5 +1,4 @@ <script> -import { GlSkeletonLoader } from '@gitlab/ui'; import { toNounSeriesText } from '~/lib/utils/grammar'; import { n__, sprintf } from '~/locale'; import { @@ -10,49 +9,21 @@ import { import UserAvatarList from '~/vue_shared/components/user_avatar/user_avatar_list.vue'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getApprovalRuleNamesLeft } from 'ee_else_ce/vue_merge_request_widget/mappers'; -import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approved_by.query.graphql'; export default { - apollo: { - approvalState: { - query: approvedByQuery, - variables() { - return { - projectPath: this.projectPath, - iid: this.iid, - }; - }, - update: (data) => data.project.mergeRequest, - }, - }, components: { - GlSkeletonLoader, UserAvatarList, }, props: { - projectPath: { - type: String, - required: true, - }, - iid: { - type: String, - required: true, - }, - updatedCount: { - type: Number, - required: false, - default: 0, - }, multipleApprovalRulesAvailable: { type: Boolean, required: false, default: false, }, - }, - data() { - return { - approvalState: {}, - }; + approvalState: { + type: Object, + required: true, + }, }, computed: { approvers() { @@ -134,37 +105,20 @@ export default { return gon.current_user_id; }, }, - watch: { - updatedCount() { - this.$apollo.queries.approvalState.refetch(); - }, - }, }; </script> <template> <div data-qa-selector="approvals_summary_content"> - <div - v-if="$apollo.queries.approvalState.loading" - class="gl-display-inline-block gl-vertical-align-middle" - style="width: 132px; height: 24px" - > - <gl-skeleton-loader :width="132" :height="24"> - <rect width="100" height="24" x="0" y="0" rx="4" /> - <circle cx="120" cy="12" r="12" /> - </gl-skeleton-loader> - </div> - <template v-else> - <span class="gl-font-weight-bold">{{ approvalLeftMessage }}</span> - <template v-if="hasApprovers"> - <span v-if="approvalLeftMessage">{{ message }}</span> - <span v-else class="gl-font-weight-bold">{{ message }}</span> - <user-avatar-list - class="gl-display-inline-block gl-vertical-align-middle gl-pt-1" - :img-size="24" - :items="approvers" - /> - </template> + <span class="gl-font-weight-bold">{{ approvalLeftMessage }}</span> + <template v-if="hasApprovers"> + <span v-if="approvalLeftMessage">{{ message }}</span> + <span v-else class="gl-font-weight-bold">{{ message }}</span> + <user-avatar-list + class="gl-display-inline-block gl-vertical-align-middle gl-pt-1" + :img-size="24" + :items="approvers" + /> </template> </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approved_by.query.graphql b/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql index c8cae6a8885..437ae578cd0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approved_by.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql @@ -1,3 +1,5 @@ +#import "~/graphql_shared/fragments/user.fragment.graphql" + query approvedBy($projectPath: ID!, $iid: String!) { project(fullPath: $projectPath) { id @@ -5,12 +7,12 @@ query approvedBy($projectPath: ID!, $iid: String!) { id approvedBy { nodes { - id - name - avatarUrl - webUrl + ...User } } + userPermissions { + canApprove + } } } } diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js index 8d596465970..c66641310ee 100644 --- a/app/assets/javascripts/vue_merge_request_widget/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/index.js @@ -13,7 +13,18 @@ Vue.use(Translate); Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), + defaultClient: createDefaultClient( + {}, + { + cacheConfig: { + typePolicies: { + MergeRequestApprovalState: { + merge: true, + }, + }, + }, + }, + ), }); export default () => { diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js index 7d0871f696b..a43c784db28 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js +++ b/app/assets/javascripts/vue_merge_request_widget/mixins/approvals.js @@ -1,7 +1,34 @@ +import { createAlert } from '~/flash'; +import approvedByQuery from 'ee_else_ce/vue_merge_request_widget/components/approvals/queries/approvals.query.graphql'; +import { FETCH_ERROR } from '../components/approvals/messages'; + export default { + apollo: { + approvals: { + query: approvedByQuery, + variables() { + return { + projectPath: this.mr.targetProjectFullPath, + iid: `${this.mr.iid}`, + }; + }, + update: (data) => data.project.mergeRequest, + result({ data }) { + const { mergeRequest } = data.project; + + this.mr.setApprovals(mergeRequest); + }, + error() { + createAlert({ + message: FETCH_ERROR, + }); + }, + }, + }, data() { return { alerts: [], + approvals: {}, }; }, methods: { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index f6a7ef58c10..827080d9866 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -356,12 +356,11 @@ export default class MergeRequestStore { initApprovals() { this.isApproved = this.isApproved || false; - this.approvals = this.approvals || null; } setApprovals(data) { - this.approvals = data; this.isApproved = data.approved || false; + this.approvals = true; this.setState(); } diff --git a/app/graphql/mutations/ci/runner/common_mutation_arguments.rb b/app/graphql/mutations/ci/runner/common_mutation_arguments.rb new file mode 100644 index 00000000000..bfeed4881c6 --- /dev/null +++ b/app/graphql/mutations/ci/runner/common_mutation_arguments.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module Runner + module CommonMutationArguments + extend ActiveSupport::Concern + + included do + argument :description, GraphQL::Types::String, + required: false, + description: 'Description of the runner.' + + argument :maintenance_note, GraphQL::Types::String, + required: false, + description: 'Runner\'s maintenance notes.' + + argument :maximum_timeout, GraphQL::Types::Int, + required: false, + description: 'Maximum timeout (in seconds) for jobs processed by the runner.' + + argument :access_level, ::Types::Ci::RunnerAccessLevelEnum, + required: false, + description: 'Access level of the runner.' + + argument :paused, GraphQL::Types::Boolean, + required: false, + description: 'Indicates the runner is not allowed to receive jobs.' + + argument :locked, GraphQL::Types::Boolean, + required: false, + description: 'Indicates the runner is locked.' + + argument :run_untagged, GraphQL::Types::Boolean, + required: false, + description: 'Indicates the runner is able to run untagged jobs.' + + argument :tag_list, [GraphQL::Types::String], + required: false, + description: 'Tags associated with the runner.' + + argument :associated_projects, [::Types::GlobalIDType[::Project]], + required: false, + description: 'Projects associated with the runner. Available only for project runners.', + prepare: ->(global_ids, _ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } } + end + end + end + end +end diff --git a/app/graphql/mutations/ci/runner/create.rb b/app/graphql/mutations/ci/runner/create.rb new file mode 100644 index 00000000000..1cdee560ca8 --- /dev/null +++ b/app/graphql/mutations/ci/runner/create.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module Mutations + module Ci + module Runner + class Create < BaseMutation + graphql_name 'RunnerCreate' + + authorize :create_runner + + include Mutations::Ci::Runner::CommonMutationArguments + + field :runner, + Types::Ci::RunnerType, + null: true, + description: 'Runner after mutation.' + + def resolve(**args) + if Feature.disabled?(:create_runner_workflow) + raise Gitlab::Graphql::Errors::ResourceNotAvailable, + '`create_runner_workflow` feature flag is disabled.' + end + + create_runner(args) + end + + private + + def create_runner(params) + response = { runner: nil, errors: [] } + result = ::Ci::Runners::CreateRunnerService.new(user: current_user, type: nil, params: params).execute + + if result.success? + response[:runner] = result.payload[:runner] + else + response[:errors] = result.errors + end + + response + end + end + end + end +end diff --git a/app/graphql/mutations/ci/runner/update.rb b/app/graphql/mutations/ci/runner/update.rb index 4f0bf19f09c..70f08e03553 100644 --- a/app/graphql/mutations/ci/runner/update.rb +++ b/app/graphql/mutations/ci/runner/update.rb @@ -8,54 +8,19 @@ module Mutations authorize :update_runner + include Mutations::Ci::Runner::CommonMutationArguments + RunnerID = ::Types::GlobalIDType[::Ci::Runner] argument :id, RunnerID, required: true, description: 'ID of the runner to update.' - argument :description, GraphQL::Types::String, - required: false, - description: 'Description of the runner.' - - argument :maintenance_note, GraphQL::Types::String, - required: false, - description: 'Runner\'s maintenance notes.' - - argument :maximum_timeout, GraphQL::Types::Int, - required: false, - description: 'Maximum timeout (in seconds) for jobs processed by the runner.' - - argument :access_level, ::Types::Ci::RunnerAccessLevelEnum, - required: false, - description: 'Access level of the runner.' - argument :active, GraphQL::Types::Boolean, required: false, description: 'Indicates the runner is allowed to receive jobs.', deprecated: { reason: :renamed, replacement: 'paused', milestone: '14.8' } - argument :paused, GraphQL::Types::Boolean, - required: false, - description: 'Indicates the runner is not allowed to receive jobs.' - - argument :locked, GraphQL::Types::Boolean, - required: false, - description: 'Indicates the runner is locked.' - - argument :run_untagged, GraphQL::Types::Boolean, - required: false, - description: 'Indicates the runner is able to run untagged jobs.' - - argument :tag_list, [GraphQL::Types::String], - required: false, - description: 'Tags associated with the runner.' - - argument :associated_projects, [::Types::GlobalIDType[::Project]], - required: false, - description: 'Projects associated with the runner. Available only for project runners.', - prepare: ->(global_ids, ctx) { global_ids&.filter_map { |gid| gid.model_id.to_i } } - field :runner, Types::Ci::RunnerType, null: true, diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index e48e9deae96..4b15b1f2ad8 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -139,6 +139,7 @@ module Types mount_mutation Mutations::Ci::JobArtifact::Destroy mount_mutation Mutations::Ci::JobTokenScope::AddProject mount_mutation Mutations::Ci::JobTokenScope::RemoveProject + mount_mutation Mutations::Ci::Runner::Create, alpha: { milestone: '15.10' } mount_mutation Mutations::Ci::Runner::Update mount_mutation Mutations::Ci::Runner::Delete mount_mutation Mutations::Ci::Runner::BulkDelete, alpha: { milestone: '15.3' } diff --git a/app/models/concerns/ci/has_status.rb b/app/models/concerns/ci/has_status.rb index 9a04776f1c6..c10a9221efb 100644 --- a/app/models/concerns/ci/has_status.rb +++ b/app/models/concerns/ci/has_status.rb @@ -13,7 +13,7 @@ module Ci STOPPED_STATUSES = COMPLETED_STATUSES + BLOCKED_STATUS ORDERED_STATUSES = %w[failed preparing pending running waiting_for_resource manual scheduled canceled success skipped created].freeze PASSED_WITH_WARNINGS_STATUSES = %w[failed canceled].to_set.freeze - EXCLUDE_IGNORED_STATUSES = %w[manual failed canceled].to_set.freeze + IGNORED_STATUSES = %w[manual].to_set.freeze ALIVE_STATUSES = (ACTIVE_STATUSES + ['created']).freeze CANCELABLE_STATUSES = (ALIVE_STATUSES + ['scheduled']).freeze STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3, diff --git a/app/models/namespaces/traversal/linear.rb b/app/models/namespaces/traversal/linear.rb index 0e9760832af..1aa59c4f1fe 100644 --- a/app/models/namespaces/traversal/linear.rb +++ b/app/models/namespaces/traversal/linear.rb @@ -235,8 +235,6 @@ module Namespaces # This is a temporary guard and will be removed. return if is_a?(Namespaces::ProjectNamespace) - return unless Feature.enabled?(:set_traversal_ids_on_save, root_ancestor) - self.transient_traversal_ids = if parent_id parent.traversal_ids + [id] else diff --git a/app/services/ci/runners/create_runner_service.rb b/app/services/ci/runners/create_runner_service.rb index 2de9ee4d38e..5906cdce99d 100644 --- a/app/services/ci/runners/create_runner_service.rb +++ b/app/services/ci/runners/create_runner_service.rb @@ -33,7 +33,7 @@ module Ci def normalize_params params[:registration_type] = :authenticated_user params[:runner_type] = type - params[:active] = !params.delete(:paused) if params[:paused].present? + params[:active] = !params.delete(:paused) if params.key?(:paused) params[:creator] = user strategy.normalize_params diff --git a/app/services/import/validate_remote_git_endpoint_service.rb b/app/services/import/validate_remote_git_endpoint_service.rb index 1b8fa45e979..2886bd5c9b7 100644 --- a/app/services/import/validate_remote_git_endpoint_service.rb +++ b/app/services/import/validate_remote_git_endpoint_service.rb @@ -21,7 +21,9 @@ module Import def execute uri = Gitlab::Utils.parse_url(@params[:url]) - return ServiceResponse.error(message: "#{@params[:url]} is not a valid URL") unless uri + if !uri || !uri.hostname || Project::VALID_IMPORT_PROTOCOLS.exclude?(uri.scheme) + return ServiceResponse.error(message: "#{@params[:url]} is not a valid URL") + end return ServiceResponse.success if uri.scheme == 'git' |