Commit b82cdff6 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'discover-unused-feature-flags' into 'master'

Detect unused feature flags during tests execution

See merge request gitlab-org/gitlab!44578
parents d9ae9036 b72f0338
...@@ -21,12 +21,10 @@ https://docs.gitlab.com/ee/development/documentation/index.html#move-or-rename-a ...@@ -21,12 +21,10 @@ https://docs.gitlab.com/ee/development/documentation/index.html#move-or-rename-a
a link to the new location. a link to the new location.
- [ ] Make sure internal links pointing to the document in question are not broken. - [ ] Make sure internal links pointing to the document in question are not broken.
- [ ] Search and replace any links referring to old docs in GitLab Rails app, - [ ] Search and replace any links referring to old docs in GitLab Rails app,
specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories. specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/documentation/index.html#redirections-for-pages-with-disqus-comments) - [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/documentation/index.html#redirections-for-pages-with-disqus-comments)
to the new document if there are any Disqus comments on the old document thread. to the new document if there are any Disqus comments on the old document thread.
- [ ] Update the link in `features.yml` (if applicable) - [ ] Update the link in `features.yml` (if applicable)
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE - [ ] Assign one of the technical writers for review.
with the changes as well (https://docs.gitlab.com/ce/development/documentation/index.html#cherry-picking-from-ce-to-ee).
- [ ] Ping one of the technical writers for review.
/label ~documentation /label ~documentation
...@@ -362,6 +362,15 @@ Graphql/AuthorizeTypes: ...@@ -362,6 +362,15 @@ Graphql/AuthorizeTypes:
- 'spec/**/*.rb' - 'spec/**/*.rb'
- 'ee/spec/**/*.rb' - 'ee/spec/**/*.rb'
Graphql/GIDExpectedType:
Enabled: true
Include:
- 'app/graphql/**/*'
- 'ee/app/graphql/**/*'
Exclude:
- 'spec/**/*.rb'
- 'ee/spec/**/*.rb'
Graphql/JSONType: Graphql/JSONType:
Enabled: true Enabled: true
Include: Include:
......
...@@ -73,7 +73,7 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart, ...@@ -73,7 +73,7 @@ sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about code reviews can be found in our star, smile, etc.). Some good tips about code reviews can be found in our
[Code Review Guidelines]. [Code Review Guidelines].
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html [Code Review Guidelines]: https://docs.gitlab.com/ee/development/code_review.html
## Feature flags ## Feature flags
...@@ -217,5 +217,5 @@ rebase with master to see if that solves the issue. ...@@ -217,5 +217,5 @@ rebase with master to see if that solves the issue.
[team]: https://about.gitlab.com/team/ [team]: https://about.gitlab.com/team/
[done]: https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#definition-of-done [done]: https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#definition-of-done
[automatic_ce_ee_merge]: https://docs.gitlab.com/ce/development/automatic_ce_ee_merge.html [automatic_ce_ee_merge]: https://docs.gitlab.com/ee/development/automatic_ce_ee_merge.html
[ee_features]: https://docs.gitlab.com/ce/development/ee_features.html [ee_features]: https://docs.gitlab.com/ee/development/ee_features.html
...@@ -376,7 +376,7 @@ const Api = { ...@@ -376,7 +376,7 @@ const Api = {
}, },
commitMultiple(id, data) { commitMultiple(id, data) {
// see https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions // see https://docs.gitlab.com/ee/api/commits.html#create-a-commit-with-multiple-files-and-actions
const url = Api.buildUrl(Api.commitsPath).replace(':id', encodeURIComponent(id)); const url = Api.buildUrl(Api.commitsPath).replace(':id', encodeURIComponent(id));
return axios.post(url, JSON.stringify(data), { return axios.post(url, JSON.stringify(data), {
headers: { headers: {
......
...@@ -360,7 +360,7 @@ export default { ...@@ -360,7 +360,7 @@ export default {
> >
<template #link="{ content }"> <template #link="{ content }">
<gl-link <gl-link
href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html" href="https://docs.gitlab.com/ee/user/project/integrations/prometheus.html"
target="_blank" target="_blank"
>{{ content }}</gl-link >{{ content }}</gl-link
> >
......
...@@ -116,6 +116,7 @@ export default { ...@@ -116,6 +116,7 @@ export default {
:placeholder="placeholder" :placeholder="placeholder"
:value="text" :value="text"
class="note-textarea ide-commit-message-textarea" class="note-textarea ide-commit-message-textarea"
data-qa-selector="ide_commit_message_field"
dir="auto" dir="auto"
name="commit-message" name="commit-message"
@scroll="handleScroll" @scroll="handleScroll"
......
import createFlash from '~/flash'; import EditorLite from '~/editor/editor_lite';
import { BLOB_EDITOR_ERROR } from '~/blob_edit/constants';
export default class CILintEditor { export default class CILintEditor {
constructor() { constructor() {
const monacoEnabled = window?.gon?.features?.monacoCi;
this.clearYml = document.querySelector('.clear-yml'); this.clearYml = document.querySelector('.clear-yml');
this.clearYml.addEventListener('click', this.clear.bind(this)); this.clearYml.addEventListener('click', this.clear.bind(this));
return monacoEnabled ? this.initEditorLite() : this.initAce(); return this.initEditorLite();
} }
clear() { clear() {
...@@ -15,34 +13,20 @@ export default class CILintEditor { ...@@ -15,34 +13,20 @@ export default class CILintEditor {
} }
initEditorLite() { initEditorLite() {
import(/* webpackChunkName: 'monaco_editor_lite' */ '~/editor/editor_lite') const editorEl = document.getElementById('editor');
.then(({ default: EditorLite }) => { const fileContentEl = document.getElementById('content');
const editorEl = document.getElementById('editor'); const form = document.querySelector('.js-ci-lint-form');
const fileContentEl = document.getElementById('content');
const form = document.querySelector('.js-ci-lint-form');
const rootEditor = new EditorLite(); const rootEditor = new EditorLite();
this.editor = rootEditor.createInstance({ this.editor = rootEditor.createInstance({
el: editorEl, el: editorEl,
blobPath: '.gitlab-ci.yml', blobPath: '.gitlab-ci.yml',
blobContent: editorEl.innerText, blobContent: editorEl.innerText,
}); });
form.addEventListener('submit', () => {
fileContentEl.value = this.editor.getValue();
});
})
.catch(() => createFlash({ message: BLOB_EDITOR_ERROR }));
}
initAce() {
this.editor = window.ace.edit('ci-editor');
this.textarea = document.getElementById('content');
this.editor.getSession().setMode('ace/mode/yaml'); form.addEventListener('submit', () => {
this.editor.on('input', () => { fileContentEl.value = this.editor.getValue();
this.textarea.value = this.editor.getSession().getValue();
}); });
} }
} }
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { GlIcon } from '@gitlab/ui'; import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '~/sidebar/event_hub'; import eventHub from '~/sidebar/event_hub';
import editForm from './edit_form.vue'; import editForm from './edit_form.vue';
...@@ -26,7 +25,7 @@ export default { ...@@ -26,7 +25,7 @@ export default {
}, },
directives: { directives: {
tooltip, GlTooltip: GlTooltipDirective,
}, },
props: { props: {
...@@ -79,13 +78,9 @@ export default { ...@@ -79,13 +78,9 @@ export default {
<template> <template>
<div class="block issuable-sidebar-item lock"> <div class="block issuable-sidebar-item lock">
<div <div
v-tooltip v-gl-tooltip.left.viewport="{ title: tooltipLabel }"
:title="tooltipLabel"
class="sidebar-collapsed-icon" class="sidebar-collapsed-icon"
data-testid="sidebar-collapse-icon" data-testid="sidebar-collapse-icon"
data-container="body"
data-placement="left"
data-boundary="viewport"
@click="toggleForm" @click="toggleForm"
> >
<gl-icon :name="lockStatus.icon" class="sidebar-item-icon is-active" /> <gl-icon :name="lockStatus.icon" class="sidebar-item-icon is-active" />
......
...@@ -471,12 +471,6 @@ $mr-widget-min-height: 69px; ...@@ -471,12 +471,6 @@ $mr-widget-min-height: 69px;
flex: 1; flex: 1;
} }
.issuable-meta {
.author-link {
display: inline-block;
}
}
.merge-request-title { .merge-request-title {
margin-bottom: 2px; margin-bottom: 2px;
......
...@@ -28,7 +28,10 @@ ...@@ -28,7 +28,10 @@
height: 14px; height: 14px;
width: 14px; width: 14px;
vertical-align: middle; vertical-align: middle;
fill: $gl-text-color-secondary;
&:not(.text-warning) {
fill: $gl-text-color-secondary;
}
} }
.sprite { .sprite {
......
...@@ -11,8 +11,8 @@ class Dashboard::LabelsController < Dashboard::ApplicationController ...@@ -11,8 +11,8 @@ class Dashboard::LabelsController < Dashboard::ApplicationController
def labels def labels
finder_params = { project_ids: projects.select(:id) } finder_params = { project_ids: projects.select(:id) }
labels = LabelsFinder.new(current_user, finder_params).execute
GlobalLabel.build_collection(labels) LabelsFinder.new(current_user, finder_params).execute
.select('DISTINCT ON (labels.title) labels.*')
end end
end end
...@@ -25,7 +25,7 @@ module Ci ...@@ -25,7 +25,7 @@ module Ci
attr_reader :current_user, :pipeline, :project, :params, :type attr_reader :current_user, :pipeline, :project, :params, :type
def init_collection def init_collection
if Feature.enabled?(:ci_jobs_finder_refactor) if Feature.enabled?(:ci_jobs_finder_refactor, default_enabled: true)
pipeline_jobs || project_jobs || all_jobs pipeline_jobs || project_jobs || all_jobs
else else
project ? project_builds : all_jobs project ? project_builds : all_jobs
...@@ -59,7 +59,7 @@ module Ci ...@@ -59,7 +59,7 @@ module Ci
end end
def filter_by_scope(builds) def filter_by_scope(builds)
if Feature.enabled?(:ci_jobs_finder_refactor) if Feature.enabled?(:ci_jobs_finder_refactor, default_enabled: true)
return filter_by_statuses!(params[:scope], builds) if params[:scope].is_a?(Array) return filter_by_statuses!(params[:scope], builds) if params[:scope].is_a?(Array)
end end
......
...@@ -102,7 +102,7 @@ class IssuableFinder ...@@ -102,7 +102,7 @@ class IssuableFinder
items = filter_items(items) items = filter_items(items)
# Let's see if we have to negate anything # Let's see if we have to negate anything
items = filter_negated_items(items) items = filter_negated_items(items) if should_filter_negated_args?
# This has to be last as we use a CTE as an optimization fence # This has to be last as we use a CTE as an optimization fence
# for counts by passing the force_cte param and passing the # for counts by passing the force_cte param and passing the
...@@ -134,13 +134,15 @@ class IssuableFinder ...@@ -134,13 +134,15 @@ class IssuableFinder
by_my_reaction_emoji(items) by_my_reaction_emoji(items)
end end
# Negates all params found in `negatable_params` def should_filter_negated_args?
def filter_negated_items(items) return false unless Feature.enabled?(:not_issuable_queries, params.group || params.project, default_enabled: true)
return items unless Feature.enabled?(:not_issuable_queries, params.group || params.project, default_enabled: true)
# API endpoints send in `nil` values so we test if there are any non-nil # API endpoints send in `nil` values so we test if there are any non-nil
return items unless not_params.present? && not_params.values.any? not_params.present? && not_params.values.any?
end
# Negates all params found in `negatable_params`
def filter_negated_items(items)
items = by_negated_author(items) items = by_negated_author(items)
items = by_negated_assignee(items) items = by_negated_assignee(items)
items = by_negated_label(items) items = by_negated_label(items)
......
# frozen_string_literal: true
module Mutations
module Issues
module CommonMutationArguments
extend ActiveSupport::Concern
included do
argument :description, GraphQL::STRING_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :description)
argument :due_date, GraphQL::Types::ISO8601Date,
required: false,
description: copy_field_description(Types::IssueType, :due_date)
argument :confidential, GraphQL::BOOLEAN_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :confidential)
argument :locked, GraphQL::BOOLEAN_TYPE,
as: :discussion_locked,
required: false,
description: copy_field_description(Types::IssueType, :discussion_locked)
end
end
end
end
Mutations::Issues::CommonMutationArguments.prepend_if_ee('::EE::Mutations::Issues::CommonMutationArguments')
# frozen_string_literal: true
module Mutations
module Issues
class Create < BaseMutation
include ResolvesProject
graphql_name 'CreateIssue'
authorize :create_issue
include CommonMutationArguments
argument :project_path, GraphQL::ID_TYPE,
required: true,
description: 'Project full path the issue is associated with'
argument :iid, GraphQL::INT_TYPE,
required: false,
description: 'The IID (internal ID) of a project issue. Only admins and project owners can modify'
argument :title, GraphQL::STRING_TYPE,
required: true,
description: copy_field_description(Types::IssueType, :title)
argument :milestone_id, ::Types::GlobalIDType[::Milestone],
required: false,
description: 'The ID of the milestone to assign to the issue. On update milestone will be removed if set to null'
argument :labels, [GraphQL::STRING_TYPE],
required: false,
description: copy_field_description(Types::IssueType, :labels)
argument :label_ids, [::Types::GlobalIDType[::Label]],
required: false,
description: 'The IDs of labels to be added to the issue'
argument :created_at, Types::TimeType,
required: false,
description: 'Timestamp when the issue was created. Available only for admins and project owners'
argument :merge_request_to_resolve_discussions_of, ::Types::GlobalIDType[::MergeRequest],
required: false,
description: 'The IID of a merge request for which to resolve discussions'
argument :discussion_to_resolve, GraphQL::STRING_TYPE,
required: false,
description: 'The ID of a discussion to resolve. Also pass `merge_request_to_resolve_discussions_of`'
argument :assignee_ids, [::Types::GlobalIDType[::User]],
required: false,
description: 'The array of user IDs to assign to the issue'
field :issue,
Types::IssueType,
null: true,
description: 'The issue after mutation'
def ready?(**args)
if args.slice(*mutually_exclusive_label_args).size > 1
arg_str = mutually_exclusive_label_args.map { |x| x.to_s.camelize(:lower) }.join(' or ')
raise Gitlab::Graphql::Errors::ArgumentError, "one and only one of #{arg_str} is required."
end
if args[:discussion_to_resolve].present? && args[:merge_request_to_resolve_discussions_of].blank?
raise Gitlab::Graphql::Errors::ArgumentError,
'to resolve a discussion please also provide `merge_request_to_resolve_discussions_of` parameter'
end
super
end
def resolve(project_path:, **attributes)
project = authorized_find!(full_path: project_path)
params = build_create_issue_params(attributes.merge(author_id: current_user.id))
issue = ::Issues::CreateService.new(project, current_user, params).execute
if issue.spam?
issue.errors.add(:base, 'Spam detected.')
end
{
issue: issue.valid? ? issue : nil,
errors: errors_on_object(issue)
}
end
private
def build_create_issue_params(params)
params[:milestone_id] &&= params[:milestone_id]&.model_id
params[:assignee_ids] &&= params[:assignee_ids].map { |assignee_id| assignee_id&.model_id }
params[:label_ids] &&= params[:label_ids].map { |label_id| label_id&.model_id }
params
end
def mutually_exclusive_label_args
[:labels, :label_ids]
end
def find_object(full_path:)
resolve_project(full_path: full_path)
end
end
end
end
Mutations::Issues::Create.prepend_if_ee('::EE::Mutations::Issues::Create')
...@@ -5,49 +5,26 @@ module Mutations ...@@ -5,49 +5,26 @@ module Mutations
class Update < Base class Update < Base
graphql_name 'UpdateIssue' graphql_name 'UpdateIssue'
argument :title, include CommonMutationArguments
GraphQL::STRING_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :title)
argument :description, argument :title, GraphQL::STRING_TYPE,
GraphQL::STRING_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :description)
argument :due_date,
Types::TimeType,
required: false,
description: copy_field_description(Types::IssueType, :due_date)
argument :confidential,
GraphQL::BOOLEAN_TYPE,
required: false,
description: copy_field_description(Types::IssueType, :confidential)
argument :locked,
GraphQL::BOOLEAN_TYPE,
as: :discussion_locked,
required: false, required: false,
description: copy_field_description(Types::IssueType, :discussion_locked) description: copy_field_description(Types::IssueType, :title)
argument :add_label_ids, argument :milestone_id, GraphQL::ID_TYPE,
[GraphQL::ID_TYPE],
required: false, required: false,
description: 'The IDs of labels to be added to the issue.' description: 'The ID of the milestone to assign to the issue. On update milestone will be removed if set to null'
argument :remove_label_ids, argument :add_label_ids, [GraphQL::ID_TYPE],
[GraphQL::ID_TYPE],
required: false, required: false,
description: 'The IDs of labels to be removed from the issue.' description: 'The IDs of labels to be added to the issue'
argument :milestone_id, argument :remove_label_ids, [GraphQL::ID_TYPE],
GraphQL::ID_TYPE,
required: false, required: false,
description: 'The ID of the milestone to be assigned, milestone will be removed if set to null.' description: 'The IDs of labels to be removed from the issue'
argument :state_event, Types::IssueStateEventEnum, argument :state_event, Types::IssueStateEventEnum,
description: 'Close or reopen an issue.', description: 'Close or reopen an issue',
required: false required: false
def resolve(project_path:, iid:, **args) def resolve(project_path:, iid:, **args)
......
...@@ -17,7 +17,7 @@ module Mutations ...@@ -17,7 +17,7 @@ module Mutations
discussion_id = nil discussion_id = nil
if args[:discussion_id] if args[:discussion_id]
discussion = GitlabSchema.object_from_id(args[:discussion_id]) discussion = GitlabSchema.object_from_id(args[:discussion_id], expected_type: ::Discussion)
authorize_discussion!(discussion) authorize_discussion!(discussion)
discussion_id = discussion.id discussion_id = discussion.id
......
...@@ -30,7 +30,7 @@ module Types ...@@ -30,7 +30,7 @@ module Types
# most recent `Version` for an issue # most recent `Version` for an issue
Gitlab::SafeRequestStore.fetch([request_cache_base_key, 'stateful_version', object.issue_id, version_gid]) do Gitlab::SafeRequestStore.fetch([request_cache_base_key, 'stateful_version', object.issue_id, version_gid]) do
if version_gid if version_gid
GitlabSchema.object_from_id(version_gid)&.sync GitlabSchema.object_from_id(version_gid, expected_type: ::DesignManagement::Version)&.sync
else else
object.issue.design_versions.most_recent object.issue.design_versions.most_recent
end end
......
...@@ -23,6 +23,7 @@ module Types ...@@ -23,6 +23,7 @@ module Types
mount_mutation Mutations::Branches::Create, calls_gitaly: true mount_mutation Mutations::Branches::Create, calls_gitaly: true
mount_mutation Mutations::Commits::Create, calls_gitaly: true mount_mutation Mutations::Commits::Create, calls_gitaly: true
mount_mutation Mutations::Discussions::ToggleResolve mount_mutation Mutations::Discussions::ToggleResolve
mount_mutation Mutations::Issues::Create
mount_mutation Mutations::Issues::SetAssignees mount_mutation Mutations::Issues::SetAssignees
mount_mutation Mutations::Issues::SetConfidential mount_mutation Mutations::Issues::SetConfidential
mount_mutation Mutations::Issues::SetLocked mount_mutation Mutations::Issues::SetLocked
......
# frozen_string_literal: true
class GlobalLabel
include Presentable
attr_accessor :title, :labels
alias_attribute :name, :title
delegate :color, :text_color, :description, :scoped_label?, to: :@first_label
def for_display
@first_label
end
def self.build_collection(labels)
labels = labels.group_by(&:title)
labels.map do |title, labels|
new(title, labels)
end
end
def initialize(title, labels)
@title = title
@labels = labels
@first_label = labels.find { |lbl| lbl.description.present? } || labels.first
end
def present(attributes)
super(attributes.merge(presenter_class: ::LabelPresenter))
end
end
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
class Packages::Event < ApplicationRecord class Packages::Event < ApplicationRecord
belongs_to :package, optional: true belongs_to :package, optional: true
# FIXME: Remove debian: 9 from here when it's added to the types in package.rb model
EVENT_SCOPES = ::Packages::Package.package_types.merge(container: 1000, tag: 1001).freeze EVENT_SCOPES = ::Packages::Package.package_types.merge(container: 1000, tag: 1001).freeze
enum event_scope: EVENT_SCOPES enum event_scope: EVENT_SCOPES
......
...@@ -106,14 +106,26 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -106,14 +106,26 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
add_special_file_path(file_name: 'LICENSE') add_special_file_path(file_name: 'LICENSE')
end end
def add_license_ide_path
ide_edit_path(project, default_branch_or_master, 'LICENSE')
end
def add_changelog_path def add_changelog_path
add_special_file_path(file_name: 'CHANGELOG') add_special_file_path(file_name: 'CHANGELOG')
end end
def add_changelog_ide_path
ide_edit_path(project, default_branch_or_master, 'CHANGELOG')
end
def add_contribution_guide_path def add_contribution_guide_path
add_special_file_path(file_name: 'CONTRIBUTING.md', commit_message: 'Add CONTRIBUTING') add_special_file_path(file_name: 'CONTRIBUTING.md', commit_message: 'Add CONTRIBUTING')
end end
def add_contribution_guide_ide_path
ide_edit_path(project, default_branch_or_master, 'CONTRIBUTING.md')
end
def add_ci_yml_path def add_ci_yml_path
add_special_file_path(file_name: ci_config_path_or_default) add_special_file_path(file_name: ci_config_path_or_default)
end end
...@@ -122,6 +134,10 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -122,6 +134,10 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
add_special_file_path(file_name: 'README.md') add_special_file_path(file_name: 'README.md')
end end
def add_readme_ide_path
ide_edit_path(project, default_branch_or_master, 'README.md')
end
def license_short_name def license_short_name
license = repository.license license = repository.license
license&.nickname || license&.name || 'LICENSE' license&.nickname || license&.name || 'LICENSE'
...@@ -218,9 +234,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -218,9 +234,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def new_file_anchor_data def new_file_anchor_data
if current_user && can_current_user_push_to_default_branch? if current_user && can_current_user_push_to_default_branch?
new_file_path = empty_repo? ? ide_edit_path(project, default_branch_or_master) : project_new_blob_path(project, default_branch_or_master)
AnchorData.new(false, AnchorData.new(false,
statistic_icon + _('New file'), statistic_icon + _('New file'),
project_new_blob_path(project, default_branch_or_master), new_file_path,
'missing') 'missing')
end end
end end
...@@ -229,7 +247,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -229,7 +247,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
if current_user && can_current_user_push_to_default_branch? && repository.readme.nil? if current_user && can_current_user_push_to_default_branch? && repository.readme.nil?
AnchorData.new(false, AnchorData.new(false,
statistic_icon + _('Add README'), statistic_icon + _('Add README'),
add_readme_path) empty_repo? ? add_readme_ide_path : add_readme_path)
elsif repository.readme elsif repository.readme
AnchorData.new(false, AnchorData.new(false,
statistic_icon('doc-text') + _('README'), statistic_icon('doc-text') + _('README'),
...@@ -243,7 +261,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -243,7 +261,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
if current_user && can_current_user_push_to_default_branch? && repository.changelog.blank? if current_user && can_current_user_push_to_default_branch? && repository.changelog.blank?
AnchorData.new(false, AnchorData.new(false,
statistic_icon + _('Add CHANGELOG'), statistic_icon + _('Add CHANGELOG'),
add_changelog_path) empty_repo? ? add_changelog_ide_path : add_changelog_path)
elsif repository.changelog.present? elsif repository.changelog.present?
AnchorData.new(false, AnchorData.new(false,
statistic_icon('doc-text') + _('CHANGELOG'), statistic_icon('doc-text') + _('CHANGELOG'),
...@@ -264,7 +282,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -264,7 +282,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
if current_user && can_current_user_push_to_default_branch? if current_user && can_current_user_push_to_default_branch?
AnchorData.new(false, AnchorData.new(false,
content_tag(:span, statistic_icon + _('Add LICENSE'), class: 'add-license-link d-flex'), content_tag(:span, statistic_icon + _('Add LICENSE'), class: 'add-license-link d-flex'),
add_license_path) empty_repo? ? add_license_ide_path : add_license_path)
else else
AnchorData.new(false, AnchorData.new(false,
icon + content_tag(:span, _('No license. All rights reserved'), class: 'project-stat-value'), icon + content_tag(:span, _('No license. All rights reserved'), class: 'project-stat-value'),
...@@ -277,7 +295,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -277,7 +295,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
if current_user && can_current_user_push_to_default_branch? && repository.contribution_guide.blank? if current_user && can_current_user_push_to_default_branch? && repository.contribution_guide.blank?
AnchorData.new(false, AnchorData.new(false,
statistic_icon + _('Add CONTRIBUTING'), statistic_icon + _('Add CONTRIBUTING'),
add_contribution_guide_path) empty_repo? ? add_contribution_guide_ide_path : add_contribution_guide_path)
elsif repository.contribution_guide.present? elsif repository.contribution_guide.present?
AnchorData.new(false, AnchorData.new(false,
statistic_icon('doc-text') + _('CONTRIBUTING'), statistic_icon('doc-text') + _('CONTRIBUTING'),
......
...@@ -133,7 +133,7 @@ class BuildDetailsEntity < JobEntity ...@@ -133,7 +133,7 @@ class BuildDetailsEntity < JobEntity
def callout_message def callout_message
return super unless build.failure_reason.to_sym == :missing_dependency_failure return super unless build.failure_reason.to_sym == :missing_dependency_failure
docs_url = "https://docs.gitlab.com/ce/ci/yaml/README.html#dependencies" docs_url = "https://docs.gitlab.com/ee/ci/yaml/README.html#dependencies"
[ [
failure_message.html_safe, failure_message.html_safe,
......
# frozen_string_literal: true # frozen_string_literal: true
class LabelEntity < Grape::Entity class LabelEntity < Grape::Entity
expose :id, if: ->(label, _) { !label.is_a?(GlobalLabel) } expose :id
expose :title expose :title
expose :color expose :color
expose :description expose :description
expose :group_id expose :group_id
expose :project_id, if: ->(label, _) { !label.is_a?(GlobalLabel) } expose :project_id
expose :template expose :template
expose :text_color expose :text_color
expose :created_at expose :created_at
......
...@@ -34,6 +34,18 @@ module Issues ...@@ -34,6 +34,18 @@ module Issues
private private
def filter_params(merge_request)
super
moved_issue = params.delete(:moved_issue)
# Setting created_at, updated_at and iid is allowed only for admins and owners or
# when moving an issue as we preserve the original issue attributes except id and iid.
params.delete(:iid) unless current_user.can?(:set_issue_iid, project)
params.delete(:created_at) unless moved_issue || current_user.can?(:set_issue_created_at, project)
params.delete(:updated_at) unless moved_issue || current_user.can?(:set_issue_updated_at, project)
end
def create_assignee_note(issue, old_assignees) def create_assignee_note(issue, old_assignees)
SystemNoteService.change_issuable_assignees( SystemNoteService.change_issuable_assignees(
issue, issue.project, current_user, old_assignees) issue, issue.project, current_user, old_assignees)
......
...@@ -52,7 +52,8 @@ module Issues ...@@ -52,7 +52,8 @@ module Issues
iid: nil, iid: nil,
project: target_project, project: target_project,
author: original_entity.author, author: original_entity.author,
assignee_ids: original_entity.assignee_ids assignee_ids: original_entity.assignee_ids,
moved_issue: true
} }
new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params) new_params = original_entity.serializable_hash.symbolize_keys.merge(new_params)
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
= hidden_field_tag 'new_parent_group_id' = hidden_field_tag 'new_parent_group_id'
%ul %ul
- side_effects_link_start = '<a href="https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths" target="_blank">' - side_effects_link_start = '<a href="https://docs.gitlab.com/ee/user/project/index.html#redirects-when-changing-repository-paths" target="_blank">'
- warning_text = s_("GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}.") % { side_effects_link_start: side_effects_link_start, side_effects_link_end:'</a>' } - warning_text = s_("GroupSettings|Be careful. Changing a group's parent can have unintended %{side_effects_link_start}side effects%{side_effects_link_end}.") % { side_effects_link_start: side_effects_link_start, side_effects_link_end:'</a>' }
%li= warning_text.html_safe %li= warning_text.html_safe
%li= s_('GroupSettings|You can only transfer the group to a group you manage.') %li= s_('GroupSettings|You can only transfer the group to a group you manage.')
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
- if ref - if ref
- if job.ref - if job.ref
.icon-container.gl-display-inline-block .icon-container.gl-display-inline-block
= job.tag? ? icon('tag') : sprite_icon('fork', css_class: 'sprite') = job.tag? ? sprite_icon('label', css_class: 'sprite') : sprite_icon('fork', css_class: 'sprite')
= link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name" = link_to job.ref, project_ref_path(job.project, job.ref), class: "ref-name"
- else - else
.light= _('none') .light= _('none')
...@@ -33,10 +33,12 @@ ...@@ -33,10 +33,12 @@
= link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha mr-0" = link_to job.short_sha, project_commit_path(job.project, job.sha), class: "commit-sha mr-0"
- if job.stuck? - if job.stuck?
= icon('warning', class: 'text-warning has-tooltip', title: _('Job is stuck. Check runners.')) %span.has-tooltip{ title: _('Job is stuck. Check runners.') }
= sprite_icon('warning', css_class: 'text-warning!')
- if retried - if retried
= icon('refresh', class: 'text-warning has-tooltip', title: _('Job was retried')) %span.has-tooltip{ title: _('Job was retried') }
= sprite_icon('retry', css_class: 'text-warning')
.label-container .label-container
- if job.tags.any? - if job.tags.any?
...@@ -87,7 +89,7 @@ ...@@ -87,7 +89,7 @@
- if job.finished_at - if job.finished_at
%p.finished-at %p.finished-at
= icon("calendar") = sprite_icon("calendar")
%span= time_ago_with_tooltip(job.finished_at) %span= time_ago_with_tooltip(job.finished_at)
%td.coverage %td.coverage
......
- page_title _("CI Lint") - page_title _("CI Lint")
- page_description _("Validate your GitLab CI configuration file") - page_description _("Validate your GitLab CI configuration file")
- unless Feature.enabled?(:monaco_ci, default_enabled: true)
- content_for :page_specific_javascripts do
= page_specific_javascript_tag('lib/ace.js')
%h2.pt-3.pb-3= _("Validate your GitLab CI configuration") %h2.pt-3.pb-3= _("Validate your GitLab CI configuration")
...@@ -17,12 +14,9 @@ ...@@ -17,12 +14,9 @@
.file-holder .file-holder
.js-file-title.file-title.clearfix .js-file-title.file-title.clearfix
= _("Contents of .gitlab-ci.yml") = _("Contents of .gitlab-ci.yml")
- if Feature.enabled?(:monaco_ci, default_enabled: true) .file-editor.code
.file-editor.code .js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }<
.js-edit-mode-pane.qa-editor#editor{ data: { 'editor-loading': true } }< %pre.editor-loading-content= params[:content]
%pre.editor-loading-content= params[:content]
- else
#ci-editor.ci-editor= @content
= text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true) = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true)
.col-sm-12 .col-sm-12
.float-left.gl-mt-3 .float-left.gl-mt-3
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
= link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') do = link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') do
= sprite_icon('warning-solid') = sprite_icon('warning-solid')
- if merge_request.assignees.any? - if merge_request.assignees.any?
%li.gl-display-flex %li.gl-display-flex.gl-align-items-center
= render 'shared/issuable/assignees', project: merge_request.project, issuable: merge_request = 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) && merge_request.reviewers.any?
%li.gl-display-flex.issuable-reviewers %li.gl-display-flex.issuable-reviewers
......
- current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1] - current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1]
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, currentRoutePath: current_route_path }) - add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path })
- breadcrumb_title _("Repository") - breadcrumb_title _("Repository")
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
......
---
title: Add GraphQL mutation to create an issue
merge_request: 43735
author:
type: added
---
title: Align badge with avatar in MR List
merge_request: 44671
author:
type: fixed
---
title: Use Web IDE to create new files in empty repos
merge_request: 44287
author:
type: added
---
title: Replace fa icons in CI build table
merge_request: 45123
author:
type: changed
---
title: Fixed incorrect parameter in GraphQL startup call
merge_request: 45115
author:
type: fixed
...@@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36622 ...@@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36622
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/245183 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/245183
group: group::continuous integration group: group::continuous integration
type: development type: development
default_enabled: false default_enabled: true
---
name: monaco_ci
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23666
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/249137
group: group::editor
type: development
default_enabled: true
...@@ -26,18 +26,18 @@ PATTERNS = %w[ ...@@ -26,18 +26,18 @@ PATTERNS = %w[
gl-deprecated-dropdown-divider gl-deprecated-dropdown-divider
gl-deprecated-dropdown-header gl-deprecated-dropdown-header
gl-deprecated-dropdown-item gl-deprecated-dropdown-item
graphql_pagination
has-tooltip has-tooltip
has_tooltip has_tooltip
initDeprecatedJQueryDropdown initDeprecatedJQueryDropdown
loading-button loading-button
pagination-button
v-popover v-popover
v-tooltip v-tooltip
with_tooltip with_tooltip
].freeze ].freeze
BLOCKING_PATTERNS = %w[ BLOCKING_PATTERNS = %w[
pagination-button
graphql_pagination
].freeze ].freeze
def get_added_lines(files) def get_added_lines(files)
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
description: 'Learn how to install, configure, update, and maintain your GitLab instance.' description: 'Learn how to install, configure, update, and maintain your GitLab instance.'
--- ---
# Administrator Docs **(CORE ONLY)** # Administrator documentation **(CORE ONLY)**
Learn how to administer your self-managed GitLab instance. Learn how to administer your self-managed GitLab instance.
...@@ -12,18 +12,16 @@ GitLab has two product distributions available through [different subscriptions] ...@@ -12,18 +12,16 @@ GitLab has two product distributions available through [different subscriptions]
- The open core [GitLab Enterprise Edition (EE)](https://gitlab.com/gitlab-org/gitlab). - The open core [GitLab Enterprise Edition (EE)](https://gitlab.com/gitlab-org/gitlab).
You can [install either GitLab CE or GitLab EE](https://about.gitlab.com/install/ce-or-ee/). You can [install either GitLab CE or GitLab EE](https://about.gitlab.com/install/ce-or-ee/).
However, the features you'll have access to depend on the subscription you choose However, the features you have access to depend on your chosen [subscription](https://about.gitlab.com/pricing/).
(Core, Starter, Premium, or Ultimate).
NOTE: **Note:** GitLab Community Edition installations have access only to Core features.
GitLab Community Edition installations only have access to Core features.
GitLab.com is administered by GitLab, Inc., therefore, only GitLab team members have Non-administrator users can't access GitLab administration tools and settings.
access to its admin configurations. If you're a GitLab.com user, please check the
[user documentation](../user/index.md).
NOTE: **Note:** GitLab.com is administered by GitLab, Inc., and only GitLab team members have
Non-administrator users don’t have access to GitLab administration tools and settings. access to its administration tools and settings. Users of GitLab.com should
instead refer to the [User documentation](../user/index.md) for GitLab
configuration and usage documentation.
## Installing and maintaining GitLab ## Installing and maintaining GitLab
......
...@@ -64,18 +64,17 @@ To set up GitLab and its components to accommodate up to 2,000 users: ...@@ -64,18 +64,17 @@ To set up GitLab and its components to accommodate up to 2,000 users:
## Configure the external load balancer ## Configure the external load balancer
NOTE: **Note:**
This architecture has been tested and validated with [HAProxy](https://www.haproxy.org/).
Although you can use a load balancer with a similar set of features, GitLab
hasn't validated other load balancers.
In an active/active GitLab configuration, you'll need a load balancer to route In an active/active GitLab configuration, you'll need a load balancer to route
traffic to the application servers. The specifics for which load balancer to traffic to the application servers. The specifics on which load balancer to use
use or its exact configuration is out of scope for the GitLab documentation. or its exact configuration is beyond the scope of GitLab documentation. We hope
If you're managing multi-node systems (including GitLab) you'll probably that if you're managing multi-node systems like GitLab, you already have a load
already have a load balancer of choice. Some examples including HAProxy balancer of choice. Some load balancer examples include HAProxy (open-source),
(open-source), F5 Big-IP LTM, and Citrix Net Scaler. This documentation F5 Big-IP LTM, and Citrix Net Scaler. This documentation outline the ports and
includes the ports and protocols for use with GitLab. protocols needed for use with GitLab.
This architecture has been tested and validated with [HAProxy](https://www.haproxy.org/)
as the load balancer. Although other load balancers with similar feature sets
could also be used, those load balancers have not been validated.
The next question is how you will handle SSL in your environment. There are The next question is how you will handle SSL in your environment. There are
several different options: several different options:
...@@ -489,11 +488,10 @@ Name. If you are addressing the Gitaly server by its IP address, you must add it ...@@ -489,11 +488,10 @@ Name. If you are addressing the Gitaly server by its IP address, you must add it
as a Subject Alternative Name to the certificate. as a Subject Alternative Name to the certificate.
[gRPC does not support using an IP address as Common Name in a certificate](https://github.com/grpc/grpc/issues/2691). [gRPC does not support using an IP address as Common Name in a certificate](https://github.com/grpc/grpc/issues/2691).
NOTE: **Note:** It's possible to configure Gitaly servers with both an unencrypted listening
It is possible to configure Gitaly servers with both an address (`listen_addr`) and an encrypted listening address (`tls_listen_addr`)
unencrypted listening address `listen_addr` and an encrypted listening at the same time. This allows you to do a gradual transition from unencrypted to
address `tls_listen_addr` at the same time. This allows you to do a encrypted traffic, if necessary.
gradual transition from unencrypted to encrypted traffic, if necessary.
To configure Gitaly with TLS: To configure Gitaly with TLS:
...@@ -537,14 +535,14 @@ To configure Gitaly with TLS: ...@@ -537,14 +535,14 @@ To configure Gitaly with TLS:
## Configure GitLab Rails ## Configure GitLab Rails
NOTE: **Note:**
In our architectures we run each GitLab Rails node using the Puma webserver
and have its number of workers set to 90% of available CPUs along with four threads. For
nodes that are running Rails with other components the worker value should be reduced
accordingly where we've found 50% achieves a good balance but this is dependent
on workload.
This section describes how to configure the GitLab application (Rails) component. This section describes how to configure the GitLab application (Rails) component.
In our architecture, we run each GitLab Rails node using the Puma webserver, and
have its number of workers set to 90% of available CPUs, with four threads. For
nodes running Rails with other components, the worker value should be reduced
accordingly. We've determined that a worker value of 50% achieves a good balance,
but this is dependent on workload.
On each node perform the following: On each node perform the following:
1. If you're [using NFS](#configure-nfs-optional): 1. If you're [using NFS](#configure-nfs-optional):
...@@ -572,10 +570,10 @@ On each node perform the following: ...@@ -572,10 +570,10 @@ On each node perform the following:
mkdir -p /var/opt/gitlab/.ssh /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/git-data mkdir -p /var/opt/gitlab/.ssh /var/opt/gitlab/gitlab-rails/uploads /var/opt/gitlab/gitlab-rails/shared /var/opt/gitlab/gitlab-ci/builds /var/opt/gitlab/git-data
``` ```
1. Download/install Omnibus GitLab using **steps 1 and 2** from 1. Download and install Omnibus GitLab using **steps 1 and 2** from
[GitLab downloads](https://about.gitlab.com/install/). Do not complete other [GitLab downloads](https://about.gitlab.com/install/). Do not complete other
steps on the download page. steps on the download page.
1. Create/edit `/etc/gitlab/gitlab.rb` and use the following configuration. 1. Create or edit `/etc/gitlab/gitlab.rb` and use the following configuration.
To maintain uniformity of links across nodes, the `external_url` To maintain uniformity of links across nodes, the `external_url`
on the application server should point to the external URL that users will use on the application server should point to the external URL that users will use
to access GitLab. This would be the URL of the [load balancer](#configure-the-external-load-balancer) to access GitLab. This would be the URL of the [load balancer](#configure-the-external-load-balancer)
...@@ -671,12 +669,10 @@ On each node perform the following: ...@@ -671,12 +669,10 @@ On each node perform the following:
[Gitaly node](#configure-gitaly) and [Gitaly node](#configure-gitaly) and
[reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure). [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
NOTE: **Note:** When you specify `https` in the `external_url`, as in the previous example,
When you specify `https` in the `external_url`, as in the example GitLab expects that the SSL certificates are in `/etc/gitlab/ssl/`. If the
above, GitLab assumes you have SSL certificates in `/etc/gitlab/ssl/`. If certificates aren't present, NGINX will fail to start. For more information, see
certificates are not present, NGINX will fail to start. See the the [NGINX documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https).
[NGINX documentation](https://docs.gitlab.com/omnibus/settings/nginx.html#enable-https)
for more information.
### GitLab Rails post-configuration ### GitLab Rails post-configuration
...@@ -688,12 +684,11 @@ for more information. ...@@ -688,12 +684,11 @@ for more information.
sudo gitlab-rake gitlab:db:configure sudo gitlab-rake gitlab:db:configure
``` ```
NOTE: **Note:** If you encounter a `rake aborted!` error message stating that PgBouncer is
If you encounter a `rake aborted!` error stating that PgBouncer is failing to connect to failing to connect to PostgreSQL, it may be that your PgBouncer node's IP
PostgreSQL it may be that your PgBouncer node's IP address is missing from address is missing from PostgreSQL's `trust_auth_cidr_addresses` in `gitlab.rb`
PostgreSQL's `trust_auth_cidr_addresses` in `gitlab.rb` on your database nodes. See on your database nodes. Before proceeding, see
[PgBouncer error `ERROR: pgbouncer cannot connect to server`](troubleshooting.md#pgbouncer-error-error-pgbouncer-cannot-connect-to-server) [PgBouncer error `ERROR: pgbouncer cannot connect to server`](troubleshooting.md#pgbouncer-error-error-pgbouncer-cannot-connect-to-server).
in the Troubleshooting section before proceeding.
1. [Configure fast lookup of authorized SSH keys in the database](../operations/fast_ssh_key_lookup.md). 1. [Configure fast lookup of authorized SSH keys in the database](../operations/fast_ssh_key_lookup.md).
...@@ -893,16 +888,13 @@ functioning backups is encountered. ...@@ -893,16 +888,13 @@ functioning backups is encountered.
## Configure Advanced Search **(STARTER ONLY)** ## Configure Advanced Search **(STARTER ONLY)**
NOTE: **Note:** You can leverage Elasticsearch and [enable Advanced Search](../../integration/elasticsearch.md)
Elasticsearch cluster design and requirements are dependent on your specific data. for faster, more advanced code search across your entire GitLab instance.
For recommended best practices on how to set up your Elasticsearch cluster
alongside your instance, read how to
[choose the optimal cluster configuration](../../integration/elasticsearch.md#guidance-on-choosing-optimal-cluster-configuration).
You can leverage Elasticsearch and enable Advanced Search for faster, more
advanced code search across your entire GitLab instance.
[Learn how to set it up.](../../integration/elasticsearch.md) Elasticsearch cluster design and requirements are dependent on your specific
data. For recommended best practices about how to set up your Elasticsearch
cluster alongside your instance, read how to
[choose the optimal cluster configuration](../../integration/elasticsearch.md#guidance-on-choosing-optimal-cluster-configuration).
<div align="right"> <div align="right">
<a type="button" class="btn btn-default" href="#setup-components"> <a type="button" class="btn btn-default" href="#setup-components">
......
...@@ -3422,6 +3422,111 @@ type CreateImageDiffNotePayload { ...@@ -3422,6 +3422,111 @@ type CreateImageDiffNotePayload {
note: Note note: Note
} }
"""
Autogenerated input type of CreateIssue
"""
input CreateIssueInput {
"""
The array of user IDs to assign to the issue
"""
assigneeIds: [UserID!]
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Indicates the issue is confidential
"""
confidential: Boolean
"""
Timestamp when the issue was created. Available only for admins and project owners
"""
createdAt: Time
"""
Description of the issue
"""
description: String
"""
The ID of a discussion to resolve. Also pass `merge_request_to_resolve_discussions_of`
"""
discussionToResolve: String
"""
Due date of the issue
"""
dueDate: ISO8601Date
"""
The ID of an epic to associate the issue with
"""
epicId: EpicID
"""
The IID (internal ID) of a project issue. Only admins and project owners can modify
"""
iid: Int
"""
The IDs of labels to be added to the issue
"""
labelIds: [LabelID!]
"""
Labels of the issue
"""
labels: [String!]
"""
Indicates discussion is locked on the issue
"""
locked: Boolean
"""
The IID of a merge request for which to resolve discussions
"""
mergeRequestToResolveDiscussionsOf: MergeRequestID
"""
The ID of the milestone to assign to the issue. On update milestone will be removed if set to null
"""
milestoneId: MilestoneID
"""
Project full path the issue is associated with
"""
projectPath: ID!
"""
Title of the issue
"""
title: String!
}
"""
Autogenerated return type of CreateIssue
"""
type CreateIssuePayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The issue after mutation
"""
issue: Issue
}
""" """
Autogenerated input type of CreateIteration Autogenerated input type of CreateIteration
""" """
...@@ -11188,6 +11293,11 @@ type MergeRequestEdge { ...@@ -11188,6 +11293,11 @@ type MergeRequestEdge {
node: MergeRequest node: MergeRequest
} }
"""
Identifier of MergeRequest
"""
scalar MergeRequestID
""" """
Check permissions for the current user on a merge request Check permissions for the current user on a merge request
""" """
...@@ -11964,6 +12074,7 @@ type Mutation { ...@@ -11964,6 +12074,7 @@ type Mutation {
createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload createDiffNote(input: CreateDiffNoteInput!): CreateDiffNotePayload
createEpic(input: CreateEpicInput!): CreateEpicPayload createEpic(input: CreateEpicInput!): CreateEpicPayload
createImageDiffNote(input: CreateImageDiffNoteInput!): CreateImageDiffNotePayload createImageDiffNote(input: CreateImageDiffNoteInput!): CreateImageDiffNotePayload
createIssue(input: CreateIssueInput!): CreateIssuePayload
createIteration(input: CreateIterationInput!): CreateIterationPayload createIteration(input: CreateIterationInput!): CreateIterationPayload
createNote(input: CreateNoteInput!): CreateNotePayload createNote(input: CreateNoteInput!): CreateNotePayload
createRequirement(input: CreateRequirementInput!): CreateRequirementPayload createRequirement(input: CreateRequirementInput!): CreateRequirementPayload
...@@ -19452,7 +19563,7 @@ Autogenerated input type of UpdateIssue ...@@ -19452,7 +19563,7 @@ Autogenerated input type of UpdateIssue
""" """
input UpdateIssueInput { input UpdateIssueInput {
""" """
The IDs of labels to be added to the issue. The IDs of labels to be added to the issue
""" """
addLabelIds: [ID!] addLabelIds: [ID!]
...@@ -19474,18 +19585,13 @@ input UpdateIssueInput { ...@@ -19474,18 +19585,13 @@ input UpdateIssueInput {
""" """
Due date of the issue Due date of the issue
""" """
dueDate: Time dueDate: ISO8601Date
""" """
The ID of the parent epic. NULL when removing the association The ID of the parent epic. NULL when removing the association
""" """
epicId: ID epicId: ID
"""
The desired health status
"""
healthStatus: HealthStatus
""" """
The IID of the issue to mutate The IID of the issue to mutate
""" """
...@@ -19497,7 +19603,7 @@ input UpdateIssueInput { ...@@ -19497,7 +19603,7 @@ input UpdateIssueInput {
locked: Boolean locked: Boolean
""" """
The ID of the milestone to be assigned, milestone will be removed if set to null. The ID of the milestone to assign to the issue. On update milestone will be removed if set to null
""" """
milestoneId: ID milestoneId: ID
...@@ -19507,12 +19613,12 @@ input UpdateIssueInput { ...@@ -19507,12 +19613,12 @@ input UpdateIssueInput {
projectPath: ID! projectPath: ID!
""" """
The IDs of labels to be removed from the issue. The IDs of labels to be removed from the issue
""" """
removeLabelIds: [ID!] removeLabelIds: [ID!]
""" """
Close or reopen an issue. Close or reopen an issue
""" """
stateEvent: IssueStateEvent stateEvent: IssueStateEvent
......
...@@ -537,6 +537,16 @@ Autogenerated return type of CreateImageDiffNote. ...@@ -537,6 +537,16 @@ Autogenerated return type of CreateImageDiffNote.
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `note` | Note | The note after mutation | | `note` | Note | The note after mutation |
### CreateIssuePayload
Autogenerated return type of CreateIssue.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `issue` | Issue | The issue after mutation |
### CreateIterationPayload ### CreateIterationPayload
Autogenerated return type of CreateIteration. Autogenerated return type of CreateIteration.
......
...@@ -324,7 +324,6 @@ There are three main considerations on the logic built for the nav: ...@@ -324,7 +324,6 @@ There are three main considerations on the logic built for the nav:
- `https://docs.gitlab.com/ee/` - `https://docs.gitlab.com/ee/`
- `https://docs.gitlab.com/omnibus/` - `https://docs.gitlab.com/omnibus/`
- `https://docs.gitlab.com/runner/` - `https://docs.gitlab.com/runner/`
- `https://docs.gitlab.com/debug/`
- `https://docs.gitlab.com/*` - `https://docs.gitlab.com/*`
- [EE-only](#ee-only-docs): documentation only available in `/ee/`, not on `/ce/`, e.g.: - [EE-only](#ee-only-docs): documentation only available in `/ee/`, not on `/ce/`, e.g.:
- `https://docs.gitlab.com/ee/user/group/epics/` - `https://docs.gitlab.com/ee/user/group/epics/`
...@@ -342,8 +341,8 @@ all the nav links to other pages: ...@@ -342,8 +341,8 @@ all the nav links to other pages:
<% dir = @item.identifier.to_s[%r{(?<=/)[^/]+}] %> <% dir = @item.identifier.to_s[%r{(?<=/)[^/]+}] %>
``` ```
For instance, for `https://docs.gitlab.com/ce/user/index.html`, For instance, for `https://docs.gitlab.com/ee/user/index.html`,
`dir` == `ce`, and for `https://docs.gitlab.com/omnibus/README.html`, `dir` == `ee`, and for `https://docs.gitlab.com/omnibus/README.html`,
`dir` == `omnibus`. `dir` == `omnibus`.
#### Default URL #### Default URL
......
...@@ -1892,7 +1892,7 @@ for the changes to take effect. ...@@ -1892,7 +1892,7 @@ for the changes to take effect.
If the document you are editing resides in a place other than the GitLab CE/EE If the document you are editing resides in a place other than the GitLab CE/EE
`doc/` directory, instead of the relative link, use the full path: `doc/` directory, instead of the relative link, use the full path:
`https://docs.gitlab.com/ce/administration/restart_gitlab.html`. Replace `https://docs.gitlab.com/ee/administration/restart_gitlab.html`. Replace
`reconfigure` with `restart` where appropriate. `reconfigure` with `restart` where appropriate.
### Installation guide ### Installation guide
......
...@@ -619,7 +619,7 @@ Ensure you comply with the [Changelog entries guide](../changelog.md). ...@@ -619,7 +619,7 @@ Ensure you comply with the [Changelog entries guide](../changelog.md).
### 8. Ask for a Product Analytics Review ### 8. Ask for a Product Analytics Review
On GitLab.com, we have DangerBot setup to monitor Product Analytics related files and DangerBot will recommend a Product Analytics review. Mention `@gitlab-org/growth/product-analytics/engineers` in your MR for a review. On GitLab.com, we have DangerBot setup to monitor Product Analytics related files and DangerBot will recommend a Product Analytics review. Mention `@gitlab-org/growth/product_analytics/engineers` in your MR for a review.
### 9. Verify your metric ### 9. Verify your metric
......
# GitLab Docker images # GitLab Docker images
This content has been moved to [our documentation site](https://docs.gitlab.com/ce/install/docker.html). This content has been moved to [our documentation site](https://docs.gitlab.com/ee/install/docker.html).
...@@ -75,5 +75,18 @@ module EE ...@@ -75,5 +75,18 @@ module EE
items.in_iterations(params.iterations) items.in_iterations(params.iterations)
end end
end end
override :filter_negated_items
def filter_negated_items(items)
items = by_negated_epic(items)
super(items)
end
def by_negated_epic(items)
return items unless not_params[:epic_id].present?
items.not_in_epics(not_params[:epic_id].to_i)
end
end end
end end
# frozen_string_literal: true
module EE
module Mutations
module Issues
module CommonMutationArguments
extend ActiveSupport::Concern
included do
argument :health_status,
::Types::HealthStatusEnum,
required: false,
description: 'The desired health status'
argument :weight, GraphQL::INT_TYPE,
required: false,
description: 'The weight of the issue'
end
end
end
end
end
# frozen_string_literal: true
module EE
module Mutations
module Issues
module Create
extend ActiveSupport::Concern
prepended do
argument :epic_id, ::Types::GlobalIDType[::Epic],
required: false,
description: 'The ID of an epic to associate the issue with'
end
private
def create_issue_params(params)
params[:epic_id] &&= params[:epic_id]&.model_id
super(params)
end
end
end
end
end
...@@ -7,12 +7,7 @@ module EE ...@@ -7,12 +7,7 @@ module EE
extend ActiveSupport::Concern extend ActiveSupport::Concern
prepended do prepended do
argument :health_status, argument :epic_id, GraphQL::ID_TYPE,
::Types::HealthStatusEnum,
required: false,
description: 'The desired health status'
argument :epic_id,
GraphQL::ID_TYPE,
required: false, required: false,
description: 'The ID of the parent epic. NULL when removing the association' description: 'The ID of the parent epic. NULL when removing the association'
end end
......
...@@ -26,6 +26,7 @@ module EE ...@@ -26,6 +26,7 @@ module EE
scope :no_epic, -> { left_outer_joins(:epic_issue).where(epic_issues: { epic_id: nil }) } scope :no_epic, -> { left_outer_joins(:epic_issue).where(epic_issues: { epic_id: nil }) }
scope :any_epic, -> { joins(:epic_issue) } scope :any_epic, -> { joins(:epic_issue) }
scope :in_epics, ->(epics) { joins(:epic_issue).where(epic_issues: { epic_id: epics }) } scope :in_epics, ->(epics) { joins(:epic_issue).where(epic_issues: { epic_id: epics }) }
scope :not_in_epics, ->(epics) { left_outer_joins(:epic_issue).where('epic_issues.epic_id NOT IN (?) OR epic_issues.epic_id IS NULL', epics) }
scope :no_iteration, -> { where(sprint_id: nil) } scope :no_iteration, -> { where(sprint_id: nil) }
scope :any_iteration, -> { where.not(sprint_id: nil) } scope :any_iteration, -> { where.not(sprint_id: nil) }
scope :in_iterations, ->(iterations) { where(sprint_id: iterations) } scope :in_iterations, ->(iterations) { where(sprint_id: iterations) }
......
---
title: Fix issue filtering by negated epic parameter
merge_request: 44719
author:
type: fixed
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Filter issues by epic', :js do
include FilteredSearchHelpers
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
let_it_be(:issue1) { create(:issue, project: project) }
let_it_be(:issue2) { create(:issue, project: project) }
let_it_be(:issue3) { create(:issue, project: project) }
let_it_be(:issue4) { create(:issue, project: project) }
let_it_be(:epic1) { create(:epic) }
let_it_be(:epic2) { create(:epic) }
let_it_be(:epic_issue1) { create(:epic_issue, issue: issue1, epic: epic1) }
let_it_be(:epic_issue2) { create(:epic_issue, issue: issue2, epic: epic2) }
let_it_be(:epic_issue3) { create(:epic_issue, issue: issue3, epic: epic2) }
let(:js_dropdown) { '#js-dropdown-epic' }
before do
stub_licensed_features(epics: true)
stub_feature_flags(vue_issuables_list: false)
project.add_developer(user)
sign_in(user)
visit project_issues_path(project)
end
it 'filter issues by epic' do
input_filtered_search("epic:=&#{epic1.id}")
expect_issues_list_count(1)
end
it 'filter issues not in the epic' do
input_filtered_search("epic:!=&#{epic1.id}")
expect_issues_list_count(3)
end
end
...@@ -121,6 +121,14 @@ RSpec.describe IssuesFinder do ...@@ -121,6 +121,14 @@ RSpec.describe IssuesFinder do
expect(issues).to contain_exactly(issue_1, issue_2, issue_subepic) expect(issues).to contain_exactly(issue_1, issue_2, issue_subepic)
end end
end end
context 'filter issues not in the epic' do
let(:params) { { not: { epic_id: epic_1.id } } }
it 'returns issues not assigned to the epic' do
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue_2, issue_subepic)
end
end
end end
context 'filter by iteration' do context 'filter by iteration' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Issues::Create do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:assignee1) { create(:user) }
let_it_be(:assignee2) { create(:user) }
let(:expected_attributes) do
{
title: 'new title',
description: 'new description',
confidential: true,
due_date: Date.tomorrow,
discussion_locked: true,
weight: 10
}
end
let(:mutation_params) do
{
project_path: project.full_path,
assignee_ids: [assignee1.to_global_id, assignee2.to_global_id],
health_status: Issue.health_statuses[:at_risk]
}.merge(expected_attributes)
end
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutated_issue) { subject[:issue] }
specify { expect(described_class).to require_graphql_authorizations(:create_issue) }
describe '#resolve' do
before do
project.add_guest(assignee1)
project.add_guest(assignee2)
stub_licensed_features(issuable_health_status: true)
end
subject { mutation.resolve(mutation_params) }
context 'when user can create issues' do
before do
project.add_developer(user)
end
it 'creates issue with correct EE values' do
expect(mutated_issue).to have_attributes(expected_attributes)
expect(mutated_issue.assignees.pluck(:id)).to eq([assignee1.id, assignee2.id])
expect(mutated_issue.health_status).to eq('at_risk')
end
end
end
end
...@@ -16,7 +16,15 @@ RSpec.describe Mutations::Issues::Update do ...@@ -16,7 +16,15 @@ RSpec.describe Mutations::Issues::Update do
let_it_be(:epic) { create(:epic, group: group) } let_it_be(:epic) { create(:epic, group: group) }
let(:epic_id) { epic.to_global_id.to_s } let(:epic_id) { epic.to_global_id.to_s }
let(:params) { { project_path: project.full_path, iid: issue.iid, epic_id: epic_id } } let(:params) do
{
project_path: project.full_path,
iid: issue.iid,
epic_id: epic_id,
weight: 10
}
end
let(:mutated_issue) { subject[:issue] } let(:mutated_issue) { subject[:issue] }
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
...@@ -53,6 +61,7 @@ RSpec.describe Mutations::Issues::Update do ...@@ -53,6 +61,7 @@ RSpec.describe Mutations::Issues::Update do
it 'returns the updated issue' do it 'returns the updated issue' do
expect(mutated_issue.epic).to eq(epic) expect(mutated_issue.epic).to eq(epic)
expect(mutated_issue.weight).to eq(10)
end end
end end
......
...@@ -132,6 +132,13 @@ RSpec.describe Issue do ...@@ -132,6 +132,13 @@ RSpec.describe Issue do
end end
end end
describe '.not_in_epics' do
it 'returns only issues not in selected epics' do
expect(described_class.count).to eq 3
expect(described_class.not_in_epics([epic1])).to match_array([epic_issue2.issue, issue_no_epic])
end
end
describe '.distinct_epic_ids' do describe '.distinct_epic_ids' do
it 'returns distinct epic ids' do it 'returns distinct epic ids' do
expect(described_class.distinct_epic_ids.map(&:epic_id)).to match_array([epic1.id, epic2.id]) expect(described_class.distinct_epic_ids.map(&:epic_id)).to match_array([epic1.id, epic2.id])
......
...@@ -128,7 +128,7 @@ module API ...@@ -128,7 +128,7 @@ module API
pipeline = user_project.all_pipelines.find(params[:pipeline_id]) pipeline = user_project.all_pipelines.find(params[:pipeline_id])
if Feature.enabled?(:ci_jobs_finder_refactor) if Feature.enabled?(:ci_jobs_finder_refactor, default_enabled: true)
builds = ::Ci::JobsFinder builds = ::Ci::JobsFinder
.new(current_user: current_user, pipeline: pipeline, params: params) .new(current_user: current_user, pipeline: pipeline, params: params)
.execute .execute
...@@ -157,7 +157,7 @@ module API ...@@ -157,7 +157,7 @@ module API
pipeline = user_project.all_pipelines.find(params[:pipeline_id]) pipeline = user_project.all_pipelines.find(params[:pipeline_id])
if Feature.enabled?(:ci_jobs_finder_refactor) if Feature.enabled?(:ci_jobs_finder_refactor, default_enabled: true)
bridges = ::Ci::JobsFinder bridges = ::Ci::JobsFinder
.new(current_user: current_user, pipeline: pipeline, params: params, type: ::Ci::Bridge) .new(current_user: current_user, pipeline: pipeline, params: params, type: ::Ci::Bridge)
.execute .execute
......
...@@ -231,9 +231,6 @@ module API ...@@ -231,9 +231,6 @@ module API
authorize! :create_issue, user_project authorize! :create_issue, user_project
params.delete(:created_at) unless current_user.can?(:set_issue_created_at, user_project)
params.delete(:iid) unless current_user.can?(:set_issue_iid, user_project)
issue_params = declared_params(include_missing: false) issue_params = declared_params(include_missing: false)
issue_params[:system_note_timestamp] = params[:created_at] issue_params[:system_note_timestamp] = params[:created_at]
...@@ -279,8 +276,6 @@ module API ...@@ -279,8 +276,6 @@ module API
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid)) issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue authorize! :update_issue, issue
# Setting updated_at is allowed only for admins and owners
params.delete(:updated_at) unless current_user.can?(:set_issue_updated_at, user_project)
issue.system_note_timestamp = params[:updated_at] issue.system_note_timestamp = params[:updated_at]
update_params = declared_params(include_missing: false).merge(request: request, api: true) update_params = declared_params(include_missing: false).merge(request: request, api: true)
......
# see https://docs.gitlab.com/ce/ci/yaml/README.html for all available options # see https://docs.gitlab.com/ee/ci/yaml/README.html for all available options
# you can delete this line if you're not using Docker # you can delete this line if you're not using Docker
image: busybox:latest image: busybox:latest
......
# Based on openjdk:8, already includes lein # Based on openjdk:8, already includes lein
image: clojure:lein-2.7.0 image: clojure:lein-2.7.0
# If you need to configure a database, add a `services` section here # If you need to configure a database, add a `services` section here
# See https://docs.gitlab.com/ce/ci/services/postgres.html # See https://docs.gitlab.com/ee/ci/services/postgres.html
# Make sure you configure the connection as well # Make sure you configure the connection as well
before_script: before_script:
......
# Template project: https://gitlab.com/pages/jekyll # Template project: https://gitlab.com/pages/jekyll
# Docs: https://docs.gitlab.com/ce/pages/ # Docs: https://docs.gitlab.com/ee/pages/
image: ruby:2.6 image: ruby:2.6
variables: variables:
......
...@@ -44,7 +44,6 @@ module Gitlab ...@@ -44,7 +44,6 @@ module Gitlab
# Initialize gon.features with any flags that should be # Initialize gon.features with any flags that should be
# made globally available to the frontend # made globally available to the frontend
push_frontend_feature_flag(:monaco_blobs, default_enabled: true) push_frontend_feature_flag(:monaco_blobs, default_enabled: true)
push_frontend_feature_flag(:monaco_ci, default_enabled: true)
push_frontend_feature_flag(:webperf_experiment, default_enabled: false) push_frontend_feature_flag(:webperf_experiment, default_enabled: false)
push_frontend_feature_flag(:snippets_binary_blob, default_enabled: false) push_frontend_feature_flag(:snippets_binary_blob, default_enabled: false)
push_frontend_feature_flag(:usage_data_api, default_enabled: false) push_frontend_feature_flag(:usage_data_api, default_enabled: false)
......
...@@ -48,7 +48,7 @@ module Gitlab ...@@ -48,7 +48,7 @@ module Gitlab
*Documentation* *Documentation*
For more information about GitLab chatops, refer to its For more information about GitLab chatops, refer to its
documentation: https://docs.gitlab.com/ce/ci/chatops/README.html. documentation: https://docs.gitlab.com/ee/ci/chatops/README.html.
MESSAGE MESSAGE
message message
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
# Gitlab::VisibilityLevelChecker verifies that: # Gitlab::VisibilityLevelChecker verifies that:
# - Current @project.visibility_level is not restricted # - Current @project.visibility_level is not restricted
# - Override visibility param is not restricted # - Override visibility param is not restricted
# - @see https://docs.gitlab.com/ce/api/project_import_export.html#import-a-file # - @see https://docs.gitlab.com/ee/api/project_import_export.html#import-a-file
# #
# @param current_user [User] Current user object to verify visibility level against # @param current_user [User] Current user object to verify visibility level against
# @param project [Project] Current project that is being created/imported # @param project [Project] Current project that is being created/imported
......
...@@ -73,6 +73,10 @@ module QA ...@@ -73,6 +73,10 @@ module QA
element :project_path_content element :project_path_content
end end
view 'app/assets/javascripts/ide/components/commit_sidebar/message_field.vue' do
element :ide_commit_message_field
end
def has_file?(file_name) def has_file?(file_name)
within_element(:file_list) do within_element(:file_list) do
page.has_content? file_name page.has_content? file_name
...@@ -83,6 +87,10 @@ module QA ...@@ -83,6 +87,10 @@ module QA
has_element?(:project_path_content, project_path: project_path) has_element?(:project_path_content, project_path: project_path)
end end
def go_to_project
click_element(:project_path_content, Page::Project::Show)
end
def create_new_file_from_template(file_name, template) def create_new_file_from_template(file_name, template)
click_element(:new_file, Page::Component::WebIDE::Modal::CreateNewFile) click_element(:new_file, Page::Component::WebIDE::Modal::CreateNewFile)
...@@ -115,7 +123,7 @@ module QA ...@@ -115,7 +123,7 @@ module QA
find_element(:commit_sha_content).text find_element(:commit_sha_content).text
end end
def commit_changes(open_merge_request: false) def commit_changes(commit_message = nil, open_merge_request: false)
# Clicking :begin_commit_button switches from the # Clicking :begin_commit_button switches from the
# edit to the commit view # edit to the commit view
click_element(:begin_commit_button) click_element(:begin_commit_button)
...@@ -133,6 +141,10 @@ module QA ...@@ -133,6 +141,10 @@ module QA
has_element?(:commit_button) has_element?(:commit_button)
end end
if commit_message
fill_element(:ide_commit_message_field, commit_message)
end
if open_merge_request if open_merge_request
click_element(:commit_button, Page::MergeRequest::New) click_element(:commit_button, Page::MergeRequest::New)
else else
......
...@@ -27,11 +27,14 @@ module QA ...@@ -27,11 +27,14 @@ module QA
Page::Project::Show.perform(&:create_first_new_file!) Page::Project::Show.perform(&:create_first_new_file!)
Page::File::Form.perform do |form| Page::Project::WebIDE::Edit.perform do |ide|
form.add_name(@name) ide.add_file(@name, @content)
form.add_content(@content) ide.commit_changes(@commit_message)
form.add_commit_message(@commit_message) ide.go_to_project
form.commit_changes end
Page::Project::Show.perform do |project|
project.click_file(@name)
end end
end end
......
...@@ -18,7 +18,6 @@ module QA ...@@ -18,7 +18,6 @@ module QA
file.commit_message = commit_message_for_create file.commit_message = commit_message_for_create
end end
expect(page).to have_content('The file has been successfully created.')
expect(page).to have_content(file_name) expect(page).to have_content(file_name)
expect(page).to have_content(file_content) expect(page).to have_content(file_content)
expect(page).to have_content(commit_message_for_create) expect(page).to have_content(commit_message_for_create)
......
...@@ -10,7 +10,6 @@ module QA ...@@ -10,7 +10,6 @@ module QA
end end
end end
let(:web_ide_url) { current_url + '-/ide/project/' + project.path_with_namespace }
let(:file_name) { 'the very first file.txt' } let(:file_name) { 'the very first file.txt' }
before do before do
...@@ -18,10 +17,8 @@ module QA ...@@ -18,10 +17,8 @@ module QA
end end
it "creates the first file in an empty project via Web IDE", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/847' do it "creates the first file in an empty project via Web IDE", testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/847' do
# In the first iteration, the test opens Web IDE by modifying the URL to address past regressions. project.visit!
# Once the Web IDE button is introduced for empty projects, the test will be modified to go through UI. Page::Project::Show.perform(&:create_first_new_file!)
# See https://gitlab.com/gitlab-org/gitlab/-/issues/27915 and https://gitlab.com/gitlab-org/gitlab/-/issues/27535.
page.visit(web_ide_url)
Page::Project::WebIDE::Edit.perform do |ide| Page::Project::WebIDE::Edit.perform do |ide|
ide.create_first_file(file_name) ide.create_first_file(file_name)
......
# frozen_string_literal: true
module RuboCop
module Cop
module Graphql
class GIDExpectedType < RuboCop::Cop::Cop
MSG = 'Add an expected_type parameter to #object_from_id calls if possible.'
def_node_search :id_from_object?, <<~PATTERN
(send ... :object_from_id (...))
PATTERN
def on_send(node)
return unless id_from_object?(node)
add_offense(node)
end
end
end
end
end
...@@ -9,7 +9,7 @@ module RuboCop ...@@ -9,7 +9,7 @@ module RuboCop
include MigrationHelpers include MigrationHelpers
MSG = 'Creating a table with more than one foreign key at once violates our migration style guide. ' \ MSG = 'Creating a table with more than one foreign key at once violates our migration style guide. ' \
'For more details check the https://docs.gitlab.com/ce/development/migration_style_guide.html#examples' 'For more details check the https://docs.gitlab.com/ee/development/migration_style_guide.html#examples'
def_node_matcher :create_table_with_block?, <<~PATTERN def_node_matcher :create_table_with_block?, <<~PATTERN
(block (block
......
# Make sure to update the docs if this file moves. Docs URL: https://docs.gitlab.com/ce/development/migration_style_guide.html#when-to-use-the-helper-method # Make sure to update the docs if this file moves. Docs URL: https://docs.gitlab.com/ee/development/migration_style_guide.html#when-to-use-the-helper-method
Migration/UpdateLargeTable: Migration/UpdateLargeTable:
Enabled: true Enabled: true
HighTrafficTables: &high_traffic_tables # size in GB (>= 10 GB on GitLab.com as of 02/2020) and/or number of records HighTrafficTables: &high_traffic_tables # size in GB (>= 10 GB on GitLab.com as of 02/2020) and/or number of records
......
...@@ -3,27 +3,32 @@ ...@@ -3,27 +3,32 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Dashboard::LabelsController do RSpec.describe Dashboard::LabelsController do
let(:project) { create(:project) } let_it_be(:user) { create(:user) }
let(:user) { create(:user) } let_it_be(:project) { create(:project) }
let!(:label) { create(:label, project: project) } let_it_be(:project_2) { create(:project) }
let_it_be(:label) { create(:label, project: project, title: 'some_label') }
let_it_be(:label_with_same_title) { create(:label, project: project_2, title: 'some_label') }
let_it_be(:unrelated_label) { create(:label, project: create(:project, :public)) }
before_all do
project.add_reporter(user)
project_2.add_reporter(user)
end
before do before do
sign_in(user) sign_in(user)
project.add_reporter(user)
end end
describe "#index" do describe "#index" do
let!(:unrelated_label) { create(:label, project: create(:project, :public)) }
subject { get :index, format: :json } subject { get :index, format: :json }
it 'returns global labels for projects the user has a relationship with' do it 'returns labels with unique titles for projects the user has a relationship with' do
subject subject
expect(json_response).to be_kind_of(Array) expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(1) expect(json_response.size).to eq(1)
expect(json_response[0]["id"]).to be_nil expect(json_response[0]['title']).to eq(label.title)
expect(json_response[0]["title"]).to eq(label.title)
end end
it_behaves_like 'disabled when using an external authorization service' it_behaves_like 'disabled when using an external authorization service'
......
...@@ -11,7 +11,7 @@ RSpec.describe 'Database schema' do ...@@ -11,7 +11,7 @@ RSpec.describe 'Database schema' do
let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb } let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb }
# List of columns historically missing a FK, don't add more columns # List of columns historically missing a FK, don't add more columns
# See: https://docs.gitlab.com/ce/development/foreign_keys.html#naming-foreign-keys # See: https://docs.gitlab.com/ee/development/foreign_keys.html#naming-foreign-keys
IGNORED_FK_COLUMNS = { IGNORED_FK_COLUMNS = {
abuse_reports: %w[reporter_id user_id], abuse_reports: %w[reporter_id user_id],
application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_app_id eks_account_id eks_access_key_id], application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_app_id eks_account_id eks_access_key_id],
......
...@@ -250,7 +250,7 @@ RSpec.describe 'User comments on a diff', :js do ...@@ -250,7 +250,7 @@ RSpec.describe 'User comments on a diff', :js do
end end
context 'multiple suggestions in a single note' do context 'multiple suggestions in a single note' do
it 'suggestions are presented' do it 'suggestions are presented', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/258989' do
click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']"))
page.within('.js-discussion-note-form') do page.within('.js-discussion-note-form') do
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'User creates blob in new project', :js do RSpec.describe 'User creates new blob', :js do
include WebIdeSpecHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :empty_repo) } let(:project) { create(:project, :empty_repo) }
...@@ -12,16 +14,19 @@ RSpec.describe 'User creates blob in new project', :js do ...@@ -12,16 +14,19 @@ RSpec.describe 'User creates blob in new project', :js do
visit project_path(project) visit project_path(project)
end end
it 'allows the user to add a new file' do it 'allows the user to add a new file in Web IDE' do
click_link 'New file' click_link 'New file'
execute_script("monaco.editor.getModels()[0].setValue('Hello world')") wait_for_requests
ide_create_new_file('dummy-file', content: "Hello world\n")
fill_in(:file_name, with: 'dummy-file') ide_commit
click_button('Commit changes') click_button('Commit')
expect(page).to have_content('The file has been successfully created') expect(page).to have_content('All changes are committed')
expect(project.repository.blob_at('master', 'dummy-file').data).to eql("Hello world\n")
end end
end end
......
...@@ -8,117 +8,88 @@ RSpec.describe 'CI Lint', :js do ...@@ -8,117 +8,88 @@ RSpec.describe 'CI Lint', :js do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { create(:user) } let(:user) { create(:user) }
shared_examples 'correct ci linting process' do let(:content_selector) { '.content .view-lines' }
describe 'YAML parsing' do
shared_examples 'validates the YAML' do
before do
stub_feature_flags(ci_lint_vue: false)
click_on 'Validate'
end
context 'YAML is correct' do before do
let(:yaml_content) do stub_feature_flags(ci_lint_vue: false)
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml')) project.add_developer(user)
end sign_in(user)
it 'parses Yaml and displays the jobs' do visit project_ci_lint_path(project)
expect(page).to have_content('Status: syntax is correct') editor_set_value(yaml_content)
within "table" do wait_for('YAML content') do
aggregate_failures do find(content_selector).text.present?
expect(page).to have_content('Job - rspec') end
expect(page).to have_content('Job - spinach') end
expect(page).to have_content('Deploy Job - staging')
expect(page).to have_content('Deploy Job - production')
end
end
end
end
context 'YAML is incorrect' do describe 'YAML parsing' do
let(:yaml_content) { 'value: cannot have :' } shared_examples 'validates the YAML' do
before do
stub_feature_flags(ci_lint_vue: false)
click_on 'Validate'
end
it 'displays information about an error' do context 'YAML is correct' do
expect(page).to have_content('Status: syntax is incorrect') let(:yaml_content) do
expect(page).to have_selector(content_selector, text: yaml_content) File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
end
end end
end
it_behaves_like 'validates the YAML' it 'parses Yaml and displays the jobs' do
expect(page).to have_content('Status: syntax is correct')
context 'when Dry Run is checked' do within "table" do
before do aggregate_failures do
check 'Simulate a pipeline created for the default branch' expect(page).to have_content('Job - rspec')
expect(page).to have_content('Job - spinach')
expect(page).to have_content('Deploy Job - staging')
expect(page).to have_content('Deploy Job - production')
end
end
end end
it_behaves_like 'validates the YAML'
end end
describe 'YAML revalidate' do context 'YAML is incorrect' do
let(:yaml_content) { 'my yaml content' } let(:yaml_content) { 'value: cannot have :' }
it 'loads previous YAML content after validation' do it 'displays information about an error' do
expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea') expect(page).to have_content('Status: syntax is incorrect')
expect(page).to have_selector(content_selector, text: yaml_content)
end end
end end
end end
describe 'YAML clearing' do it_behaves_like 'validates the YAML'
context 'when Dry Run is checked' do
before do before do
click_on 'Clear' check 'Simulate a pipeline created for the default branch'
end end
context 'YAML is present' do it_behaves_like 'validates the YAML'
let(:yaml_content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
end
it 'YAML content is cleared' do
expect(page).to have_field('content', with: '', visible: false, type: 'textarea')
end
end
end end
end
context 'with ACE editor' do describe 'YAML revalidate' do
it_behaves_like 'correct ci linting process' do let(:yaml_content) { 'my yaml content' }
let(:content_selector) { '.ace_content' }
before do it 'loads previous YAML content after validation' do
stub_feature_flags(monaco_ci: false) expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea')
stub_feature_flags(ci_lint_vue: false)
project.add_developer(user)
sign_in(user)
visit project_ci_lint_path(project)
find('#ci-editor')
execute_script("ace.edit('ci-editor').setValue(#{yaml_content.to_json});")
# Ace editor updates a hidden textarea and it happens asynchronously
wait_for('YAML content') do
find(content_selector).text.present?
end
end end
end end
end end
context 'with Editor Lite' do describe 'YAML clearing' do
it_behaves_like 'correct ci linting process' do before do
let(:content_selector) { '.content .view-lines' } click_on 'Clear'
end
before do
stub_feature_flags(monaco_ci: true)
stub_feature_flags(ci_lint_vue: false)
project.add_developer(user)
sign_in(user)
visit project_ci_lint_path(project) context 'YAML is present' do
editor_set_value(yaml_content) let(:yaml_content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
end
wait_for('YAML content') do it 'YAML content is cleared' do
find(content_selector).text.present? expect(page).to have_field('content', with: '', visible: false, type: 'textarea')
end
end end
end end
end end
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Projects > Files > Project owner sees a link to create a license file in empty project', :js do RSpec.describe 'Projects > Files > Project owner sees a link to create a license file in empty project', :js do
include WebIdeSpecHelpers
let(:project) { create(:project_empty_repo) } let(:project) { create(:project_empty_repo) }
let(:project_maintainer) { project.owner } let(:project_maintainer) { project.owner }
...@@ -10,36 +12,35 @@ RSpec.describe 'Projects > Files > Project owner sees a link to create a license ...@@ -10,36 +12,35 @@ RSpec.describe 'Projects > Files > Project owner sees a link to create a license
sign_in(project_maintainer) sign_in(project_maintainer)
end end
it 'project maintainer creates a license file from a template' do it 'allows project maintainer creates a license file from a template in Web IDE' do
visit project_path(project) visit project_path(project)
click_on 'Add LICENSE' click_on 'Add LICENSE'
expect(page).to have_content('New file')
expect(current_path).to eq( expect(current_path).to eq("/-/ide/project/#{project.full_path}/edit/master/-/LICENSE")
project_new_blob_path(project, 'master'))
expect(find('#file_name').value).to eq('LICENSE') expect(page).to have_selector('.qa-file-templates-bar')
expect(page).to have_selector('.license-selector')
select_template('MIT License') select_template('MIT License')
file_content = first('.file-editor') expect(ide_editor_value).to have_content('MIT License')
expect(file_content).to have_content('MIT License') expect(ide_editor_value).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
expect(file_content).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
ide_commit
click_button('Commit')
expect(current_path).to eq("/-/ide/project/#{project.full_path}/tree/master/-/")
fill_in :commit_message, with: 'Add a LICENSE file', visible: true expect(page).to have_content('All changes are committed')
click_button 'Commit changes'
expect(current_path).to eq( license_file = project.repository.blob_at('master', 'LICENSE').data
project_blob_path(project, 'master/LICENSE')) expect(license_file).to have_content('MIT License')
expect(page).to have_content('MIT License') expect(license_file).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
expect(page).to have_content("Copyright (c) #{Time.now.year} #{project.namespace.human_name}")
end end
def select_template(template) def select_template(template)
page.within('.js-license-selector-wrap') do click_button 'Choose a template...'
click_button 'Apply a template' click_button template
click_link template wait_for_requests
wait_for_requests
end
end end
end end
...@@ -46,21 +46,21 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do ...@@ -46,21 +46,21 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project) visit project_path(project)
end end
it '"New file" button linked to new file page' do it '"New file" button linked to IDE new file page' do
page.within('.project-buttons') do page.within('.project-buttons') do
expect(page).to have_link('New file', href: project_new_blob_path(project, project.default_branch || 'master')) expect(page).to have_link('New file', href: presenter.ide_edit_path(project, project.default_branch || 'master'))
end end
end end
it '"Add README" button linked to new file populated for a README' do it '"Add README" button linked to IDE new file populated for a README' do
page.within('.project-buttons') do page.within('.project-buttons') do
expect(page).to have_link('Add README', href: presenter.add_readme_path) expect(page).to have_link('Add README', href: presenter.add_readme_ide_path)
end end
end end
it '"Add license" button linked to new file populated for a license' do it '"Add license" button linked to IDE new file populated for a license' do
page.within('.project-buttons') do page.within('.project-buttons') do
expect(page).to have_link('Add LICENSE', href: presenter.add_license_path) expect(page).to have_link('Add LICENSE', href: presenter.add_license_ide_path)
end end
end end
...@@ -74,9 +74,9 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do ...@@ -74,9 +74,9 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project) visit project_path(project)
end end
it '"New file" button linked to new file page' do it '"New file" button linked to IDE new file page' do
page.within('.project-buttons') do page.within('.project-buttons') do
expect(page).to have_link('New file', href: project_new_blob_path(project, 'example_branch')) expect(page).to have_link('New file', href: presenter.ide_edit_path(project, 'example_branch'))
end end
end end
end end
...@@ -144,7 +144,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do ...@@ -144,7 +144,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
expect(project.repository.readme).not_to be_nil expect(project.repository.readme).not_to be_nil
page.within('.project-buttons') do page.within('.project-buttons') do
expect(page).not_to have_link('Add README', href: presenter.add_readme_path) expect(page).not_to have_link('Add README', href: presenter.add_readme_ide_path)
expect(page).to have_link('README', href: presenter.readme_path) expect(page).to have_link('README', href: presenter.readme_path)
end end
end end
...@@ -164,7 +164,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do ...@@ -164,7 +164,7 @@ RSpec.describe 'Projects > Show > User sees setup shortcut buttons' do
end end
context 'when the project does not have a README' do context 'when the project does not have a README' do
it 'shows the "Add README" button' do it 'shows the single file editor "Add README" button' do
allow(project.repository).to receive(:readme).and_return(nil) allow(project.repository).to receive(:readme).and_return(nil)
visit project_path(project) visit project_path(project)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Developer views tags' do RSpec.describe 'Developer views tags' do
include RepoHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
...@@ -15,10 +17,13 @@ RSpec.describe 'Developer views tags' do ...@@ -15,10 +17,13 @@ RSpec.describe 'Developer views tags' do
let(:project) { create(:project_empty_repo, namespace: group) } let(:project) { create(:project_empty_repo, namespace: group) }
before do before do
visit project_path(project) project.repository.create_file(
click_on 'Add README' user,
fill_in :commit_message, with: 'Add a README file', visible: true 'README.md',
click_button 'Commit changes' 'Example readme',
message: 'Add README',
branch_name: 'master')
visit project_tags_path(project) visit project_tags_path(project)
end end
......
...@@ -94,7 +94,7 @@ exports[`Applications Prometheus application shows the correct description 1`] = ...@@ -94,7 +94,7 @@ exports[`Applications Prometheus application shows the correct description 1`] =
Prometheus is an open-source monitoring system with Prometheus is an open-source monitoring system with
<a <a
class="gl-link" class="gl-link"
href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html" href="https://docs.gitlab.com/ee/user/project/integrations/prometheus.html"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { mockTracking, triggerEvent } from 'helpers/tracking_helper'; import { mockTracking, triggerEvent } from 'helpers/tracking_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import IssuableLockForm from '~/sidebar/components/lock/issuable_lock_form.vue'; import IssuableLockForm from '~/sidebar/components/lock/issuable_lock_form.vue';
import EditForm from '~/sidebar/components/lock/edit_form.vue'; import EditForm from '~/sidebar/components/lock/edit_form.vue';
import createStore from '~/notes/stores'; import createStore from '~/notes/stores';
...@@ -19,6 +20,8 @@ describe('IssuableLockForm', () => { ...@@ -19,6 +20,8 @@ describe('IssuableLockForm', () => {
const findLockStatus = () => wrapper.find('[data-testid="lock-status"]'); const findLockStatus = () => wrapper.find('[data-testid="lock-status"]');
const findEditLink = () => wrapper.find('[data-testid="edit-link"]'); const findEditLink = () => wrapper.find('[data-testid="edit-link"]');
const findEditForm = () => wrapper.find(EditForm); const findEditForm = () => wrapper.find(EditForm);
const findSidebarLockStatusTooltip = () =>
getBinding(findSidebarCollapseIcon().element, 'gl-tooltip');
const initStore = isLocked => { const initStore = isLocked => {
if (issuableType === ISSUABLE_TYPE_ISSUE) { if (issuableType === ISSUABLE_TYPE_ISSUE) {
...@@ -37,6 +40,9 @@ describe('IssuableLockForm', () => { ...@@ -37,6 +40,9 @@ describe('IssuableLockForm', () => {
isEditable: true, isEditable: true,
...props, ...props,
}, },
directives: {
GlTooltip: createMockDirective(),
},
}); });
}; };
...@@ -125,6 +131,13 @@ describe('IssuableLockForm', () => { ...@@ -125,6 +131,13 @@ describe('IssuableLockForm', () => {
expect(findEditForm().exists()).toBe(true); expect(findEditForm().exists()).toBe(true);
}); });
}); });
it('renders a tooltip with the lock status text', () => {
const tooltip = findSidebarLockStatusTooltip();
expect(tooltip).toBeDefined();
expect(tooltip.value.title).toBe(isLocked ? 'Locked' : 'Unlocked');
});
}); });
}); });
}); });
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Mutations::Issues::Create do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:assignee1) { create(:user) }
let_it_be(:assignee2) { create(:user) }
let_it_be(:project_label1) { create(:label, project: project) }
let_it_be(:project_label2) { create(:label, project: project) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:new_label1) { FFaker::Lorem.word }
let_it_be(:new_label2) { new_label1 + 'Extra' }
let(:expected_attributes) do
{
title: 'new title',
description: 'new description',
confidential: true,
due_date: Date.tomorrow,
discussion_locked: true
}
end
let(:mutation_params) do
{
project_path: project.full_path,
milestone_id: milestone.to_global_id,
labels: [project_label1.title, project_label2.title, new_label1, new_label2],
assignee_ids: [assignee1.to_global_id, assignee2.to_global_id]
}.merge(expected_attributes)
end
let(:special_params) do
{
iid: non_existing_record_id,
created_at: 2.days.ago
}
end
let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
let(:mutated_issue) { subject[:issue] }
specify { expect(described_class).to require_graphql_authorizations(:create_issue) }
describe '#resolve' do
before do
stub_licensed_features(multiple_issue_assignees: false, issue_weights: false)
project.add_guest(assignee1)
project.add_guest(assignee2)
end
subject { mutation.resolve(mutation_params) }
context 'when the user does not have permission to create an issue' do
it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
context 'when the user can create an issue' do
context 'when creating an issue a developer' do
before do
project.add_developer(user)
end
it 'creates issue with correct values' do
expect(mutated_issue).to have_attributes(expected_attributes)
expect(mutated_issue.milestone_id).to eq(milestone.id)
expect(mutated_issue.labels.pluck(:title)).to eq([project_label1.title, project_label2.title, new_label1, new_label2])
expect(mutated_issue.assignees.pluck(:id)).to eq([assignee1.id])
end
context 'when passing in label_ids' do
before do
mutation_params.delete(:labels)
mutation_params.merge!(label_ids: [project_label1.to_global_id, project_label2.to_global_id])
end
it 'creates issue with correct values' do
expect(mutated_issue.labels.pluck(:title)).to eq([project_label1.title, project_label2.title])
end
end
context 'when trying to create issue with restricted params' do
before do
mutation_params.merge!(special_params)
end
it 'ignores the special params' do
expect(mutated_issue).not_to be_like_time(special_params[:created_at])
expect(mutated_issue.iid).not_to eq(special_params[:iid])
end
end
end
context 'when creating an issue as owner' do
let_it_be(:user) { project.owner }
before do
mutation_params.merge!(special_params)
end
it 'sets the special params' do
expect(mutated_issue.created_at).to be_like_time(special_params[:created_at])
expect(mutated_issue.iid).to eq(special_params[:iid])
end
end
end
end
describe "#ready?" do
context 'when passing in both labels and label_ids' do
before do
mutation_params.merge!(label_ids: [project_label1.to_global_id, project_label2.to_global_id])
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /one and only one of/)
end
end
context 'when passing only `discussion_to_resolve` param' do
before do
mutation_params.merge!(discussion_to_resolve: 'abc')
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }
.to raise_error(Gitlab::Graphql::Errors::ArgumentError, /to resolve a discussion please also provide `merge_request_to_resolve_discussions_of` parameter/)
end
end
context 'when passing only `merge_request_to_resolve_discussions_of` param' do
before do
mutation_params.merge!(merge_request_to_resolve_discussions_of: 'abc')
end
it 'raises exception when mutually exclusive params are given' do
expect { mutation.ready?(mutation_params) }.not_to raise_error
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Create an issue' do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:assignee1) { create(:user) }
let_it_be(:assignee2) { create(:user) }
let_it_be(:project_label1) { create(:label, project: project) }
let_it_be(:project_label2) { create(:label, project: project) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:new_label1) { FFaker::Lorem.word }
let_it_be(:new_label2) { FFaker::Lorem.word }
let(:input) do
{
'title' => 'new title',
'description' => 'new description',
'confidential' => true,
'dueDate' => Date.tomorrow.strftime('%Y-%m-%d')
}
end
let(:mutation) { graphql_mutation(:createIssue, input.merge('projectPath' => project.full_path, 'locked' => true)) }
let(:mutation_response) { graphql_mutation_response(:create_issue) }
context 'the user is not allowed to create an issue' do
it_behaves_like 'a mutation that returns a top-level access error'
end
context 'when user has permissions to create an issue' do
before do
project.add_developer(current_user)
end
it 'updates the issue' do
post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['issue']).to include(input)
expect(mutation_response['issue']).to include('discussionLocked' => true)
end
end
end
...@@ -10,13 +10,15 @@ RSpec.describe 'Update of an existing issue' do ...@@ -10,13 +10,15 @@ RSpec.describe 'Update of an existing issue' do
let_it_be(:issue) { create(:issue, project: project) } let_it_be(:issue) { create(:issue, project: project) }
let(:input) do let(:input) do
{ {
project_path: project.full_path, 'iid' => issue.iid.to_s,
iid: issue.iid.to_s, 'title' => 'new title',
locked: true 'description' => 'new description',
'confidential' => true,
'dueDate' => Date.tomorrow.strftime('%Y-%m-%d')
} }
end end
let(:mutation) { graphql_mutation(:update_issue, input) } let(:mutation) { graphql_mutation(:update_issue, input.merge(project_path: project.full_path, locked: true)) }
let(:mutation_response) { graphql_mutation_response(:update_issue) } let(:mutation_response) { graphql_mutation_response(:update_issue) }
context 'the user is not allowed to update issue' do context 'the user is not allowed to update issue' do
...@@ -32,9 +34,8 @@ RSpec.describe 'Update of an existing issue' do ...@@ -32,9 +34,8 @@ RSpec.describe 'Update of an existing issue' do
post_graphql_mutation(mutation, current_user: current_user) post_graphql_mutation(mutation, current_user: current_user)
expect(response).to have_gitlab_http_status(:success) expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['issue']).to include( expect(mutation_response['issue']).to include(input)
'discussionLocked' => true expect(mutation_response['issue']).to include('discussionLocked' => true)
)
end end
end end
end end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rubocop'
require_relative '../../../../rubocop/cop/graphql/gid_expected_type'
RSpec.describe RuboCop::Cop::Graphql::GIDExpectedType, type: :rubocop do
include CopHelper
subject(:cop) { described_class.new }
it 'adds an offense when there is no expected_type parameter' do
inspect_source(<<~TYPE)
GitlabSchema.object_from_id(received_id)
TYPE
expect(cop.offenses.size).to eq 1
end
it 'does not add an offense for calls that have an expected_type parameter' do
expect_no_offenses(<<~TYPE.strip)
GitlabSchema.object_from_id("some_id", expected_type: SomeClass)
TYPE
end
end
...@@ -83,7 +83,7 @@ RSpec.describe RuboCop::Cop::Migration::CreateTableWithForeignKeys, type: :ruboc ...@@ -83,7 +83,7 @@ RSpec.describe RuboCop::Cop::Migration::CreateTableWithForeignKeys, type: :ruboc
context 'with more than one foreign keys' do context 'with more than one foreign keys' do
let(:offense) do let(:offense) do
'Creating a table with more than one foreign key at once violates our migration style guide. ' \ 'Creating a table with more than one foreign key at once violates our migration style guide. ' \
'For more details check the https://docs.gitlab.com/ce/development/migration_style_guide.html#examples' 'For more details check the https://docs.gitlab.com/ee/development/migration_style_guide.html#examples'
end end
shared_examples 'target to high traffic table' do |dsl_method, table_name| shared_examples 'target to high traffic table' do |dsl_method, table_name|
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment