diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-16 15:10:18 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-16 15:10:18 +0000 |
commit | 6364c14cc1f445d471bca118dca5af5a85b2c5dc (patch) | |
tree | 2579c5592f207e86ff7a0c5c7499caad723cdec1 /app | |
parent | 5a2284f3500088e04cf3a5854fb06dc9db2b6077 (diff) | |
download | gitlab-ce-6364c14cc1f445d471bca118dca5af5a85b2c5dc.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
28 files changed, 258 insertions, 103 deletions
diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js new file mode 100644 index 00000000000..70bab8092c0 --- /dev/null +++ b/app/assets/javascripts/pipeline_editor/constants.js @@ -0,0 +1,2 @@ +export const CI_CONFIG_STATUS_VALID = 'VALID'; +export const CI_CONFIG_STATUS_INVALID = 'INVALID'; diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index 8a57c9b1970..96dc782964b 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -2,6 +2,7 @@ import { GlAlert, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui'; import { __, s__, sprintf } from '~/locale'; import { mergeUrlParams, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; import CommitForm from './components/commit/commit_form.vue'; @@ -31,6 +32,7 @@ export default { PipelineGraph, TextEditor, }, + mixins: [glFeatureFlagsMixin()], props: { projectPath: { type: String, @@ -115,6 +117,9 @@ export default { isBlobContentLoading() { return this.$apollo.queries.content.loading; }, + isVisualizationTabLoading() { + return this.$apollo.queries.ciConfigData.loading; + }, isVisualizeTabActive() { return this.currentTabIndex === 1; }, @@ -266,8 +271,14 @@ export default { <text-editor v-model="contentModel" @editor-ready="editorIsReady = true" /> </gl-tab> - <gl-tab :title="$options.i18n.tabGraph" :lazy="!isVisualizeTabActive"> - <pipeline-graph :pipeline-data="ciConfigData" /> + <gl-tab + v-if="glFeatures.ciConfigVisualizationTab" + :title="$options.i18n.tabGraph" + :lazy="!isVisualizeTabActive" + data-testid="visualization-tab" + > + <gl-loading-icon v-if="isVisualizationTabLoading" size="lg" class="gl-m-3" /> + <pipeline-graph v-else :pipeline-data="ciConfigData" /> </gl-tab> </gl-tabs> </div> diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js b/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js index 45940d4a39c..35230e1511b 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js @@ -1,5 +1,5 @@ import * as d3 from 'd3'; -import { createUniqueJobId } from '../../utils'; +import { createUniqueLinkId } from '../../utils'; /** * This function expects its first argument data structure * to be the same shaped as the one generated by `parseData`, @@ -12,13 +12,13 @@ import { createUniqueJobId } from '../../utils'; * @returns {Array} Links that contain all the information about them */ -export const generateLinksData = ({ links }, jobs, containerID) => { +export const generateLinksData = ({ links }, containerID) => { const containerEl = document.getElementById(containerID); return links.map(link => { const path = d3.path(); - const sourceId = jobs[link.source].id; - const targetId = jobs[link.target].id; + const sourceId = link.source; + const targetId = link.target; const sourceNodeEl = document.getElementById(sourceId); const targetNodeEl = document.getElementById(targetId); @@ -89,7 +89,7 @@ export const generateLinksData = ({ links }, jobs, containerID) => { ...link, source: sourceId, target: targetId, - ref: createUniqueJobId(sourceId, targetId), + ref: createUniqueLinkId(sourceId, targetId), path: path.toString(), }; }); diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue index a0c35f54c0e..51a95612d3f 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue @@ -10,10 +10,6 @@ export default { type: String, required: true, }, - jobId: { - type: String, - required: true, - }, isHighlighted: { type: Boolean, required: false, @@ -45,7 +41,7 @@ export default { }, methods: { onMouseEnter() { - this.$emit('on-mouse-enter', this.jobId); + this.$emit('on-mouse-enter', this.jobName); }, onMouseLeave() { this.$emit('on-mouse-leave'); @@ -56,7 +52,7 @@ export default { <template> <tooltip-on-truncate :title="jobName" truncate-target="child" placement="top"> <div - :id="jobId" + :id="jobName" class="gl-w-15 gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" :class="jobPillClasses" @mouseover="onMouseEnter" diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue index 11ad2f2a3b6..73e5f2542fb 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue @@ -6,8 +6,10 @@ import JobPill from './job_pill.vue'; import StagePill from './stage_pill.vue'; import { generateLinksData } from './drawing_utils'; import { parseData } from '../parsing_utils'; -import { DRAW_FAILURE, DEFAULT } from '../../constants'; -import { generateJobNeedsDict } from '../../utils'; +import { unwrapArrayOfJobs } from '../unwrapping_utils'; +import { DRAW_FAILURE, DEFAULT, INVALID_CI_CONFIG, EMPTY_PIPELINE_DATA } from '../../constants'; +import { createJobsHash, generateJobNeedsDict } from '../../utils'; +import { CI_CONFIG_STATUS_INVALID } from '~/pipeline_editor/constants'; export default { components: { @@ -22,6 +24,12 @@ export default { [DRAW_FAILURE]: __('Could not draw the lines for job relationships'), [DEFAULT]: __('An unknown error occurred.'), }, + warningTexts: { + [EMPTY_PIPELINE_DATA]: __( + 'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.', + ), + [INVALID_CI_CONFIG]: __('Your CI configuration file is invalid.'), + }, props: { pipelineData: { required: true, @@ -40,18 +48,51 @@ export default { }, computed: { isPipelineDataEmpty() { - return isEmpty(this.pipelineData); + return !this.isInvalidCiConfig && isEmpty(this.pipelineData?.stages); + }, + isInvalidCiConfig() { + return this.pipelineData?.status === CI_CONFIG_STATUS_INVALID; + }, + showAlert() { + return this.hasError || this.hasWarning; }, hasError() { return this.failureType; }, + hasWarning() { + return this.warning; + }, hasHighlightedJob() { return Boolean(this.highlightedJob); }, + alert() { + if (this.hasError) { + return this.failure; + } + + return this.warning; + }, failure() { const text = this.$options.errorTexts[this.failureType] || this.$options.errorTexts[DEFAULT]; - return { text, variant: 'danger' }; + return { text, variant: 'danger', dismissible: true }; + }, + warning() { + if (this.isPipelineDataEmpty) { + return { + text: this.$options.warningTexts[EMPTY_PIPELINE_DATA], + variant: 'tip', + dismissible: false, + }; + } else if (this.isInvalidCiConfig) { + return { + text: this.$options.warningTexts[INVALID_CI_CONFIG], + variant: 'danger', + dismissible: false, + }; + } + + return null; }, viewBox() { return [0, 0, this.width, this.height]; @@ -80,19 +121,21 @@ export default { }, }, mounted() { - if (!this.isPipelineDataEmpty) { - this.getGraphDimensions(); - this.drawJobLinks(); + if (!this.isPipelineDataEmpty && !this.isInvalidCiConfig) { + // This guarantee that all sub-elements are rendered + // https://v3.vuejs.org/api/options-lifecycle-hooks.html#mounted + this.$nextTick(() => { + this.getGraphDimensions(); + this.prepareLinkData(); + }); } }, methods: { - drawJobLinks() { - const { stages, jobs } = this.pipelineData; - const unwrappedGroups = this.unwrapPipelineData(stages); - + prepareLinkData() { try { - const parsedData = parseData(unwrappedGroups); - this.links = generateLinksData(parsedData, jobs, this.$options.CONTAINER_ID); + const arrayOfJobs = unwrapArrayOfJobs(this.pipelineData); + const parsedData = parseData(arrayOfJobs); + this.links = generateLinksData(parsedData, this.$options.CONTAINER_ID); } catch { this.reportFailure(DRAW_FAILURE); } @@ -119,7 +162,8 @@ export default { // The first time we hover, we create the object where // we store all the data to properly highlight the needs. if (!this.needsObject) { - this.needsObject = generateJobNeedsDict(this.pipelineData) ?? {}; + const jobs = createJobsHash(this.pipelineData); + this.needsObject = generateJobNeedsDict(jobs) ?? {}; } this.highlightedJob = uniqueJobId; @@ -127,18 +171,9 @@ export default { removeHighlightNeeds() { this.highlightedJob = null; }, - unwrapPipelineData(stages) { - return stages - .map(({ name, groups }) => { - return groups.map(group => { - return { category: name, ...group }; - }); - }) - .flat(2); - }, getGraphDimensions() { - this.width = `${this.$refs[this.$options.CONTAINER_REF].scrollWidth}px`; - this.height = `${this.$refs[this.$options.CONTAINER_REF].scrollHeight}px`; + this.width = `${this.$refs[this.$options.CONTAINER_REF].scrollWidth}`; + this.height = `${this.$refs[this.$options.CONTAINER_REF].scrollHeight}`; }, reportFailure(errorType) { this.failureType = errorType; @@ -163,21 +198,20 @@ export default { </script> <template> <div> - <gl-alert v-if="hasError" :variant="failure.variant" @dismiss="resetFailure"> - {{ failure.text }} - </gl-alert> - <gl-alert v-if="isPipelineDataEmpty" variant="tip" :dismissible="false"> - {{ - __( - 'The visualization will appear in this tab when the CI/CD configuration file is populated with valid syntax.', - ) - }} + <gl-alert + v-if="showAlert" + :variant="alert.variant" + :dismissible="alert.dismissible" + @dismiss="alert.dismissible ? resetFailure : null" + > + {{ alert.text }} </gl-alert> <div - v-else + v-if="!hasWarning" :id="$options.CONTAINER_ID" :ref="$options.CONTAINER_REF" class="gl-display-flex gl-bg-gray-50 gl-px-4 gl-overflow-auto gl-relative gl-py-7" + data-testid="graph-container" > <svg :viewBox="viewBox" :width="width" :height="height" class="gl-absolute"> <template> @@ -210,10 +244,9 @@ export default { <job-pill v-for="group in stage.groups" :key="group.name" - :job-id="group.id" :job-name="group.name" - :is-highlighted="hasHighlightedJob && isJobHighlighted(group.id)" - :is-faded-out="hasHighlightedJob && !isJobHighlighted(group.id)" + :is-highlighted="hasHighlightedJob && isJobHighlighted(group.name)" + :is-faded-out="hasHighlightedJob && !isJobHighlighted(group.name)" @on-mouse-enter="highlightNeeds" @on-mouse-leave="removeHighlightNeeds" /> diff --git a/app/assets/javascripts/pipelines/components/unwrapping_utils.js b/app/assets/javascripts/pipelines/components/unwrapping_utils.js index 99934cd5014..aa33f622ce6 100644 --- a/app/assets/javascripts/pipelines/components/unwrapping_utils.js +++ b/app/assets/javascripts/pipelines/components/unwrapping_utils.js @@ -1,3 +1,20 @@ +/** + * This function takes the stages and add the stage name + * at the group level as `category` to have an easier + * implementation while constructions nodes with D3 + * @param {Array} stages + * @returns {Array} - Array of stages with stage name at the group level as `category` + */ +export const unwrapArrayOfJobs = (stages = []) => { + return stages + .map(({ name, groups }) => { + return groups.map(group => { + return { category: name, ...group }; + }); + }) + .flat(2); +}; + const unwrapGroups = stages => { return stages.map(stage => { const { diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js index 607e7a66f44..757d285ef19 100644 --- a/app/assets/javascripts/pipelines/constants.js +++ b/app/assets/javascripts/pipelines/constants.js @@ -28,6 +28,8 @@ export const RAW_TEXT_WARNING = s__( export const DEFAULT = 'default'; export const DELETE_FAILURE = 'delete_pipeline_failure'; export const DRAW_FAILURE = 'draw_failure'; +export const EMPTY_PIPELINE_DATA = 'empty_data'; +export const INVALID_CI_CONFIG = 'invalid_ci_config'; export const LOAD_FAILURE = 'load_failure'; export const PARSE_FAILURE = 'parse_failure'; export const POST_FAILURE = 'post_failure'; diff --git a/app/assets/javascripts/pipelines/utils.js b/app/assets/javascripts/pipelines/utils.js index 46e54bfb4ff..28d6c0edb0f 100644 --- a/app/assets/javascripts/pipelines/utils.js +++ b/app/assets/javascripts/pipelines/utils.js @@ -5,9 +5,42 @@ export const validateParams = params => { return pickBy(params, (val, key) => SUPPORTED_FILTER_PARAMETERS.includes(key) && val); }; -export const createUniqueJobId = (stageName, jobName) => `${stageName}-${jobName}`; +export const createUniqueLinkId = (stageName, jobName) => `${stageName}-${jobName}`; -export const generateJobNeedsDict = ({ jobs }) => { +/** + * This function takes the stages array and transform it + * into a hash where each key is a job name and the job data + * is associated to that key. + * @param {Array} stages + * @returns {Object} - Hash of jobs + */ +export const createJobsHash = (stages = []) => { + const jobsHash = {}; + + stages.forEach(stage => { + if (stage.groups.length > 0) { + stage.groups.forEach(group => { + group.jobs.forEach(job => { + jobsHash[job.name] = job; + }); + }); + } + }); + + return jobsHash; +}; + +/** + * This function takes the jobs hash generated by + * `createJobsHash` function and returns an easier + * structure to work with for needs relationship + * where the key is the job name and the value is an + * array of all the needs this job has recursively + * (includes the needs of the needs) + * @param {Object} jobs + * @returns {Object} - Hash of jobs and array of needs + */ +export const generateJobNeedsDict = (jobs = {}) => { const arrOfJobNames = Object.keys(jobs); return arrOfJobNames.reduce((acc, value) => { @@ -18,13 +51,12 @@ export const generateJobNeedsDict = ({ jobs }) => { return jobs[jobName].needs .map(job => { - const { id } = jobs[job]; // If we already have the needs of a job in the accumulator, // then we use the memoized data instead of the recursive call // to save some performance. - const newNeeds = acc[id] ?? recursiveNeeds(job); + const newNeeds = acc[job] ?? recursiveNeeds(job); - return [id, ...newNeeds]; + return [job, ...newNeeds]; }) .flat(Infinity); }; @@ -34,6 +66,6 @@ export const generateJobNeedsDict = ({ jobs }) => { // duplicates from the array. const uniqueValues = Array.from(new Set(recursiveNeeds(value))); - return { ...acc, [jobs[value].id]: uniqueValues }; + return { ...acc, [value]: uniqueValues }; }, {}); }; diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb index c2428270fa6..cc391868df0 100644 --- a/app/controllers/projects/ci/pipeline_editor_controller.rb +++ b/app/controllers/projects/ci/pipeline_editor_controller.rb @@ -2,6 +2,9 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController before_action :check_can_collaborate! + before_action do + push_frontend_feature_flag(:ci_config_visualization_tab, @project, default_enabled: false) + end feature_category :pipeline_authoring diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 453928e251f..2aa7e9038e9 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -12,7 +12,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap before_action :build_merge_request, except: [:create] before_action do - push_frontend_feature_flag(:merge_request_reviewers, @project) + push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true) push_frontend_feature_flag(:mr_collapsed_approval_rules, @project) end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ee25a2006ad..b48aa02a81b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -51,7 +51,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action do push_frontend_feature_flag(:vue_issuable_sidebar, @project.group) - push_frontend_feature_flag(:merge_request_reviewers, @project) + push_frontend_feature_flag(:merge_request_reviewers, @project, default_enabled: true) push_frontend_feature_flag(:mr_collapsed_approval_rules, @project) end diff --git a/app/graphql/mutations/boards/common_mutation_arguments.rb b/app/graphql/mutations/boards/common_mutation_arguments.rb new file mode 100644 index 00000000000..c4f8d299318 --- /dev/null +++ b/app/graphql/mutations/boards/common_mutation_arguments.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Mutations + module Boards + module CommonMutationArguments + extend ActiveSupport::Concern + + included do + argument :name, + GraphQL::STRING_TYPE, + required: false, + description: 'The board name.' + argument :hide_backlog_list, + GraphQL::BOOLEAN_TYPE, + required: false, + description: copy_field_description(Types::BoardType, :hide_backlog_list) + argument :hide_closed_list, + GraphQL::BOOLEAN_TYPE, + required: false, + description: copy_field_description(Types::BoardType, :hide_closed_list) + end + end + end +end diff --git a/app/graphql/mutations/boards/create.rb b/app/graphql/mutations/boards/create.rb index ebbd19930ec..92bce557446 100644 --- a/app/graphql/mutations/boards/create.rb +++ b/app/graphql/mutations/boards/create.rb @@ -7,36 +7,18 @@ module Mutations graphql_name 'CreateBoard' + include Mutations::Boards::CommonMutationArguments + field :board, Types::BoardType, null: true, description: 'The board after mutation.' - argument :name, - GraphQL::STRING_TYPE, - required: false, - description: 'The board name.' - argument :assignee_id, - GraphQL::STRING_TYPE, - required: false, - description: 'The ID of the user to be assigned to the board.' - argument :milestone_id, - Types::GlobalIDType[Milestone], - required: false, - description: 'The ID of the milestone to be assigned to the board.' - argument :weight, - GraphQL::BOOLEAN_TYPE, - required: false, - description: 'The weight of the board.' - argument :label_ids, - [Types::GlobalIDType[Label]], - required: false, - description: 'The IDs of labels to be added to the board.' - authorize :admin_board def resolve(args) board_parent = authorized_resource_parent_find!(args) + response = ::Boards::CreateService.new(board_parent, current_user, args).execute { @@ -47,3 +29,5 @@ module Mutations end end end + +Mutations::Boards::Create.prepend_if_ee('::EE::Mutations::Boards::Create') diff --git a/app/graphql/mutations/boards/update.rb b/app/graphql/mutations/boards/update.rb new file mode 100644 index 00000000000..5cb434e41fd --- /dev/null +++ b/app/graphql/mutations/boards/update.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Mutations + module Boards + class Update < ::Mutations::BaseMutation + graphql_name 'UpdateBoard' + + include Mutations::Boards::CommonMutationArguments + + argument :id, + ::Types::GlobalIDType[::Board], + required: true, + description: 'The board global ID.' + + field :board, + Types::BoardType, + null: true, + description: 'The board after mutation.' + + authorize :admin_board + + def resolve(id:, **args) + board = authorized_find!(id: id) + + ::Boards::UpdateService.new(board.resource_parent, current_user, args).execute(board) + + { + board: board, + errors: errors_on_object(board) + } + end + + def find_object(id:) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ::Types::GlobalIDType[::Board].coerce_isolated_input(id) + GitlabSchema.find_by_gid(id) + end + end + end +end + +Mutations::Boards::Update.prepend_if_ee('::EE::Mutations::Boards::Update') diff --git a/app/graphql/types/board_type.rb b/app/graphql/types/board_type.rb index 2a7b318e283..f47c744d1bb 100644 --- a/app/graphql/types/board_type.rb +++ b/app/graphql/types/board_type.rb @@ -12,6 +12,12 @@ module Types field :name, type: GraphQL::STRING_TYPE, null: true, description: 'Name of the board' + field :hide_backlog_list, type: GraphQL::BOOLEAN_TYPE, null: true, + description: 'Whether or not backlog list is hidden' + + field :hide_closed_list, type: GraphQL::BOOLEAN_TYPE, null: true, + description: 'Whether or not closed list is hidden' + field :lists, Types::BoardListType.connection_type, null: true, diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index a0a840add94..e8eb6a5d417 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -21,7 +21,6 @@ module GroupsHelper integrations#edit ldap_group_links#index hooks#index - audit_events#index pipeline_quota#index ] end diff --git a/app/models/cycle_analytics/level_base.rb b/app/models/cycle_analytics/level_base.rb index 63c55e2ca64..901636a7263 100644 --- a/app/models/cycle_analytics/level_base.rb +++ b/app/models/cycle_analytics/level_base.rb @@ -60,7 +60,7 @@ module CycleAnalytics end def [](stage_name) - if Feature.enabled?(:new_project_level_vsa_backend, resource_parent) + if Feature.enabled?(:new_project_level_vsa_backend, resource_parent, default_enabled: true) StageAdapter.new(build_stage(stage_name), options) else Gitlab::CycleAnalytics::Stage[stage_name].new(options: options) diff --git a/app/models/label_priority.rb b/app/models/label_priority.rb index 8f8f36efbfe..11854404a71 100644 --- a/app/models/label_priority.rb +++ b/app/models/label_priority.rb @@ -1,10 +1,13 @@ # frozen_string_literal: true class LabelPriority < ApplicationRecord + include Importable + belongs_to :project belongs_to :label - validates :project, :label, :priority, presence: true + validates :label, presence: true, unless: :importing? + validates :project, :priority, presence: true validates :label_id, uniqueness: { scope: :project_id } validates :priority, numericality: { only_integer: true, greater_than_or_equal_to: 0 } end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6231d8c9421..043f07cf9f3 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1755,7 +1755,7 @@ class MergeRequest < ApplicationRecord end def allows_reviewers? - Feature.enabled?(:merge_request_reviewers, project) + Feature.enabled?(:merge_request_reviewers, project, default_enabled: true) end def allows_multiple_reviewers? diff --git a/app/models/resource_event.rb b/app/models/resource_event.rb index 26dcda2630a..54fa4137f73 100644 --- a/app/models/resource_event.rb +++ b/app/models/resource_event.rb @@ -30,14 +30,6 @@ class ResourceEvent < ApplicationRecord return true if issuable_count == 1 - # if none of issuable IDs is set, check explicitly if nested issuable - # object is set, this is used during project import - if issuable_count == 0 && importing? - issuable_count = self.class.issuable_attrs.count { |attr| self.public_send(attr) } # rubocop:disable GitlabSecurity/PublicSend - - return true if issuable_count == 1 - end - errors.add( :base, _("Exactly one of %{attributes} is required") % { attributes: self.class.issuable_attrs.join(', ') } diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb index cc96698be09..57a3b568c53 100644 --- a/app/models/resource_label_event.rb +++ b/app/models/resource_label_event.rb @@ -12,7 +12,7 @@ class ResourceLabelEvent < ResourceEvent scope :inc_relations, -> { includes(:label, :user) } validates :label, presence: { unless: :importing? }, on: :create - validate :exactly_one_issuable + validate :exactly_one_issuable, unless: :importing? after_save :expire_etag_cache after_destroy :expire_etag_cache diff --git a/app/models/sentry_issue.rb b/app/models/sentry_issue.rb index 30f4026e633..fec1a55f17d 100644 --- a/app/models/sentry_issue.rb +++ b/app/models/sentry_issue.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true class SentryIssue < ApplicationRecord + include Importable + belongs_to :issue - validates :issue, uniqueness: true, presence: true + validates :issue, uniqueness: true + validates :issue, presence: true, unless: :importing? validates :sentry_issue_identifier, presence: true validate :ensure_sentry_issue_identifier_is_unique_per_project diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb index 8c72bd5ae7e..ff564d87449 100644 --- a/app/models/suggestion.rb +++ b/app/models/suggestion.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true class Suggestion < ApplicationRecord + include Importable include Suggestible belongs_to :note, inverse_of: :suggestions - validates :note, presence: true + validates :note, presence: true, unless: :importing? validates :commit_id, presence: true, if: :applied? delegate :position, :noteable, to: :note diff --git a/app/models/timelog.rb b/app/models/timelog.rb index 60aaaaef831..f4debedb656 100644 --- a/app/models/timelog.rb +++ b/app/models/timelog.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true class Timelog < ApplicationRecord + include Importable + validates :time_spent, :user, presence: true - validate :issuable_id_is_present + validate :issuable_id_is_present, unless: :importing? belongs_to :issue, touch: true belongs_to :merge_request, touch: true diff --git a/app/models/zoom_meeting.rb b/app/models/zoom_meeting.rb index f83aa93b69a..c8b510c4779 100644 --- a/app/models/zoom_meeting.rb +++ b/app/models/zoom_meeting.rb @@ -1,13 +1,17 @@ # frozen_string_literal: true class ZoomMeeting < ApplicationRecord + include Importable include UsageStatistics - belongs_to :project, optional: false - belongs_to :issue, optional: false + belongs_to :project + belongs_to :issue + + validates :project, presence: true, unless: :importing? + validates :issue, presence: true, unless: :importing? validates :url, presence: true, length: { maximum: 255 }, zoom_url: true - validates :issue, same_project_association: true + validates :issue, same_project_association: true, unless: :importing? enum issue_status: { added: 1, diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 3bc485cbdfc..4e39b5a05c0 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -437,8 +437,6 @@ %span = _('Pages') - = render_if_exists 'projects/sidebar/settings_audit_events' - = render 'shared/sidebar_toggle_button' -# Shortcut to Project > Activity diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 2f7b5b93de7..4711143c900 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -55,7 +55,7 @@ - if merge_request.assignees.any? %li.gl-display-flex.gl-align-items-center = render 'shared/issuable/assignees', project: merge_request.project, issuable: merge_request - - if Feature.enabled?(:merge_request_reviewers, @project) && merge_request.reviewers.any? + - if Feature.enabled?(:merge_request_reviewers, @project, default_enabled: true) && merge_request.reviewers.any? %li.gl-display-flex.issuable-reviewers = render 'shared/issuable/reviewers', project: merge_request.project, issuable: merge_request = render 'projects/merge_requests/approvals_count', merge_request: merge_request diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index fe507697321..cd265c10451 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -25,7 +25,7 @@ .block.assignee.qa-assignee-block = render "shared/issuable/sidebar_assignees", issuable_sidebar: issuable_sidebar, assignees: assignees, signed_in: signed_in - - if Feature.enabled?(:merge_request_reviewers, @project) && reviewers + - if Feature.enabled?(:merge_request_reviewers, @project, default_enabled: true) && reviewers .block.reviewer.qa-reviewer-block = render "shared/issuable/sidebar_reviewers", issuable_sidebar: issuable_sidebar, reviewers: reviewers, signed_in: signed_in |