summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-16 15:10:18 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-16 15:10:18 +0000
commit6364c14cc1f445d471bca118dca5af5a85b2c5dc (patch)
tree2579c5592f207e86ff7a0c5c7499caad723cdec1 /app
parent5a2284f3500088e04cf3a5854fb06dc9db2b6077 (diff)
downloadgitlab-ce-6364c14cc1f445d471bca118dca5af5a85b2c5dc.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/pipeline_editor/constants.js2
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue15
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js10
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue109
-rw-r--r--app/assets/javascripts/pipelines/components/unwrapping_utils.js17
-rw-r--r--app/assets/javascripts/pipelines/constants.js2
-rw-r--r--app/assets/javascripts/pipelines/utils.js44
-rw-r--r--app/controllers/projects/ci/pipeline_editor_controller.rb3
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/graphql/mutations/boards/common_mutation_arguments.rb24
-rw-r--r--app/graphql/mutations/boards/create.rb26
-rw-r--r--app/graphql/mutations/boards/update.rb43
-rw-r--r--app/graphql/types/board_type.rb6
-rw-r--r--app/helpers/groups_helper.rb1
-rw-r--r--app/models/cycle_analytics/level_base.rb2
-rw-r--r--app/models/label_priority.rb5
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/resource_event.rb8
-rw-r--r--app/models/resource_label_event.rb2
-rw-r--r--app/models/sentry_issue.rb5
-rw-r--r--app/models/suggestion.rb3
-rw-r--r--app/models/timelog.rb4
-rw-r--r--app/models/zoom_meeting.rb10
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
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