diff options
78 files changed, 1103 insertions, 344 deletions
diff --git a/.test_license_encryption_key.pub b/.test_license_encryption_key.pub new file mode 100644 index 00000000000..d3936e5e07e --- /dev/null +++ b/.test_license_encryption_key.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtgemxR8RUJXi3p7G/dkh +Yuln1L4lA6GtQsT83X0yTVDbLVsI2C6bepsRjGiLV0R/9JGvTojORx+9F/ZQAiEC +g6QXWasAOSmrzr4EjADG6cWcCnOju8hX9yib1HUIBxl+jHkmXP3NPuwyb8p2G149 +EG1o4apEqE5RtqV/Xyx/u57xTYYZShJ/c7o4iA8xvt6IAKFPFKpQwb5hv4KvUZBP +h0xG2qvOjDu430fK8JclPlXHqPjXDkXOZyLd4FvRStdEQU3RVXvUQfuGt/tOMS7J +nPQ94fr/xdaEbcEtIlr32+tcgsMWyhqtDCPUWJT1aRPviUgaJKLoVs8tRKwYMV9+ +1wIDAQAB +-----END PUBLIC KEY----- diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 16d25615779..061c9ffe8d4 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -1,4 +1,5 @@ <script> +import { GlBadge, GlTab, GlTabs } from '@gitlab/ui'; import environmentsMixin from '../mixins/environments_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import StopEnvironmentModal from '../components/stop_environment_modal.vue'; @@ -6,8 +7,11 @@ import DeleteEnvironmentModal from '../components/delete_environment_modal.vue'; export default { components: { - StopEnvironmentModal, DeleteEnvironmentModal, + GlBadge, + GlTab, + GlTabs, + StopEnvironmentModal, }, mixins: [environmentsMixin, CIPaginationMixin], @@ -73,9 +77,21 @@ export default { <b>{{ folderName }}</b> </h4> - <div class="top-area"> - <tabs v-if="!isLoading" :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" /> - </div> + <gl-tabs v-if="!isLoading" scope="environments" content-class="gl-display-none"> + <gl-tab + v-for="(tab, i) in tabs" + :key="`${tab.name}-${i}`" + :active="tab.isActive" + :title-item-class="tab.isActive ? 'gl-outline-none' : ''" + :title-link-attributes="{ 'data-testid': `environments-tab-${tab.scope}` }" + @click="onChangeTab(tab.scope)" + > + <template #title> + <span>{{ tab.name }}</span> + <gl-badge size="sm" class="gl-tab-counter-badge">{{ tab.count }}</gl-badge> + </template> + </gl-tab> + </gl-tabs> <container :is-loading="isLoading" diff --git a/app/assets/javascripts/jira_import/index.js b/app/assets/javascripts/jira_import/index.js index 695a237bf50..003f3c7107e 100644 --- a/app/assets/javascripts/jira_import/index.js +++ b/app/assets/javascripts/jira_import/index.js @@ -6,7 +6,7 @@ import App from './components/jira_import_app.vue'; Vue.use(VueApollo); -const defaultClient = createDefaultClient(); +const defaultClient = createDefaultClient({}, { assumeImmutableResults: true }); const apolloProvider = new VueApollo({ defaultClient, diff --git a/app/assets/javascripts/jira_import/queries/initiate_jira_import.mutation.graphql b/app/assets/javascripts/jira_import/queries/initiate_jira_import.mutation.graphql index 8fda8287988..807374bf06c 100644 --- a/app/assets/javascripts/jira_import/queries/initiate_jira_import.mutation.graphql +++ b/app/assets/javascripts/jira_import/queries/initiate_jira_import.mutation.graphql @@ -2,7 +2,6 @@ mutation($input: JiraImportStartInput!) { jiraImportStart(input: $input) { - clientMutationId jiraImport { ...JiraImport } diff --git a/app/assets/javascripts/jira_import/utils/cache_update.js b/app/assets/javascripts/jira_import/utils/cache_update.js index 6aaf2010866..01b851ca527 100644 --- a/app/assets/javascripts/jira_import/utils/cache_update.js +++ b/app/assets/javascripts/jira_import/utils/cache_update.js @@ -1,3 +1,4 @@ +import produce from 'immer'; import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql'; import { IMPORT_STATE } from './jira_import_utils'; @@ -13,22 +14,16 @@ export const addInProgressImportToStore = (store, jiraImportStart, fullPath) => }, }; - const cacheData = store.readQuery({ + const sourceData = store.readQuery({ ...queryDetails, }); store.writeQuery({ ...queryDetails, - data: { - project: { - ...cacheData.project, - jiraImportStatus: IMPORT_STATE.SCHEDULED, - jiraImports: { - ...cacheData.project.jiraImports, - nodes: cacheData.project.jiraImports.nodes.concat(jiraImportStart.jiraImport), - }, - }, - }, + data: produce(sourceData, draftData => { + draftData.project.jiraImportStatus = IMPORT_STATE.SCHEDULED; // eslint-disable-line no-param-reassign + draftData.project.jiraImports.nodes.push(jiraImportStart.jiraImport); + }), }); }; diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js index 701d36b71f2..d3acd1ef3d0 100644 --- a/app/assets/javascripts/pipelines/constants.js +++ b/app/assets/javascripts/pipelines/constants.js @@ -13,6 +13,8 @@ export const TestStatus = { FAILED: 'failed', SKIPPED: 'skipped', SUCCESS: 'success', + ERROR: 'error', + UNKNOWN: 'unknown', }; export const FETCH_AUTHOR_ERROR_MESSAGE = __('There was a problem fetching project users.'); diff --git a/app/assets/javascripts/pipelines/stores/test_reports/utils.js b/app/assets/javascripts/pipelines/stores/test_reports/utils.js index 8f1ac305cda..42406e5a67a 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/utils.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/utils.js @@ -1,13 +1,19 @@ import { __, sprintf } from '../../../locale'; +import { TestStatus } from '../../constants'; export function iconForTestStatus(status) { switch (status) { - case 'success': + case TestStatus.SUCCESS: return 'status_success_borderless'; - case 'failed': + case TestStatus.FAILED: return 'status_failed_borderless'; - default: + case TestStatus.ERROR: + return 'status_warning_borderless'; + case TestStatus.SKIPPED: return 'status_skipped_borderless'; + case TestStatus.UNKNOWN: + default: + return 'status_notfound_borderless'; } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index cf21c23cb17..52319d9658b 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -203,18 +203,6 @@ margin-right: 0; } } - - &:hover, - &:focus { - text-decoration: none; - outline: 0; - opacity: 1; - color: $white; - - &.header-user-dropdown-toggle .header-user-avatar { - border-color: $white; - } - } } .header-new-dropdown-toggle { diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index ec0755b1614..5623d38d66e 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -9,6 +9,7 @@ } } +.ci-status-icon-error, .ci-status-icon-failed { svg { fill: $red-500; diff --git a/app/controllers/concerns/show_inherited_labels_checker.rb b/app/controllers/concerns/show_inherited_labels_checker.rb new file mode 100644 index 00000000000..acbea37a62e --- /dev/null +++ b/app/controllers/concerns/show_inherited_labels_checker.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ShowInheritedLabelsChecker + extend ActiveSupport::Concern + + private + + def show_inherited_labels?(include_ancestor_groups) + Feature.enabled?(:show_inherited_labels, @project || @group) || include_ancestor_groups # rubocop:disable Gitlab/ModuleWithInstanceVariables + end +end diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index df0b71d2cf0..97d9f8fcecd 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -2,6 +2,7 @@ class Groups::LabelsController < Groups::ApplicationController include ToggleSubscriptionAction + include ShowInheritedLabelsChecker before_action :label, only: [:edit, :update, :destroy] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] @@ -12,8 +13,9 @@ class Groups::LabelsController < Groups::ApplicationController def index respond_to do |format| format.html do - @labels = GroupLabelsFinder - .new(current_user, @group, params.merge(sort: sort)).execute + # at group level we do not want to list project labels, + # we only want `only_group_labels = false` when pulling labels for label filter dropdowns, fetched through json + @labels = available_labels(params.merge(only_group_labels: true)).page(params[:page]) end format.json do render json: LabelSerializer.new.represent_appearance(available_labels) @@ -74,7 +76,7 @@ class Groups::LabelsController < Groups::ApplicationController end def label - @label ||= @group.labels.find(params[:id]) + @label ||= available_labels(params.merge(only_group_labels: true)).find(params[:id]) end alias_method :subscribable_resource, :label @@ -102,15 +104,17 @@ class Groups::LabelsController < Groups::ApplicationController session[:previous_labels_path] = URI(request.referer || '').path end - def available_labels + def available_labels(options = params) @available_labels ||= LabelsFinder.new( current_user, group_id: @group.id, - only_group_labels: params[:only_group_labels], - include_ancestor_groups: params[:include_ancestor_groups], - include_descendant_groups: params[:include_descendant_groups], - search: params[:search]).execute + only_group_labels: options[:only_group_labels], + include_ancestor_groups: show_inherited_labels?(params[:include_ancestor_groups]), + sort: sort, + subscribed: options[:subscribed], + include_descendant_groups: options[:include_descendant_groups], + search: options[:search]).execute end def sort diff --git a/app/controllers/import/manifest_controller.rb b/app/controllers/import/manifest_controller.rb index 9c47e6d4b0b..fdca6da95c5 100644 --- a/app/controllers/import/manifest_controller.rb +++ b/app/controllers/import/manifest_controller.rb @@ -26,8 +26,7 @@ class Import::ManifestController < Import::BaseController manifest = Gitlab::ManifestImport::Manifest.new(params[:manifest].tempfile) if manifest.valid? - session[:manifest_import_repositories] = manifest.projects - session[:manifest_import_group_id] = group.id + manifest_import_metadata.save(manifest.projects, group.id) redirect_to status_import_manifest_path else @@ -96,12 +95,16 @@ class Import::ManifestController < Import::BaseController # rubocop: disable CodeReuse/ActiveRecord def group - @group ||= Group.find_by(id: session[:manifest_import_group_id]) + @group ||= Group.find_by(id: manifest_import_metadata.group_id) end # rubocop: enable CodeReuse/ActiveRecord + def manifest_import_metadata + @manifest_import_status ||= Gitlab::ManifestImport::Metadata.new(current_user, fallback: session) + end + def repositories - @repositories ||= session[:manifest_import_repositories] + @repositories ||= manifest_import_metadata.repositories end def find_jobs diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index b7aeab8f5ff..ca2fad35451 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -2,6 +2,7 @@ class Projects::LabelsController < Projects::ApplicationController include ToggleSubscriptionAction + include ShowInheritedLabelsChecker before_action :check_issuables_available! before_action :label, only: [:edit, :update, :destroy, :promote] @@ -161,7 +162,7 @@ class Projects::LabelsController < Projects::ApplicationController @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id, - include_ancestor_groups: params[:include_ancestor_groups], + include_ancestor_groups: show_inherited_labels?(params[:include_ancestor_groups]), search: params[:search], subscribed: params[:subscribed], sort: sort).execute diff --git a/app/finders/group_labels_finder.rb b/app/finders/group_labels_finder.rb deleted file mode 100644 index a668a0f0fae..00000000000 --- a/app/finders/group_labels_finder.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -class GroupLabelsFinder - attr_reader :current_user, :group, :params - - def initialize(current_user, group, params = {}) - @current_user = current_user - @group = group - @params = params - end - - def execute - group.labels - .optionally_subscribed_by(subscriber_id) - .optionally_search(params[:search]) - .order_by(params[:sort]) - .page(params[:page]) - end - - private - - def subscriber_id - current_user&.id if subscribed? - end - - def subscribed? - params[:subscribed] == 'true' - end -end diff --git a/app/graphql/types/issue_sort_enum.rb b/app/graphql/types/issue_sort_enum.rb index e458d6e02c5..08762264b1b 100644 --- a/app/graphql/types/issue_sort_enum.rb +++ b/app/graphql/types/issue_sort_enum.rb @@ -8,6 +8,8 @@ module Types value 'DUE_DATE_ASC', 'Due date by ascending order', value: :due_date_asc value 'DUE_DATE_DESC', 'Due date by descending order', value: :due_date_desc value 'RELATIVE_POSITION_ASC', 'Relative position by ascending order', value: :relative_position_asc + value 'SEVERITY_ASC', 'Severity from less critical to more critical', value: :severity_asc + value 'SEVERITY_DESC', 'Severity from more critical to less critical', value: :severity_desc end end diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index d6253f74ce5..487508f448f 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -36,8 +36,7 @@ module Types end field :author, Types::UserType, null: false, - description: 'User that created the issue', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, obj.author_id).find } + description: 'User that created the issue' field :assignees, Types::UserType.connection_type, null: true, description: 'Assignees of the issue' @@ -45,16 +44,14 @@ module Types field :labels, Types::LabelType.connection_type, null: true, description: 'Labels of the issue' field :milestone, Types::MilestoneType, null: true, - description: 'Milestone of the issue', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, obj.milestone_id).find } + description: 'Milestone of the issue' field :due_date, Types::TimeType, null: true, description: 'Due date of the issue' field :confidential, GraphQL::BOOLEAN_TYPE, null: false, description: 'Indicates the issue is confidential' field :discussion_locked, GraphQL::BOOLEAN_TYPE, null: false, - description: 'Indicates discussion is locked on the issue', - resolve: -> (obj, _args, _ctx) { !!obj.discussion_locked } + description: 'Indicates discussion is locked on the issue' field :upvotes, GraphQL::INT_TYPE, null: false, description: 'Number of upvotes the issue has received' @@ -108,6 +105,18 @@ module Types field :severity, Types::IssuableSeverityEnum, null: true, description: 'Severity level of the incident' + + def author + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find + end + + def milestone + Gitlab::Graphql::Loaders::BatchModelLoader.new(Milestone, object.milestone_id).find + end + + def discussion_locked + !!object.discussion_locked + end end end diff --git a/app/graphql/types/project_member_type.rb b/app/graphql/types/project_member_type.rb index f08781238d0..01731531ae2 100644 --- a/app/graphql/types/project_member_type.rb +++ b/app/graphql/types/project_member_type.rb @@ -12,7 +12,10 @@ module Types authorize :read_project field :project, Types::ProjectType, null: true, - description: 'Project that User is a member of', - resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, obj.source_id).find } + description: 'Project that User is a member of' + + def project + Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.source_id).find + end end end diff --git a/app/graphql/types/snippet_type.rb b/app/graphql/types/snippet_type.rb index f29b513c26c..495c25c1776 100644 --- a/app/graphql/types/snippet_type.rb +++ b/app/graphql/types/snippet_type.rb @@ -24,16 +24,14 @@ module Types field :project, Types::ProjectType, description: 'The project the snippet is associated with', null: true, - authorize: :read_project, - resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, snippet.project_id).find } + authorize: :read_project # Author can be nil in some scenarios. For example, # when the admin setting restricted visibility # level is set to public field :author, Types::UserType, description: 'The owner of the snippet', - null: true, - resolve: -> (snippet, args, context) { Gitlab::Graphql::Loaders::BatchModelLoader.new(User, snippet.author_id).find } + null: true field :file_name, GraphQL::STRING_TYPE, description: 'File Name of the snippet', @@ -86,5 +84,13 @@ module Types null: true markdown_field :description_html, null: true, method: :description + + def author + Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find + end + + def project + Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find + end end end diff --git a/app/graphql/types/sort_enum.rb b/app/graphql/types/sort_enum.rb index 3245cb33e0d..f9fa8829c7d 100644 --- a/app/graphql/types/sort_enum.rb +++ b/app/graphql/types/sort_enum.rb @@ -5,9 +5,16 @@ module Types graphql_name 'Sort' description 'Common sort values' + # Deprecated, as we prefer uppercase enums + # https://gitlab.com/groups/gitlab-org/-/epics/1838 value 'updated_desc', 'Updated at descending order' value 'updated_asc', 'Updated at ascending order' value 'created_desc', 'Created at descending order' value 'created_asc', 'Created at ascending order' + + value 'UPDATED_DESC', 'Updated at descending order', value: :updated_desc + value 'UPDATED_ASC', 'Updated at ascending order', value: :updated_asc + value 'CREATED_DESC', 'Created at descending order', value: :created_desc + value 'CREATED_ASC', 'Created at ascending order', value: :created_asc end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 0dea6217276..fc1bf4e38d7 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SearchHelper - SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets, :state].freeze + SEARCH_PERMITTED_PARAMS = [:search, :scope, :project_id, :group_id, :repository_ref, :snippets, :state, :confidential].freeze def search_autocomplete_opts(term) return unless current_user diff --git a/app/models/issue.rb b/app/models/issue.rb index 5a5de371301..a9b821e7d21 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -101,6 +101,8 @@ class Issue < ApplicationRecord scope :order_relative_position_asc, -> { reorder(::Gitlab::Database.nulls_last_order('relative_position', 'ASC')) } scope :order_closed_date_desc, -> { reorder(closed_at: :desc) } scope :order_created_at_desc, -> { reorder(created_at: :desc) } + scope :order_severity_asc, -> { includes(:issuable_severity).order('issuable_severities.severity ASC NULLS FIRST') } + scope :order_severity_desc, -> { includes(:issuable_severity).order('issuable_severities.severity DESC NULLS LAST') } scope :preload_associated_models, -> { preload(:assignees, :labels, project: :namespace) } scope :with_web_entity_associations, -> { preload(:author, :project) } @@ -232,6 +234,8 @@ class Issue < ApplicationRecord when 'due_date', 'due_date_asc' then order_due_date_asc.with_order_id_desc when 'due_date_desc' then order_due_date_desc.with_order_id_desc when 'relative_position', 'relative_position_asc' then order_relative_position_asc.with_order_id_desc + when 'severity_asc' then order_severity_asc.with_order_id_desc + when 'severity_desc' then order_severity_desc.with_order_id_desc else super end diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index fab02697cf0..e4907bf0761 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -14,7 +14,7 @@ module Search Gitlab::SearchResults.new(current_user, params[:search], projects, - filters: { state: params[:state] }) + filters: { state: params[:state], confidential: params[:confidential] }) end def projects diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb index 68778aa2768..24409a04e74 100644 --- a/app/services/search/group_service.rb +++ b/app/services/search/group_service.rb @@ -16,7 +16,7 @@ module Search params[:search], projects, group: group, - filters: { state: params[:state] } + filters: { state: params[:state], confidential: params[:confidential] } ) end diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb index 5eba909c23b..9c18f8e947c 100644 --- a/app/services/search/project_service.rb +++ b/app/services/search/project_service.rb @@ -13,7 +13,8 @@ module Search params[:search], project: project, repository_ref: params[:repository_ref], - filters: { state: params[:state] }) + filters: { confidential: params[:confidential], state: params[:state] } + ) end def scope diff --git a/app/services/users/build_service.rb b/app/services/users/build_service.rb index 2fc46f033dd..e3f02bf85f0 100644 --- a/app/services/users/build_service.rb +++ b/app/services/users/build_service.rb @@ -104,7 +104,6 @@ module Users def build_user_params(skip_authorization:) if current_user&.admin? user_params = params.slice(*admin_create_params) - user_params[:created_by_id] = current_user&.id if params[:reset_password] user_params.merge!(force_random_password: true, password_expires_at: nil) @@ -125,6 +124,8 @@ module Users end end + user_params[:created_by_id] = current_user&.id + if user_default_internal_regex_enabled? && !user_params.key?(:external) user_params[:external] = user_external? end diff --git a/changelogs/unreleased/229534-incident-sorting-backend.yml b/changelogs/unreleased/229534-incident-sorting-backend.yml new file mode 100644 index 00000000000..0732f2868f9 --- /dev/null +++ b/changelogs/unreleased/229534-incident-sorting-backend.yml @@ -0,0 +1,5 @@ +--- +title: Add severity and published sorting for incident issues +merge_request: 42800 +author: +type: added diff --git a/changelogs/unreleased/230720-tabs-vue-migrate-app-assets-javascripts-environments-folder-enviro.yml b/changelogs/unreleased/230720-tabs-vue-migrate-app-assets-javascripts-environments-folder-enviro.yml new file mode 100644 index 00000000000..f27930f26e5 --- /dev/null +++ b/changelogs/unreleased/230720-tabs-vue-migrate-app-assets-javascripts-environments-folder-enviro.yml @@ -0,0 +1,5 @@ +--- +title: Migrate environments folder tabs to GlTabs +merge_request: 42894 +author: +type: changed diff --git a/changelogs/unreleased/249368-unit-test-report-test-cases-with-errored-status-show-skipped-icon.yml b/changelogs/unreleased/249368-unit-test-report-test-cases-with-errored-status-show-skipped-icon.yml new file mode 100644 index 00000000000..5767b241881 --- /dev/null +++ b/changelogs/unreleased/249368-unit-test-report-test-cases-with-errored-status-show-skipped-icon.yml @@ -0,0 +1,5 @@ +--- +title: 'Unit Test Report: Fix icon for errored status' +merge_request: 42540 +author: +type: fixed diff --git a/changelogs/unreleased/dblessing_user_created_by_id.yml b/changelogs/unreleased/dblessing_user_created_by_id.yml new file mode 100644 index 00000000000..686b1834136 --- /dev/null +++ b/changelogs/unreleased/dblessing_user_created_by_id.yml @@ -0,0 +1,5 @@ +--- +title: Always set created_by_id when creating a user +merge_request: 43342 +author: +type: changed diff --git a/config/feature_flags/development/migrate_bio_to_user_details.yml b/config/feature_flags/development/migrate_bio_to_user_details.yml deleted file mode 100644 index f54e45f9bd3..00000000000 --- a/config/feature_flags/development/migrate_bio_to_user_details.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: migrate_bio_to_user_details -introduced_by_url: -rollout_issue_url: -group: -type: development -default_enabled: true diff --git a/config/feature_flags/development/service_desk_custom_address.yml b/config/feature_flags/development/service_desk_custom_address.yml index 25cab0059c7..13a9ef6f37a 100644 --- a/config/feature_flags/development/service_desk_custom_address.yml +++ b/config/feature_flags/development/service_desk_custom_address.yml @@ -2,6 +2,6 @@ name: service_desk_custom_address introduced_by_url: rollout_issue_url: -group: +group: group::certify type: development default_enabled: false diff --git a/config/feature_flags/development/show_inherited_labels.yml b/config/feature_flags/development/show_inherited_labels.yml new file mode 100644 index 00000000000..3fb26d91227 --- /dev/null +++ b/config/feature_flags/development/show_inherited_labels.yml @@ -0,0 +1,7 @@ +--- +name: show_inherited_labels +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42960 +rollout_issue_url: +group: group::project management +type: development +default_enabled: false diff --git a/config/feature_flags/development/soft_email_confirmation.yml b/config/feature_flags/development/soft_email_confirmation.yml index d471cf442de..ee951d84ed8 100644 --- a/config/feature_flags/development/soft_email_confirmation.yml +++ b/config/feature_flags/development/soft_email_confirmation.yml @@ -1,7 +1,7 @@ --- name: soft_email_confirmation -introduced_by_url: -rollout_issue_url: -group: +introduced_by_url: https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/31245 +rollout_issue_url: +group: group::acquisition type: development default_enabled: false diff --git a/config/feature_flags/development/users_search.yml b/config/feature_flags/development/users_search.yml index 0397ee60225..932439af448 100644 --- a/config/feature_flags/development/users_search.yml +++ b/config/feature_flags/development/users_search.yml @@ -1,7 +1,7 @@ --- name: users_search introduced_by_url: -rollout_issue_url: -group: +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/255282 +group: group::global search type: development default_enabled: true diff --git a/config/initializers/0_license.rb b/config/initializers/0_license.rb index e7b46a14630..ce3103be2e4 100644 --- a/config/initializers/0_license.rb +++ b/config/initializers/0_license.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true Gitlab.ee do - public_key_file = File.read(Rails.root.join(".license_encryption_key.pub")) + prefix = ENV['GITLAB_LICENSE_MODE'] == 'test' ? 'test_' : '' + public_key_file = File.read(Rails.root.join(".#{prefix}license_encryption_key.pub")) public_key = OpenSSL::PKey::RSA.new(public_key_file) Gitlab::License.encryption_key = public_key rescue diff --git a/db/post_migrate/20200511083541_cleanup_projects_with_missing_namespace.rb b/db/post_migrate/20200511083541_cleanup_projects_with_missing_namespace.rb index 1ead10a4de6..9e606b2264b 100644 --- a/db/post_migrate/20200511083541_cleanup_projects_with_missing_namespace.rb +++ b/db/post_migrate/20200511083541_cleanup_projects_with_missing_namespace.rb @@ -66,8 +66,6 @@ class CleanupProjectsWithMissingNamespace < ActiveRecord::Migration[6.0] end def ensure_bio_is_assigned_to_user_details - return if Feature.disabled?(:migrate_bio_to_user_details, default_enabled: true) - user_detail.bio = bio.to_s[0...255] end diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index 2a79923b793..c9be41ff97e 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -117,9 +117,9 @@ For source installations the following settings are nested under `artifacts:` an |---------|-------------|---------| | `enabled` | Enable/disable object storage | `false` | | `remote_directory` | The bucket name where Artifacts will be stored| | -| `direct_upload` | Set to true to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` | -| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | -| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | +| `direct_upload` | Set to `true` to enable direct upload of Artifacts without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` | +| `background_upload` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | +| `proxy_download` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `connection` | Various connection options described below | | #### Connection settings @@ -318,7 +318,7 @@ _The uploads are stored by default in In order to migrate back to local storage: -1. Set both `direct_upload` and `background_upload` to false in `gitlab.rb`, under the artifacts object storage settings. +1. Set both `direct_upload` and `background_upload` to `false` in `gitlab.rb`, under the artifacts object storage settings. 1. [Reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure). 1. Run `gitlab-rake gitlab:artifacts:migrate_to_local`. 1. Disable object_storage for artifacts in `gitlab.rb`: diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md index 3f4cd6e2751..2dc0d4b167c 100644 --- a/doc/administration/merge_request_diffs.md +++ b/doc/administration/merge_request_diffs.md @@ -92,9 +92,9 @@ then `object_store:`. On Omnibus installations, they are prefixed by |---------|-------------|---------| | `enabled` | Enable/disable object storage | `false` | | `remote_directory` | The bucket name where external diffs will be stored| | -| `direct_upload` | Set to true to enable direct upload of external diffs without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` | -| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | -| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | +| `direct_upload` | Set to `true` to enable direct upload of external diffs without the need of local shared storage. Option may be removed once we decide to support only single storage for all files. | `false` | +| `background_upload` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 | `true` | +| `proxy_download` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `connection` | Various connection options described below | | ### S3 compatible connection settings diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md index 71a41719003..d9679971fa5 100644 --- a/doc/administration/uploads.md +++ b/doc/administration/uploads.md @@ -68,9 +68,9 @@ For source installations the following settings are nested under `uploads:` and |---------|-------------|---------| | `enabled` | Enable/disable object storage | `false` | | `remote_directory` | The bucket name where Uploads will be stored| | -| `direct_upload` | Set to true to remove Puma from the Upload path. Workhorse handles the actual Artifact Upload to Object Storage while Puma does minimal processing to keep track of the upload. There is no need for local shared storage. The option may be removed if support for a single storage type for all files is introduced. Read more on [direct upload](../development/uploads.md#direct-upload). | `false` | -| `background_upload` | Set to false to disable automatic upload. Option may be removed once upload is direct to S3 (if `direct_upload` is set to `true` it will override `background_upload`) | `true` | -| `proxy_download` | Set to true to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | +| `direct_upload` | Set to `true` to remove Puma from the Upload path. Workhorse handles the actual Artifact Upload to Object Storage while Puma does minimal processing to keep track of the upload. There is no need for local shared storage. The option may be removed if support for a single storage type for all files is introduced. Read more on [direct upload](../development/uploads.md#direct-upload). | `false` | +| `background_upload` | Set to `false` to disable automatic upload. Option may be removed once upload is direct to S3 (if `direct_upload` is set to `true` it will override `background_upload`) | `true` | +| `proxy_download` | Set to `true` to enable proxying all files served. Option allows to reduce egress traffic as this allows clients to download directly from remote storage instead of proxying all data | `false` | | `connection` | Various connection options described below | | ### Connection settings diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql index f876875c82c..1feb713d7f6 100644 --- a/doc/api/graphql/reference/gitlab_schema.graphql +++ b/doc/api/graphql/reference/gitlab_schema.graphql @@ -435,6 +435,16 @@ Values for sorting alerts """ enum AlertManagementAlertSort { """ + Created at ascending order + """ + CREATED_ASC + + """ + Created at descending order + """ + CREATED_DESC + + """ Created time by ascending order """ CREATED_TIME_ASC @@ -495,6 +505,16 @@ enum AlertManagementAlertSort { STATUS_DESC """ + Updated at ascending order + """ + UPDATED_ASC + + """ + Updated at descending order + """ + UPDATED_DESC + + """ Created time by ascending order """ UPDATED_TIME_ASC @@ -8713,6 +8733,16 @@ Values for sorting issues """ enum IssueSort { """ + Created at ascending order + """ + CREATED_ASC + + """ + Created at descending order + """ + CREATED_DESC + + """ Due date by ascending order """ DUE_DATE_ASC @@ -8753,11 +8783,41 @@ enum IssueSort { PRIORITY_DESC """ + Published issues shown last + """ + PUBLISHED_ASC + + """ + Published issues shown first + """ + PUBLISHED_DESC + + """ Relative position by ascending order """ RELATIVE_POSITION_ASC """ + Severity from less critical to more critical + """ + SEVERITY_ASC + + """ + Severity from more critical to less critical + """ + SEVERITY_DESC + + """ + Updated at ascending order + """ + UPDATED_ASC + + """ + Updated at descending order + """ + UPDATED_DESC + + """ Weight by ascending order """ WEIGHT_ASC @@ -10489,6 +10549,16 @@ Values for sorting merge requests """ enum MergeRequestSort { """ + Created at ascending order + """ + CREATED_ASC + + """ + Created at descending order + """ + CREATED_DESC + + """ Label priority by ascending order """ LABEL_PRIORITY_ASC @@ -10529,6 +10599,16 @@ enum MergeRequestSort { PRIORITY_DESC """ + Updated at ascending order + """ + UPDATED_ASC + + """ + Updated at descending order + """ + UPDATED_DESC + + """ Created at ascending order """ created_asc @@ -16764,6 +16844,26 @@ enum Sort { """ Created at ascending order """ + CREATED_ASC + + """ + Created at descending order + """ + CREATED_DESC + + """ + Updated at ascending order + """ + UPDATED_ASC + + """ + Updated at descending order + """ + UPDATED_DESC + + """ + Created at ascending order + """ created_asc """ diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index e27fc66a3d6..beb7de90b02 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -1249,6 +1249,30 @@ "deprecationReason": null }, { + "name": "UPDATED_DESC", + "description": "Updated at descending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UPDATED_ASC", + "description": "Updated at ascending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_DESC", + "description": "Created at descending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_ASC", + "description": "Created at ascending order", + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "STARTED_AT_ASC", "description": "Start time by ascending order", "isDeprecated": false, @@ -24109,6 +24133,30 @@ "deprecationReason": null }, { + "name": "UPDATED_DESC", + "description": "Updated at descending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UPDATED_ASC", + "description": "Updated at ascending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_DESC", + "description": "Created at descending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_ASC", + "description": "Created at ascending order", + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "PRIORITY_ASC", "description": "Priority by ascending order", "isDeprecated": false, @@ -24163,6 +24211,18 @@ "deprecationReason": null }, { + "name": "SEVERITY_ASC", + "description": "Severity from less critical to more critical", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SEVERITY_DESC", + "description": "Severity from more critical to less critical", + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "WEIGHT_ASC", "description": "Weight by ascending order", "isDeprecated": false, @@ -24173,6 +24233,18 @@ "description": "Weight by descending order", "isDeprecated": false, "deprecationReason": null + }, + { + "name": "PUBLISHED_ASC", + "description": "Published issues shown last", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "PUBLISHED_DESC", + "description": "Published issues shown first", + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -29155,6 +29227,30 @@ "deprecationReason": null }, { + "name": "UPDATED_DESC", + "description": "Updated at descending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UPDATED_ASC", + "description": "Updated at ascending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_DESC", + "description": "Created at descending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_ASC", + "description": "Created at ascending order", + "isDeprecated": false, + "deprecationReason": null + }, + { "name": "PRIORITY_ASC", "description": "Priority by ascending order", "isDeprecated": false, @@ -49085,6 +49181,30 @@ "description": "Created at ascending order", "isDeprecated": false, "deprecationReason": null + }, + { + "name": "UPDATED_DESC", + "description": "Updated at descending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UPDATED_ASC", + "description": "Updated at ascending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_DESC", + "description": "Created at descending order", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CREATED_ASC", + "description": "Created at ascending order", + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 0a18756a788..357e435cf24 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -2935,6 +2935,8 @@ Values for sorting alerts. | Value | Description | | ----- | ----------- | +| `CREATED_ASC` | Created at ascending order | +| `CREATED_DESC` | Created at descending order | | `CREATED_TIME_ASC` | Created time by ascending order | | `CREATED_TIME_DESC` | Created time by descending order | | `ENDED_AT_ASC` | End time by ascending order | @@ -2947,6 +2949,8 @@ Values for sorting alerts. | `STARTED_AT_DESC` | Start time by descending order | | `STATUS_ASC` | Status by order: Ignored > Resolved > Acknowledged > Triggered | | `STATUS_DESC` | Status by order: Triggered > Acknowledged > Resolved > Ignored | +| `UPDATED_ASC` | Updated at ascending order | +| `UPDATED_DESC` | Updated at descending order | | `UPDATED_TIME_ASC` | Created time by ascending order | | `UPDATED_TIME_DESC` | Created time by descending order | | `created_asc` | Created at ascending order | @@ -3170,6 +3174,8 @@ Values for sorting issues. | Value | Description | | ----- | ----------- | +| `CREATED_ASC` | Created at ascending order | +| `CREATED_DESC` | Created at descending order | | `DUE_DATE_ASC` | Due date by ascending order | | `DUE_DATE_DESC` | Due date by descending order | | `LABEL_PRIORITY_ASC` | Label priority by ascending order | @@ -3178,7 +3184,13 @@ Values for sorting issues. | `MILESTONE_DUE_DESC` | Milestone due date by descending order | | `PRIORITY_ASC` | Priority by ascending order | | `PRIORITY_DESC` | Priority by descending order | +| `PUBLISHED_ASC` | Published issues shown last | +| `PUBLISHED_DESC` | Published issues shown first | | `RELATIVE_POSITION_ASC` | Relative position by ascending order | +| `SEVERITY_ASC` | Severity from less critical to more critical | +| `SEVERITY_DESC` | Severity from more critical to less critical | +| `UPDATED_ASC` | Updated at ascending order | +| `UPDATED_DESC` | Updated at descending order | | `WEIGHT_ASC` | Weight by ascending order | | `WEIGHT_DESC` | Weight by descending order | | `created_asc` | Created at ascending order | @@ -3252,6 +3264,8 @@ Values for sorting merge requests. | Value | Description | | ----- | ----------- | +| `CREATED_ASC` | Created at ascending order | +| `CREATED_DESC` | Created at descending order | | `LABEL_PRIORITY_ASC` | Label priority by ascending order | | `LABEL_PRIORITY_DESC` | Label priority by descending order | | `MERGED_AT_ASC` | Merge time by ascending order | @@ -3260,6 +3274,8 @@ Values for sorting merge requests. | `MILESTONE_DUE_DESC` | Milestone due date by descending order | | `PRIORITY_ASC` | Priority by ascending order | | `PRIORITY_DESC` | Priority by descending order | +| `UPDATED_ASC` | Updated at ascending order | +| `UPDATED_DESC` | Updated at descending order | | `created_asc` | Created at ascending order | | `created_desc` | Created at descending order | | `updated_asc` | Updated at ascending order | @@ -3488,6 +3504,10 @@ Common sort values. | Value | Description | | ----- | ----------- | +| `CREATED_ASC` | Created at ascending order | +| `CREATED_DESC` | Created at descending order | +| `UPDATED_ASC` | Updated at ascending order | +| `UPDATED_DESC` | Updated at descending order | | `created_asc` | Created at ascending order | | `created_desc` | Created at descending order | | `updated_asc` | Updated at ascending order | diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md index ba59d467bc8..35a8226b4d8 100644 --- a/doc/api/namespaces.md +++ b/doc/api/namespaces.md @@ -87,6 +87,23 @@ the `plan` parameter associated with a namespace: ] ``` +Users on GitLab.com will also see a `max_seats_used` parameter. `max_seats_used` +is the highest number of users the group had. + +`max_seats_used` will be non-zero only for namespaces on paid plans. + +```json +[ + { + "id": 1, + "name": "user1", + "billable_members_count": 2, + "max_seats_used": 3, + ... + } +] +``` + NOTE: **Note:** Only group maintainers/owners are presented with `members_count_with_descendants`, as well as `plan` **(BRONZE ONLY)**. @@ -123,6 +140,7 @@ Example response: "web_url": "https://gitlab.example.com/groups/twitter", "members_count_with_descendants": 2, "billable_members_count": 2, + "max_seats_used": 0, "plan": "default", "trial_ends_on": null, "trial": false @@ -162,6 +180,7 @@ Example response: "web_url": "https://gitlab.example.com/groups/group1", "members_count_with_descendants": 2, "billable_members_count": 2, + "max_seats_used": 0, "plan": "default", "trial_ends_on": null, "trial": false @@ -188,6 +207,7 @@ Example response: "web_url": "https://gitlab.example.com/groups/group1", "members_count_with_descendants": 2, "billable_members_count": 2, + "max_seats_used": 0, "plan": "default", "trial_ends_on": null, "trial": false diff --git a/lib/api/entities/feature_flag.rb b/lib/api/entities/feature_flag.rb new file mode 100644 index 00000000000..82fdb20af00 --- /dev/null +++ b/lib/api/entities/feature_flag.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + expose :name + expose :description + expose :active + expose :version, if: :feature_flags_new_version_enabled + expose :created_at + expose :updated_at + expose :scopes, using: FeatureFlag::LegacyScope + expose :strategies, using: FeatureFlag::Strategy, if: :feature_flags_new_version_enabled + end + end +end diff --git a/lib/api/entities/feature_flag/detailed_legacy_scope.rb b/lib/api/entities/feature_flag/detailed_legacy_scope.rb new file mode 100644 index 00000000000..47078c1dfde --- /dev/null +++ b/lib/api/entities/feature_flag/detailed_legacy_scope.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class DetailedLegacyScope < LegacyScope + expose :name + end + end + end +end diff --git a/lib/api/entities/feature_flag/legacy_scope.rb b/lib/api/entities/feature_flag/legacy_scope.rb new file mode 100644 index 00000000000..7329f71c599 --- /dev/null +++ b/lib/api/entities/feature_flag/legacy_scope.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class LegacyScope < Grape::Entity + expose :id + expose :active + expose :environment_scope + expose :strategies + expose :created_at + expose :updated_at + end + end + end +end diff --git a/lib/api/entities/feature_flag/scope.rb b/lib/api/entities/feature_flag/scope.rb new file mode 100644 index 00000000000..906fe718257 --- /dev/null +++ b/lib/api/entities/feature_flag/scope.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class Scope < Grape::Entity + expose :id + expose :environment_scope + end + end + end +end diff --git a/lib/api/entities/feature_flag/strategy.rb b/lib/api/entities/feature_flag/strategy.rb new file mode 100644 index 00000000000..32699be0ee3 --- /dev/null +++ b/lib/api/entities/feature_flag/strategy.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class Strategy < Grape::Entity + expose :id + expose :name + expose :parameters + expose :scopes, using: FeatureFlag::Scope + end + end + end +end diff --git a/lib/api/entities/feature_flag/user_list.rb b/lib/api/entities/feature_flag/user_list.rb new file mode 100644 index 00000000000..bc8b12ea22e --- /dev/null +++ b/lib/api/entities/feature_flag/user_list.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module API + module Entities + class FeatureFlag < Grape::Entity + class UserList < Grape::Entity + include RequestAwareEntity + + expose :id + expose :iid + expose :project_id + expose :created_at + expose :updated_at + expose :name + expose :user_xids + + expose :path do |list| + project_feature_flags_user_list_path(list.project, list) + end + + expose :edit_path do |list| + edit_project_feature_flags_user_list_path(list.project, list) + end + end + end + end +end diff --git a/lib/gitlab/background_migration/migrate_users_bio_to_user_details.rb b/lib/gitlab/background_migration/migrate_users_bio_to_user_details.rb index ca64d13b118..bbe2164ae4e 100644 --- a/lib/gitlab/background_migration/migrate_users_bio_to_user_details.rb +++ b/lib/gitlab/background_migration/migrate_users_bio_to_user_details.rb @@ -13,8 +13,6 @@ module Gitlab end def perform(start_id, stop_id) - return if Feature.disabled?(:migrate_bio_to_user_details, default_enabled: true) - relation = User .select("id AS user_id", "substring(COALESCE(bio, '') from 1 for 255) AS bio") .where("(COALESCE(bio, '') IS DISTINCT FROM '')") diff --git a/lib/gitlab/graphql/markdown_field.rb b/lib/gitlab/graphql/markdown_field.rb index 7be6810f7ba..0b5bde8d8d9 100644 --- a/lib/gitlab/graphql/markdown_field.rb +++ b/lib/gitlab/graphql/markdown_field.rb @@ -12,13 +12,19 @@ module Gitlab end method_name = kwargs.delete(:method) || name.to_s.sub(/_html$/, '') - kwargs[:resolve] = Gitlab::Graphql::MarkdownField::Resolver.new(method_name.to_sym).proc + resolver_method = "#{name}_resolver".to_sym + kwargs[:resolver_method] = resolver_method kwargs[:description] ||= "The GitLab Flavored Markdown rendering of `#{method_name}`" # Adding complexity to rendered notes since that could cause queries. kwargs[:complexity] ||= 5 field name, GraphQL::STRING_TYPE, **kwargs + + define_method resolver_method do + # We need to `dup` the context so the MarkdownHelper doesn't modify it + ::MarkupHelper.markdown_field(object, method_name.to_sym, context.to_h.dup) + end end end end diff --git a/lib/gitlab/graphql/markdown_field/resolver.rb b/lib/gitlab/graphql/markdown_field/resolver.rb deleted file mode 100644 index 11a01b95ad1..00000000000 --- a/lib/gitlab/graphql/markdown_field/resolver.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module Graphql - module MarkdownField - class Resolver - attr_reader :method_name - - def initialize(method_name) - @method_name = method_name - end - - def proc - -> (object, _args, ctx) do - # We need to `dup` the context so the MarkdownHelper doesn't modify it - ::MarkupHelper.markdown_field(object, method_name, ctx.to_h.dup) - end - end - end - end - end -end diff --git a/lib/gitlab/manifest_import/metadata.rb b/lib/gitlab/manifest_import/metadata.rb new file mode 100644 index 00000000000..80dff075391 --- /dev/null +++ b/lib/gitlab/manifest_import/metadata.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +module Gitlab + module ManifestImport + class Metadata + EXPIRY_TIME = 1.week + + attr_reader :user, :fallback + + def initialize(user, fallback: {}) + @user = user + @fallback = fallback + end + + def save(repositories, group_id) + Gitlab::Redis::SharedState.with do |redis| + redis.multi do + redis.set(key_for('repositories'), Gitlab::Json.dump(repositories), ex: EXPIRY_TIME) + redis.set(key_for('group_id'), group_id, ex: EXPIRY_TIME) + end + end + end + + def repositories + redis_get('repositories').then do |repositories| + next unless repositories + + Gitlab::Json.parse(repositories).map(&:symbolize_keys) + end || fallback[:manifest_import_repositories] + end + + def group_id + redis_get('group_id')&.to_i || fallback[:manifest_import_group_id] + end + + private + + def key_for(field) + "manifest_import:metadata:user:#{user.id}:#{field}" + end + + def redis_get(field) + Gitlab::Redis::SharedState.with do |redis| + redis.get(key_for(field)) + end + end + end + end +end diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 06d8dca2f70..91c0f46750b 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -193,6 +193,10 @@ module Gitlab end params[:state] = filters[:state] if filters.key?(:state) + + if Feature.enabled?(:search_filter_by_confidential) && filters.key?(:confidential) && %w(yes no).include?(filters[:confidential]) + params[:confidential] = filters[:confidential] == 'yes' + end end end diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index 8a08b5b8849..33041f1af9f 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -9,6 +9,8 @@ RSpec.describe Groups::LabelsController do before do group.add_owner(user) + # by default FFs are enabled in specs so we turn it off + stub_feature_flags(show_inherited_labels: false) sign_in(user) end @@ -32,11 +34,41 @@ RSpec.describe Groups::LabelsController do subgroup.add_owner(user) end - it 'returns ancestor group labels' do - get :index, params: { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true }, format: :json + RSpec.shared_examples 'returns ancestor group labels' do + it 'returns ancestor group labels' do + get :index, params: params, format: :json - label_ids = json_response.map {|label| label['title']} - expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) + label_ids = json_response.map {|label| label['title']} + expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) + end + end + + context 'when include_ancestor_groups true' do + let(:params) { { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true } } + + it_behaves_like 'returns ancestor group labels' + end + + context 'when include_ancestor_groups false' do + let(:params) { { group_id: subgroup, only_group_labels: true } } + + it 'does not return ancestor group labels', :aggregate_failures do + get :index, params: params, format: :json + + label_ids = json_response.map {|label| label['title']} + expect(label_ids).to match_array([subgroup_label_1.title]) + expect(label_ids).not_to include([group_label_1.title]) + end + end + + context 'when show_inherited_labels enabled' do + let(:params) { { group_id: subgroup } } + + before do + stub_feature_flags(show_inherited_labels: true) + end + + it_behaves_like 'returns ancestor group labels' end end diff --git a/spec/controllers/import/manifest_controller_spec.rb b/spec/controllers/import/manifest_controller_spec.rb index ec8bd45b65c..6b21b45e698 100644 --- a/spec/controllers/import/manifest_controller_spec.rb +++ b/spec/controllers/import/manifest_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Import::ManifestController do +RSpec.describe Import::ManifestController, :clean_gitlab_redis_shared_state do include ImportSpecHelper let_it_be(:user) { create(:user) } @@ -16,42 +16,93 @@ RSpec.describe Import::ManifestController do sign_in(user) end - def assign_session_group - session[:manifest_import_repositories] = [] - session[:manifest_import_group_id] = group.id + describe 'POST upload' do + context 'with a valid manifest' do + it 'saves the manifest and redirects to the status page', :aggregate_failures do + post :upload, params: { + group_id: group.id, + manifest: fixture_file_upload('spec/fixtures/aosp_manifest.xml') + } + + metadata = Gitlab::ManifestImport::Metadata.new(user) + + expect(metadata.group_id).to eq(group.id) + expect(metadata.repositories.size).to eq(660) + expect(metadata.repositories.first).to include(name: 'platform/build', path: 'build/make') + + expect(response).to redirect_to(status_import_manifest_path) + end + end + + context 'with an invalid manifest' do + it 'displays an error' do + post :upload, params: { + group_id: group.id, + manifest: fixture_file_upload('spec/fixtures/invalid_manifest.xml') + } + + expect(assigns(:errors)).to be_present + end + end + + context 'when the user cannot create projects in the group' do + it 'displays an error' do + sign_in(create(:user)) + + post :upload, params: { + group_id: group.id, + manifest: fixture_file_upload('spec/fixtures/aosp_manifest.xml') + } + + expect(assigns(:errors)).to be_present + end + end end describe 'GET status' do - let(:repo1) { OpenStruct.new(id: 'test1', url: 'http://demo.host/test1') } - let(:repo2) { OpenStruct.new(id: 'test2', url: 'http://demo.host/test2') } + let(:repo1) { { id: 'test1', url: 'http://demo.host/test1' } } + let(:repo2) { { id: 'test2', url: 'http://demo.host/test2' } } let(:repos) { [repo1, repo2] } - before do - assign_session_group + shared_examples 'status action' do + it "returns variables for json request" do + project = create(:project, import_type: 'manifest', creator_id: user.id) - session[:manifest_import_repositories] = repos - end + get :status, format: :json - it "returns variables for json request" do - project = create(:project, import_type: 'manifest', creator_id: user.id) + expect(response).to have_gitlab_http_status(:ok) + expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id) + expect(json_response.dig("provider_repos", 0, "id")).to eq(repo1[:id]) + expect(json_response.dig("provider_repos", 1, "id")).to eq(repo2[:id]) + expect(json_response.dig("namespaces", 0, "id")).to eq(group.id) + end - get :status, format: :json + it "does not show already added project" do + project = create(:project, import_type: 'manifest', namespace: user.namespace, import_status: :finished, import_url: repo1[:url]) - expect(response).to have_gitlab_http_status(:ok) - expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id) - expect(json_response.dig("provider_repos", 0, "id")).to eq(repo1.id) - expect(json_response.dig("provider_repos", 1, "id")).to eq(repo2.id) - expect(json_response.dig("namespaces", 0, "id")).to eq(group.id) + get :status, format: :json + + expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id) + expect(json_response.dig("provider_repos").length).to eq(1) + expect(json_response.dig("provider_repos", 0, "id")).not_to eq(repo1[:id]) + end end - it "does not show already added project" do - project = create(:project, import_type: 'manifest', namespace: user.namespace, import_status: :finished, import_url: repo1.url) + context 'when the data is stored via Gitlab::ManifestImport::Metadata' do + before do + Gitlab::ManifestImport::Metadata.new(user).save(repos, group.id) + end + + include_examples 'status action' + end - get :status, format: :json + context 'when the data is stored in the user session' do + before do + session[:manifest_import_repositories] = repos + session[:manifest_import_group_id] = group.id + end - expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id) - expect(json_response.dig("provider_repos").length).to eq(1) - expect(json_response.dig("provider_repos", 0, "id")).not_to eq(repo1.id) + include_examples 'status action' end end end diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index f213d104747..8a3c55033cb 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -3,9 +3,9 @@ require 'spec_helper' RSpec.describe Projects::LabelsController do - let(:group) { create(:group) } - let(:project) { create(:project, namespace: group) } - let(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project, reload: true) { create(:project, namespace: group) } + let_it_be(:user) { create(:user) } before do project.add_maintainer(user) @@ -14,16 +14,21 @@ RSpec.describe Projects::LabelsController do end describe 'GET #index' do - let!(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') } - let!(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') } - let!(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') } - let!(:label_4) { create(:label, project: project, title: 'Label 4') } - let!(:label_5) { create(:label, project: project, title: 'Label 5') } - - let!(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1') } - let!(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') } - let!(:group_label_3) { create(:group_label, group: group, title: 'Group Label 3') } - let!(:group_label_4) { create(:group_label, group: group, title: 'Group Label 4') } + let_it_be(:label_1) { create(:label, project: project, priority: 1, title: 'Label 1') } + let_it_be(:label_2) { create(:label, project: project, priority: 3, title: 'Label 2') } + let_it_be(:label_3) { create(:label, project: project, priority: 1, title: 'Label 3') } + let_it_be(:label_4) { create(:label, project: project, title: 'Label 4') } + let_it_be(:label_5) { create(:label, project: project, title: 'Label 5') } + + let_it_be(:group_label_1) { create(:group_label, group: group, title: 'Group Label 1') } + let_it_be(:group_label_2) { create(:group_label, group: group, title: 'Group Label 2') } + let_it_be(:group_label_3) { create(:group_label, group: group, title: 'Group Label 3') } + let_it_be(:group_label_4) { create(:group_label, group: group, title: 'Group Label 4') } + + let_it_be(:group_labels) { [group_label_3, group_label_4]} + let_it_be(:project_labels) { [label_4, label_5]} + let_it_be(:group_priority_labels) { [group_label_1, group_label_2]} + let_it_be(:project_priority_labels) { [label_1, label_2, label_3]} before do create(:label_priority, project: project, label: group_label_1, priority: 3) @@ -68,6 +73,60 @@ RSpec.describe Projects::LabelsController do end end + context 'with subgroups' do + let_it_be(:subgroup) { create(:group, parent: group) } + let_it_be(:subgroup_label_1) { create(:group_label, group: subgroup, title: 'subgroup_label_1') } + let_it_be(:subgroup_label_2) { create(:group_label, group: subgroup, title: 'subgroup_label_2') } + + before do + project.update!(namespace: subgroup) + subgroup.add_owner(user) + create(:label_priority, project: project, label: subgroup_label_2, priority: 1) + end + + RSpec.shared_examples 'returns ancestor group labels' do + it 'returns ancestor group labels', :aggregate_failures do + get :index, params: params + + expect(assigns(:labels)).to match_array([subgroup_label_1] + group_labels + project_labels) + expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + group_priority_labels + project_priority_labels) + end + end + + context 'when show_inherited_labels disabled' do + before do + stub_feature_flags(show_inherited_labels: false) + end + + context 'when include_ancestor_groups false' do + let(:params) { { namespace_id: project.namespace.to_param, project_id: project } } + + it 'does not return ancestor group labels', :aggregate_failures do + get :index, params: params + + expect(assigns(:labels)).to match_array([subgroup_label_1] + project_labels) + expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + project_priority_labels) + end + end + + context 'when include_ancestor_groups true' do + let(:params) { { namespace_id: project.namespace.to_param, project_id: project, include_ancestor_groups: true } } + + it_behaves_like 'returns ancestor group labels' + end + end + + context 'when show_inherited_labels enabled' do + let(:params) { { namespace_id: project.namespace.to_param, project_id: project } } + + before do + stub_feature_flags(show_inherited_labels: true) + end + + it_behaves_like 'returns ancestor group labels' + end + end + def list_labels get :index, params: { namespace_id: project.namespace.to_param, project_id: project } end @@ -75,7 +134,7 @@ RSpec.describe Projects::LabelsController do describe 'POST #generate' do context 'personal project' do - let(:personal_project) { create(:project, namespace: user.namespace) } + let_it_be(:personal_project) { create(:project, namespace: user.namespace) } it 'creates labels' do post :generate, params: { namespace_id: personal_project.namespace.to_param, project_id: personal_project } @@ -116,8 +175,8 @@ RSpec.describe Projects::LabelsController do end describe 'POST #promote' do - let!(:promoted_label_name) { "Promoted Label" } - let!(:label_1) { create(:label, title: promoted_label_name, project: project) } + let_it_be(:promoted_label_name) { "Promoted Label" } + let_it_be(:label_1) { create(:label, title: promoted_label_name, project: project) } context 'not group reporters' do it 'denies access' do @@ -196,7 +255,7 @@ RSpec.describe Projects::LabelsController do end context 'when requesting a redirected path' do - let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } + let_it_be(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } it 'redirects to the canonical path' do get :index, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' } @@ -242,7 +301,7 @@ RSpec.describe Projects::LabelsController do end context 'when requesting a redirected path' do - let!(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } + let_it_be(:redirect_route) { project.redirect_routes.create(path: project.full_path + 'old') } it 'returns not found' do post :generate, params: { namespace_id: project.namespace, project_id: project.to_param + 'old' } diff --git a/spec/finders/group_labels_finder_spec.rb b/spec/finders/group_labels_finder_spec.rb deleted file mode 100644 index d65a8fb4fed..00000000000 --- a/spec/finders/group_labels_finder_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe GroupLabelsFinder, '#execute' do - let!(:group) { create(:group) } - let!(:user) { create(:user) } - let!(:label1) { create(:group_label, title: 'Foo', description: 'Lorem ipsum', group: group) } - let!(:label2) { create(:group_label, title: 'Bar', description: 'Fusce consequat', group: group) } - - it 'returns all group labels sorted by name if no params' do - result = described_class.new(user, group).execute - - expect(result.to_a).to match_array([label2, label1]) - end - - it 'returns all group labels sorted by name desc' do - result = described_class.new(user, group, sort: 'name_desc').execute - - expect(result.to_a).to match_array([label2, label1]) - end - - it 'returns group labels that match search' do - result = described_class.new(user, group, search: 'Foo').execute - - expect(result.to_a).to match_array([label1]) - end - - it 'returns group labels user subscribed to' do - label2.subscribe(user) - - result = described_class.new(user, group, subscribed: 'true').execute - - expect(result.to_a).to match_array([label2]) - end - - it 'returns second page of labels' do - result = described_class.new(user, group, page: '2').execute - - expect(result.to_a).to match_array([]) - end -end diff --git a/spec/fixtures/invalid_manifest.xml b/spec/fixtures/invalid_manifest.xml new file mode 100644 index 00000000000..5357329784c --- /dev/null +++ b/spec/fixtures/invalid_manifest.xml @@ -0,0 +1,4 @@ +<manifest> + <remote review="invalid-url" /> + <project name="platform/build"/> +</manifest> diff --git a/spec/frontend/environments/folder/environments_folder_view_spec.js b/spec/frontend/environments/folder/environments_folder_view_spec.js index f33c8de0094..14c710dd7ba 100644 --- a/spec/frontend/environments/folder/environments_folder_view_spec.js +++ b/spec/frontend/environments/folder/environments_folder_view_spec.js @@ -46,9 +46,10 @@ describe('Environments Folder View', () => { wrapper = mount(EnvironmentsFolderViewComponent, { propsData: mockData }); }; - const findEnvironmentsTabAvailable = () => wrapper.find('.js-environments-tab-available'); + const findEnvironmentsTabAvailable = () => + wrapper.find('[data-testid="environments-tab-available"]'); - const findEnvironmentsTabStopped = () => wrapper.find('.js-environments-tab-stopped'); + const findEnvironmentsTabStopped = () => wrapper.find('[data-testid="environments-tab-stopped"]'); beforeEach(() => { mock = new MockAdapter(axios); diff --git a/spec/frontend/pipelines/test_reports/mock_data.js b/spec/frontend/pipelines/test_reports/mock_data.js index 1d03f0b655f..872cb5c87be 100644 --- a/spec/frontend/pipelines/test_reports/mock_data.js +++ b/spec/frontend/pipelines/test_reports/mock_data.js @@ -9,4 +9,20 @@ export default [ status: TestStatus.SKIPPED, system_output: null, }, + { + classname: 'spec.test_spec', + execution_time: 0, + name: 'Test#error text', + stack_trace: null, + status: TestStatus.ERROR, + system_output: null, + }, + { + classname: 'spec.test_spec', + execution_time: 0, + name: 'Test#unknown text', + stack_trace: null, + status: TestStatus.UNKNOWN, + system_output: null, + }, ]; diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js index 2feb6aa5799..af2150be7a0 100644 --- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js +++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js @@ -61,18 +61,17 @@ describe('Test reports suite table', () => { expect(allCaseRows().length).toBe(testCases.length); }); - it('renders the correct icon for each status', () => { - const failedTest = testCases.findIndex(x => x.status === TestStatus.FAILED); - const skippedTest = testCases.findIndex(x => x.status === TestStatus.SKIPPED); - const successTest = testCases.findIndex(x => x.status === TestStatus.SUCCESS); + it.each([ + TestStatus.ERROR, + TestStatus.FAILED, + TestStatus.SKIPPED, + TestStatus.SUCCESS, + 'unknown', + ])('renders the correct icon for test case with %s status', status => { + const test = testCases.findIndex(x => x.status === status); + const row = findCaseRowAtIndex(test); - const failedRow = findCaseRowAtIndex(failedTest); - const skippedRow = findCaseRowAtIndex(skippedTest); - const successRow = findCaseRowAtIndex(successTest); - - expect(findIconForRow(failedRow, TestStatus.FAILED).exists()).toBe(true); - expect(findIconForRow(skippedRow, TestStatus.SKIPPED).exists()).toBe(true); - expect(findIconForRow(successRow, TestStatus.SUCCESS).exists()).toBe(true); + expect(findIconForRow(row, status).exists()).toBe(true); }); }); }); diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 2a91e4e2800..49686d5ad76 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -223,6 +223,21 @@ RSpec.describe Resolvers::IssuesResolver do expect(resolve_issues(sort: :milestone_due_desc).items).to eq([milestone_issue3, milestone_issue2, milestone_issue1]) end end + + context 'when sorting by severity' do + let_it_be(:project) { create(:project) } + let_it_be(:issue_high_severity) { create_issue_with_severity(project, severity: :high) } + let_it_be(:issue_low_severity) { create_issue_with_severity(project, severity: :low) } + let_it_be(:issue_no_severity) { create(:incident, project: project) } + + it 'sorts issues ascending' do + expect(resolve_issues(sort: :severity_asc)).to eq([issue_no_severity, issue_low_severity, issue_high_severity]) + end + + it 'sorts issues descending' do + expect(resolve_issues(sort: :severity_desc)).to eq([issue_high_severity, issue_low_severity, issue_no_severity]) + end + end end it 'returns issues user can see' do @@ -308,6 +323,13 @@ RSpec.describe Resolvers::IssuesResolver do expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8 end + def create_issue_with_severity(project, severity:) + issue = create(:incident, project: project) + create(:issuable_severity, issue: issue, severity: severity) + + issue + end + def resolve_issues(args = {}, context = { current_user: current_user }) resolve(described_class, obj: project, args: args, ctx: context) end diff --git a/spec/graphql/types/issue_sort_enum_spec.rb b/spec/graphql/types/issue_sort_enum_spec.rb index 9313d3aee84..4433709d193 100644 --- a/spec/graphql/types/issue_sort_enum_spec.rb +++ b/spec/graphql/types/issue_sort_enum_spec.rb @@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['IssueSort'] do it 'exposes all the existing issue sort values' do expect(described_class.values.keys).to include( - *%w[DUE_DATE_ASC DUE_DATE_DESC RELATIVE_POSITION_ASC] + *%w[DUE_DATE_ASC DUE_DATE_DESC RELATIVE_POSITION_ASC SEVERITY_ASC SEVERITY_DESC] ) end end diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index 62b1711ee57..276fa7952be 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -5,9 +5,11 @@ require 'spec_helper' RSpec.describe Banzai::Filter::MilestoneReferenceFilter do include FilterSpecHelper - let(:parent_group) { create(:group, :public) } - let(:group) { create(:group, :public, parent: parent_group) } - let(:project) { create(:project, :public, group: group) } + let_it_be(:parent_group) { create(:group, :public) } + let_it_be(:group) { create(:group, :public, parent: parent_group) } + let_it_be(:project) { create(:project, :public, group: group) } + let_it_be(:namespace) { create(:namespace) } + let_it_be(:another_project) { create(:project, :public, namespace: namespace) } it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) @@ -188,11 +190,9 @@ RSpec.describe Banzai::Filter::MilestoneReferenceFilter do end shared_examples 'cross-project / cross-namespace complete reference' do - let(:namespace) { create(:namespace) } - let(:another_project) { create(:project, :public, namespace: namespace) } - let(:milestone) { create(:milestone, project: another_project) } - let(:reference) { "#{another_project.full_path}%#{milestone.iid}" } - let!(:result) { reference_filter("See #{reference}") } + let_it_be(:milestone) { create(:milestone, project: another_project) } + let(:reference) { "#{another_project.full_path}%#{milestone.iid}" } + let!(:result) { reference_filter("See #{reference}") } it 'points to referenced project milestone page' do expect(result.css('a').first.attr('href')).to eq urls @@ -226,12 +226,10 @@ RSpec.describe Banzai::Filter::MilestoneReferenceFilter do end shared_examples 'cross-project / same-namespace complete reference' do - let(:namespace) { create(:namespace) } - let(:project) { create(:project, :public, namespace: namespace) } - let(:another_project) { create(:project, :public, namespace: namespace) } - let(:milestone) { create(:milestone, project: another_project) } - let(:reference) { "#{another_project.full_path}%#{milestone.iid}" } - let!(:result) { reference_filter("See #{reference}") } + let_it_be(:project) { create(:project, :public, namespace: namespace) } + let_it_be(:milestone) { create(:milestone, project: another_project) } + let(:reference) { "#{another_project.full_path}%#{milestone.iid}" } + let!(:result) { reference_filter("See #{reference}") } it 'points to referenced project milestone page' do expect(result.css('a').first.attr('href')).to eq urls @@ -265,12 +263,10 @@ RSpec.describe Banzai::Filter::MilestoneReferenceFilter do end shared_examples 'cross project shorthand reference' do - let(:namespace) { create(:namespace) } - let(:project) { create(:project, :public, namespace: namespace) } - let(:another_project) { create(:project, :public, namespace: namespace) } - let(:milestone) { create(:milestone, project: another_project) } - let(:reference) { "#{another_project.path}%#{milestone.iid}" } - let!(:result) { reference_filter("See #{reference}") } + let_it_be(:project) { create(:project, :public, namespace: namespace) } + let_it_be(:milestone) { create(:milestone, project: another_project) } + let(:reference) { "#{another_project.path}%#{milestone.iid}" } + let!(:result) { reference_filter("See #{reference}") } it 'points to referenced project milestone page' do expect(result.css('a').first.attr('href')).to eq urls @@ -439,13 +435,13 @@ RSpec.describe Banzai::Filter::MilestoneReferenceFilter do context 'when milestone is open' do context 'project milestones' do - let(:milestone) { create(:milestone, project: project) } + let_it_be_with_reload(:milestone) { create(:milestone, project: project) } include_context 'project milestones' end context 'group milestones' do - let(:milestone) { create(:milestone, group: group) } + let_it_be_with_reload(:milestone) { create(:milestone, group: group) } include_context 'group milestones' end @@ -453,13 +449,13 @@ RSpec.describe Banzai::Filter::MilestoneReferenceFilter do context 'when milestone is closed' do context 'project milestones' do - let(:milestone) { create(:milestone, :closed, project: project) } + let_it_be_with_reload(:milestone) { create(:milestone, :closed, project: project) } include_context 'project milestones' end context 'group milestones' do - let(:milestone) { create(:milestone, :closed, group: group) } + let_it_be_with_reload(:milestone) { create(:milestone, :closed, group: group) } include_context 'group milestones' end diff --git a/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb b/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb index db3cbe7ccdc..3cec5cb4c35 100644 --- a/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb +++ b/spec/lib/gitlab/background_migration/migrate_users_bio_to_user_details_spec.rb @@ -82,21 +82,4 @@ RSpec.describe Gitlab::BackgroundMigration::MigrateUsersBioToUserDetails, :migra expect(user_detail).to be_nil end - - context 'when `migrate_bio_to_user_details` feature flag is off' do - before do - stub_feature_flags(migrate_bio_to_user_details: false) - end - - it 'does nothing' do - already_existing_user_details = user_details.where(user_id: [ - user_has_different_details.id, - user_already_has_details.id - ]) - - subject - - expect(user_details.all).to match_array(already_existing_user_details) - end - end end diff --git a/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb b/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb deleted file mode 100644 index af604e1c7d5..00000000000 --- a/spec/lib/gitlab/graphql/markdown_field/resolver_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true -require 'spec_helper' - -RSpec.describe Gitlab::Graphql::MarkdownField::Resolver do - include Gitlab::Routing - let(:resolver) { described_class.new(:note) } - - describe '#proc' do - let(:project) { create(:project, :public) } - let(:issue) { create(:issue, project: project) } - let(:note) do - create(:note, - note: "Referencing #{issue.to_reference(full: true)}") - end - - it 'renders markdown correctly' do - expect(resolver.proc.call(note, {}, {})).to include(issue_path(issue)) - end - - context 'when the issue is not publicly accessible' do - let(:project) { create(:project, :private) } - - it 'hides the references from users that are not allowed to see the reference' do - expect(resolver.proc.call(note, {}, {})).not_to include(issue_path(issue)) - end - - it 'shows the reference to users that are allowed to see it' do - expect(resolver.proc.call(note, {}, { current_user: project.owner })) - .to include(issue_path(issue)) - end - end - end -end diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb index e3da925376e..82090f992eb 100644 --- a/spec/lib/gitlab/graphql/markdown_field_spec.rb +++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb @@ -2,6 +2,8 @@ require 'spec_helper' RSpec.describe Gitlab::Graphql::MarkdownField do + include Gitlab::Routing + describe '.markdown_field' do it 'creates the field with some default attributes' do field = class_with_markdown_field(:test_html, null: true, method: :hello).fields['testHtml'] @@ -13,7 +15,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do end context 'developer warnings' do - let(:expected_error) { /Only `method` is allowed to specify the markdown field/ } + let_it_be(:expected_error) { /Only `method` is allowed to specify the markdown field/ } it 'raises when passing a resolver' do expect { class_with_markdown_field(:test_html, null: true, resolver: 'not really') } @@ -27,30 +29,61 @@ RSpec.describe Gitlab::Graphql::MarkdownField do end context 'resolving markdown' do - let(:note) { build(:note, note: '# Markdown!') } - let(:thing_with_markdown) { double('markdown thing', object: note) } - let(:expected_markdown) { '<h1 data-sourcepos="1:1-1:11" dir="auto">Markdown!</h1>' } - let(:query_type) { GraphQL::ObjectType.new } - let(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)} - let(:context) { GraphQL::Query::Context.new(query: OpenStruct.new(schema: schema), values: nil, object: nil) } + let_it_be(:note) { build(:note, note: '# Markdown!') } + let_it_be(:expected_markdown) { '<h1 data-sourcepos="1:1-1:11" dir="auto">Markdown!</h1>' } + let_it_be(:query_type) { GraphQL::ObjectType.new } + let_it_be(:schema) { GraphQL::Schema.define(query: query_type, mutation: nil)} + let_it_be(:query) { GraphQL::Query.new(schema, document: nil, context: {}, variables: {}) } + let_it_be(:context) { GraphQL::Query::Context.new(query: query, values: {}, object: nil) } + + let(:type_class) { class_with_markdown_field(:note_html, null: false) } + let(:type_instance) { type_class.authorized_new(note, context) } + let(:field) { type_class.fields['noteHtml'] } it 'renders markdown from the same property as the field name without the `_html` suffix' do - field = class_with_markdown_field(:note_html, null: false).fields['noteHtml'] + expect(field.to_graphql.resolve(type_instance, {}, context)).to eq(expected_markdown) + end + + context 'when a `method` argument is passed' do + let(:type_class) { class_with_markdown_field(:test_html, null: false, method: :note) } + let(:field) { type_class.fields['testHtml'] } - expect(field.to_graphql.resolve(thing_with_markdown, {}, context)).to eq(expected_markdown) + it 'renders markdown from a specific property' do + expect(field.to_graphql.resolve(type_instance, {}, context)).to eq(expected_markdown) + end end - it 'renders markdown from a specific property when a `method` argument is passed' do - field = class_with_markdown_field(:test_html, null: false, method: :note).fields['testHtml'] + describe 'basic verification that references work' do + let_it_be(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:note) { build(:note, note: "Referencing #{issue.to_reference(full: true)}") } + + it 'renders markdown correctly' do + expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue)) + end + + context 'when the issue is not publicly accessible' do + let_it_be(:project) { create(:project, :private) } + + it 'hides the references from users that are not allowed to see the reference' do + expect(field.to_graphql.resolve(type_instance, {}, context)).not_to include(issue_path(issue)) + end + + it 'shows the reference to users that are allowed to see it' do + context = GraphQL::Query::Context.new(query: query, values: { current_user: project.owner }, object: nil) + type_instance = type_class.authorized_new(note, context) - expect(field.to_graphql.resolve(thing_with_markdown, {}, context)).to eq(expected_markdown) + expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue)) + end + end end end end def class_with_markdown_field(name, **args) - Class.new(GraphQL::Schema::Object) do + Class.new(Types::BaseObject) do prepend Gitlab::Graphql::MarkdownField + graphql_name 'MarkdownFieldTest' markdown_field name, **args end diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb index 045c922783a..009f66d2108 100644 --- a/spec/lib/gitlab/group_search_results_spec.rb +++ b/spec/lib/gitlab/group_search_results_spec.rb @@ -17,10 +17,17 @@ RSpec.describe Gitlab::GroupSearchResults do describe 'issues search' do let_it_be(:opened_result) { create(:issue, :opened, project: project, title: 'foo opened') } let_it_be(:closed_result) { create(:issue, :closed, project: project, title: 'foo closed') } + let_it_be(:confidential_result) { create(:issue, :confidential, project: project, title: 'foo confidential') } + let(:query) { 'foo' } let(:scope) { 'issues' } + before do + project.add_developer(user) + end + include_examples 'search results filtered by state' + include_examples 'search results filtered by confidential' end describe 'merge_requests search' do diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb index 2e8753b0880..352120c079d 100644 --- a/spec/lib/gitlab/manifest_import/manifest_spec.rb +++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb @@ -12,19 +12,7 @@ RSpec.describe Gitlab::ManifestImport::Manifest do end context 'missing or invalid attributes' do - let(:file) { Tempfile.new('foo') } - - before do - content = <<~EOS - <manifest> - <remote review="invalid-url" /> - <project name="platform/build"/> - </manifest> - EOS - - file.write(content) - file.rewind - end + let(:file) { File.open(Rails.root.join('spec/fixtures/invalid_manifest.xml')) } it { expect(manifest.valid?).to be false } diff --git a/spec/lib/gitlab/manifest_import/metadata_spec.rb b/spec/lib/gitlab/manifest_import/metadata_spec.rb new file mode 100644 index 00000000000..c8158d3e148 --- /dev/null +++ b/spec/lib/gitlab/manifest_import/metadata_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::ManifestImport::Metadata, :clean_gitlab_redis_shared_state do + let(:user) { double(id: 1) } + let(:repositories) do + [ + { id: 'test1', url: 'http://demo.host/test1' }, + { id: 'test2', url: 'http://demo.host/test2' } + ] + end + + describe '#save' do + it 'stores data in Redis with an expiry of EXPIRY_TIME' do + status = described_class.new(user) + repositories_key = 'manifest_import:metadata:user:1:repositories' + group_id_key = 'manifest_import:metadata:user:1:group_id' + + status.save(repositories, 2) + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.ttl(repositories_key)).to be_within(5).of(described_class::EXPIRY_TIME) + expect(redis.ttl(group_id_key)).to be_within(5).of(described_class::EXPIRY_TIME) + end + end + end + + describe '#repositories' do + it 'allows repositories to round-trip with symbol keys' do + status = described_class.new(user) + + status.save(repositories, 2) + + expect(status.repositories).to eq(repositories) + end + + it 'uses the fallback when there is nothing in Redis' do + fallback = { manifest_import_repositories: repositories } + status = described_class.new(user, fallback: fallback) + + expect(status.repositories).to eq(repositories) + end + end + + describe '#group_id' do + it 'returns the group ID as an integer' do + status = described_class.new(user) + + status.save(repositories, 2) + + expect(status.group_id).to eq(2) + end + + it 'uses the fallback when there is nothing in Redis' do + fallback = { manifest_import_group_id: 3 } + status = described_class.new(user, fallback: fallback) + + expect(status.group_id).to eq(3) + end + end +end diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index fe0735b8043..a76ad1f6f4c 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -265,9 +265,15 @@ RSpec.describe Gitlab::ProjectSearchResults do let_it_be(:project) { create(:project, :public) } let_it_be(:closed_result) { create(:issue, :closed, project: project, title: 'foo closed') } let_it_be(:opened_result) { create(:issue, :opened, project: project, title: 'foo opened') } + let_it_be(:confidential_result) { create(:issue, :confidential, project: project, title: 'foo confidential') } let(:query) { 'foo' } + before do + project.add_developer(user) + end + include_examples 'search results filtered by state' + include_examples 'search results filtered by confidential' end end diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index b4cf6a568b4..e41bd86a84c 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -181,8 +181,10 @@ RSpec.describe Gitlab::SearchResults do let_it_be(:closed_result) { create(:issue, :closed, project: project, title: 'foo closed') } let_it_be(:opened_result) { create(:issue, :opened, project: project, title: 'foo open') } + let_it_be(:confidential_result) { create(:issue, :confidential, project: project, title: 'foo confidential') } include_examples 'search results filtered by state' + include_examples 'search results filtered by confidential' end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 3cbffa7cb86..338d705c189 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -145,6 +145,24 @@ RSpec.describe Issue do end end + describe '.order_severity' do + let_it_be(:issue_high_severity) { create(:issuable_severity, severity: :high).issue } + let_it_be(:issue_low_severity) { create(:issuable_severity, severity: :low).issue } + let_it_be(:issue_no_severity) { create(:incident) } + + context 'sorting ascending' do + subject { described_class.order_severity_asc } + + it { is_expected.to eq([issue_no_severity, issue_low_severity, issue_high_severity]) } + end + + context 'sorting descending' do + subject { described_class.order_severity_desc } + + it { is_expected.to eq([issue_high_severity, issue_low_severity, issue_no_severity]) } + end + end + describe '#order_by_position_and_priority' do let(:project) { reusable_project } let(:p1) { create(:label, title: 'P1', project: project, priority: 1) } diff --git a/spec/services/resource_access_tokens/create_service_spec.rb b/spec/services/resource_access_tokens/create_service_spec.rb index 7dbd55a6909..b4215ee63a1 100644 --- a/spec/services/resource_access_tokens/create_service_spec.rb +++ b/spec/services/resource_access_tokens/create_service_spec.rb @@ -53,6 +53,7 @@ RSpec.describe ResourceAccessTokens::CreateService do access_token = response.payload[:access_token] expect(access_token.user.reload.user_type).to eq("#{resource_type}_bot") + expect(access_token.user.created_by_id).to eq(user.id) end context 'email confirmation status' do diff --git a/spec/services/users/build_service_spec.rb b/spec/services/users/build_service_spec.rb index c14fdb35bfa..b445b5b81b1 100644 --- a/spec/services/users/build_service_spec.rb +++ b/spec/services/users/build_service_spec.rb @@ -16,6 +16,10 @@ RSpec.describe Users::BuildService do expect(service.execute).to be_valid end + it 'sets the created_by_id' do + expect(service.execute.created_by_id).to eq(admin_user.id) + end + context 'calls the UpdateCanonicalEmailService' do specify do expect(Users::UpdateCanonicalEmailService).to receive(:new).and_call_original @@ -128,6 +132,16 @@ RSpec.describe Users::BuildService do it 'raises AccessDeniedError exception' do expect { service.execute }.to raise_error Gitlab::Access::AccessDeniedError end + + context 'when authorization is skipped' do + subject(:built_user) { service.execute(skip_authorization: true) } + + it { is_expected.to be_valid } + + it 'sets the created_by_id' do + expect(built_user.created_by_id).to eq(user.id) + end + end end context 'with nil user' do diff --git a/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb new file mode 100644 index 00000000000..6c8ab38413d --- /dev/null +++ b/spec/support/shared_examples/lib/gitlab/search_confidential_filter_shared_examples.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'search results filtered by confidential' do + context 'filter not provided (all behavior)' do + let(:filters) { {} } + + context 'when Feature search_filter_by_confidential enabled' do + it 'returns confidential and not confidential results', :aggregate_failures do + expect(results.objects('issues')).to include confidential_result + expect(results.objects('issues')).to include opened_result + end + end + + context 'when Feature search_filter_by_confidential not enabled' do + before do + stub_feature_flags(search_filter_by_confidential: false) + end + + it 'returns confidential and not confidential results', :aggregate_failures do + expect(results.objects('issues')).to include confidential_result + expect(results.objects('issues')).to include opened_result + end + end + end + + context 'confidential filter' do + let(:filters) { { confidential: 'yes' } } + + context 'when Feature search_filter_by_confidential enabled' do + it 'returns only confidential results', :aggregate_failures do + expect(results.objects('issues')).to include confidential_result + expect(results.objects('issues')).not_to include opened_result + end + end + + context 'when Feature search_filter_by_confidential not enabled' do + before do + stub_feature_flags(search_filter_by_confidential: false) + end + + it 'returns confidential and not confidential results', :aggregate_failures do + expect(results.objects('issues')).to include confidential_result + expect(results.objects('issues')).to include opened_result + end + end + end + + context 'not confidential filter' do + let(:filters) { { confidential: 'no' } } + + context 'when Feature search_filter_by_confidential enabled' do + it 'returns not confidential results', :aggregate_failures do + expect(results.objects('issues')).not_to include confidential_result + expect(results.objects('issues')).to include opened_result + end + end + + context 'when Feature search_filter_by_confidential not enabled' do + before do + stub_feature_flags(search_filter_by_confidential: false) + end + + it 'returns confidential and not confidential results', :aggregate_failures do + expect(results.objects('issues')).to include confidential_result + expect(results.objects('issues')).to include opened_result + end + end + end + + context 'unsupported filter' do + let(:filters) { { confidential: 'goodbye' } } + + context 'when Feature search_filter_by_confidential enabled' do + it 'returns confidential and not confidential results', :aggregate_failures do + expect(results.objects('issues')).to include confidential_result + expect(results.objects('issues')).to include opened_result + end + end + + context 'when Feature search_filter_by_confidential not enabled' do + before do + stub_feature_flags(search_filter_by_confidential: false) + end + + it 'returns confidential and not confidential results', :aggregate_failures do + expect(results.objects('issues')).to include confidential_result + expect(results.objects('issues')).to include opened_result + end + end + end +end diff --git a/spec/support/shared_examples/lib/gitlab/search_issue_state_filter_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/search_state_filter_shared_examples.rb index e80ec516407..e80ec516407 100644 --- a/spec/support/shared_examples/lib/gitlab/search_issue_state_filter_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/search_state_filter_shared_examples.rb |