diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-27 18:08:31 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-27 18:08:31 +0300 |
commit | e77cb2465fa34b8883987206a42648603d5bf073 (patch) | |
tree | f2bbff57b35c6a0c97309056a795e446a3e5b139 | |
parent | e9de373448d0ae4dd330c131db2366a30e0c025d (diff) |
Add latest changes from gitlab-org/gitlab@master
72 files changed, 793 insertions, 186 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index cf569bff2f4..18c713ea09a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -978,4 +978,5 @@ Search/NamespacedClass: - 'scripts/**/*' - 'spec/migrations/**/*.rb' - 'app/experiments/**/*_experiment.rb' - - 'ee/app/experiments/**/*_experiment.rb'
\ No newline at end of file + - 'ee/app/experiments/**/*_experiment.rb' + - 'lib/gitlab/instrumentation/**/*.rb'
\ No newline at end of file diff --git a/.rubocop_todo/search/namespaced_class.yml b/.rubocop_todo/search/namespaced_class.yml index ab91b78722f..b87520d022f 100644 --- a/.rubocop_todo/search/namespaced_class.yml +++ b/.rubocop_todo/search/namespaced_class.yml @@ -195,9 +195,6 @@ Search/NamespacedClass: - 'lib/gitlab/empty_search_results.rb' - 'lib/gitlab/github_import/clients/search_repos.rb' - 'lib/gitlab/group_search_results.rb' - - 'lib/gitlab/instrumentation/elasticsearch_transport.rb' - - 'lib/gitlab/instrumentation/global_search_api.rb' - - 'lib/gitlab/instrumentation/zoekt.rb' - 'lib/gitlab/metrics/elasticsearch_rack_middleware.rb' - 'lib/gitlab/metrics/global_search_slis.rb' - 'lib/gitlab/project_search_results.rb' diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue index 53a93bbce30..9db3011ba5d 100644 --- a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue +++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue @@ -75,7 +75,12 @@ export default { if (this.hasMultipleCommits) { if (this.graphql) { const { lastDeployment } = this.environment; - return this.commitData(lastDeployment, 'commitPath'); + return ( + // data shape comming from REST and GraphQL is unfortunately different + // once we fully migrate to GraphQL it could be streamlined + this.commitData(lastDeployment, 'commitPath') || + this.commitData(lastDeployment, 'webUrl') + ); } const { last_deployment } = this.environment; @@ -135,7 +140,6 @@ export default { csrf, cancelProps: { text: __('Cancel'), - attributes: { variant: 'danger' }, }, docsPath: helpPagePath('ci/environments/index.md', { anchor: 'retry-or-roll-back-a-deployment' }), }; @@ -157,7 +161,7 @@ export default { }}</gl-link> </template> <template #docs="{ content }"> - <gl-link :href="$options.docsLink" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.docsPath" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </gl-modal> diff --git a/app/assets/javascripts/environments/environment_details/components/deployment_actions.vue b/app/assets/javascripts/environments/environment_details/components/deployment_actions.vue index 77d9311743c..94804f3eb6e 100644 --- a/app/assets/javascripts/environments/environment_details/components/deployment_actions.vue +++ b/app/assets/javascripts/environments/environment_details/components/deployment_actions.vue @@ -1,10 +1,18 @@ <script> +import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui'; +import { translations } from '~/environments/environment_details/constants'; import ActionsComponent from '~/environments/components/environment_actions.vue'; +import setEnvironmentToRollback from '~/environments/graphql/mutations/set_environment_to_rollback.mutation.graphql'; export default { components: { + GlButton, ActionsComponent, }, + directives: { + GlModal: GlModalDirective, + GlTooltip: GlTooltipDirective, + }, props: { actions: { // actions shape: @@ -18,14 +26,63 @@ export default { type: Array, required: true, }, + rollback: { + // rollback shape: + /* + { + id: string, + name: string, + lastDeployment: { + commit: Commit, + isLast: boolean, + }, + retryUrl: url, + }; + */ + type: Object, + required: false, + default: null, + }, }, computed: { + isRollbackAvailable() { + return Boolean(this.rollback?.lastDeployment); + }, + rollbackIcon() { + return this.rollback.lastDeployment.isLast ? 'repeat' : 'redo'; + }, isActionsShown() { return this.actions.length > 0; }, + rollbackButtonTitle() { + return this.rollback.lastDeployment?.isLast + ? translations.redeployButtonTitle + : translations.rollbackButtonTitle; + }, + }, + methods: { + onRollbackClick() { + this.$apollo.mutate({ + mutation: setEnvironmentToRollback, + variables: { + environment: this.rollback, + }, + }); + }, }, }; </script> <template> - <actions-component v-if="isActionsShown" :actions="actions" graphql /> + <div> + <actions-component v-if="isActionsShown" :actions="actions" graphql /> + <gl-button + v-if="isRollbackAvailable" + v-gl-modal.confirm-rollback-modal + v-gl-tooltip + data-testid="rollback-button" + :title="rollbackButtonTitle" + :icon="rollbackIcon" + @click="onRollbackClick" + /> + </div> </template> diff --git a/app/assets/javascripts/environments/environment_details/constants.js b/app/assets/javascripts/environments/environment_details/constants.js index 3b33d6a676e..d3683613d8c 100644 --- a/app/assets/javascripts/environments/environment_details/constants.js +++ b/app/assets/javascripts/environments/environment_details/constants.js @@ -61,6 +61,8 @@ export const translations = { ), nextPageButtonLabel: __('Next'), previousPageButtonLabel: __('Prev'), + redeployButtonTitle: s__('Environments|Re-deploy to environment'), + rollbackButtonTitle: s__('Environments|Rollback environment'), }; export const codeBlockPlaceholders = { code: ['code_open', 'code_close'] }; diff --git a/app/assets/javascripts/environments/environment_details/deployments_table.vue b/app/assets/javascripts/environments/environment_details/deployments_table.vue index 10f8c06e581..430fe8d9ece 100644 --- a/app/assets/javascripts/environments/environment_details/deployments_table.vue +++ b/app/assets/javascripts/environments/environment_details/deployments_table.vue @@ -54,7 +54,7 @@ export default { <time-ago-tooltip :time="item.deployed" /> </template> <template #cell(actions)="{ item }"> - <deployment-actions :actions="item.actions" /> + <deployment-actions :actions="item.actions" :rollback="item.rollback" /> </template> </gl-table-lite> </template> diff --git a/app/assets/javascripts/environments/environment_details/index.vue b/app/assets/javascripts/environments/environment_details/index.vue index f4657c5100a..f91e68e793f 100644 --- a/app/assets/javascripts/environments/environment_details/index.vue +++ b/app/assets/javascripts/environments/environment_details/index.vue @@ -1,7 +1,9 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; import { logError } from '~/lib/logger'; +import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue'; import environmentDetailsQuery from '../graphql/queries/environment_details.query.graphql'; +import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql'; import { convertToDeploymentTableRow } from '../helpers/deployment_data_transformation_helper'; import EmptyState from './empty_state.vue'; import DeploymentsTable from './deployments_table.vue'; @@ -10,6 +12,7 @@ import { ENVIRONMENT_DETAILS_PAGE_SIZE } from './constants'; export default { components: { + ConfirmRollbackModal, Pagination, DeploymentsTable, EmptyState, @@ -49,10 +52,14 @@ export default { }; }, }, + environmentToRollback: { + query: environmentToRollbackQuery, + }, }, data() { return { project: {}, + environmentToRollback: {}, isInitialPageDataReceived: false, isPrefetchingPages: false, }; @@ -143,5 +150,6 @@ export default { <pagination :page-info="pageInfo" :disabled="isPaginationDisabled" /> </div> <empty-state v-if="!isDeploymentTableShown && !isLoading" /> + <confirm-rollback-modal :environment="environmentToRollback" graphql /> </div> </template> diff --git a/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js b/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js index 9802dcbcf78..63ccb568794 100644 --- a/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js +++ b/app/assets/javascripts/environments/helpers/deployment_data_transformation_helper.js @@ -62,6 +62,25 @@ export const getActionsFromDeploymentNode = (deploymentNode, lastDeploymentName) ); }; +export const getRollbackActionFromDeploymentNode = (deploymentNode, environment) => { + const { job, id } = deploymentNode; + + if (!job) { + return null; + } + const isLastDeployment = id === environment.lastDeployment?.id; + const { webPath } = job; + return { + id, + name: environment.name, + lastDeployment: { + commit: deploymentNode.commit, + isLast: isLastDeployment, + }, + retryUrl: `${webPath}/retry`, + }; +}; + /** * This function transforms deploymentNode object coming from GraphQL to object compatible with app/assets/javascripts/environments/environment_details/page.vue table * @param {Object} deploymentNode @@ -82,5 +101,6 @@ export const convertToDeploymentTableRow = (deploymentNode, environment) => { created: deploymentNode.createdAt || '', deployed: deploymentNode.finishedAt || '', actions: getActionsFromDeploymentNode(deploymentNode, lastDeployment?.job?.name), + rollback: getRollbackActionFromDeploymentNode(deploymentNode, environment), }; }; diff --git a/app/assets/javascripts/issues/index.js b/app/assets/javascripts/issues/index.js index 83387d3ac29..3599a2660cc 100644 --- a/app/assets/javascripts/issues/index.js +++ b/app/assets/javascripts/issues/index.js @@ -8,7 +8,7 @@ import { initIssuableHeaderWarnings, initIssuableSidebar } from '~/issuable'; import IssuableTemplateSelectors from '~/issuable/issuable_template_selectors'; import { TYPE_INCIDENT } from '~/issues/constants'; import Issue from '~/issues/issue'; -import { initTitleSuggestions, initTypePopover } from '~/issues/new'; +import { initTitleSuggestions, initTypePopover, initTypeSelect } from '~/issues/new'; import { initRelatedMergeRequests } from '~/issues/related_merge_requests'; import { initRelatedIssues } from '~/related_issues'; import { @@ -47,6 +47,7 @@ export function initForm() { initTitleSuggestions(); initTypePopover(); + initTypeSelect(); mountMilestoneDropdown(); } diff --git a/app/assets/javascripts/issues/new/components/type_select.vue b/app/assets/javascripts/issues/new/components/type_select.vue new file mode 100644 index 00000000000..81c3a769d26 --- /dev/null +++ b/app/assets/javascripts/issues/new/components/type_select.vue @@ -0,0 +1,113 @@ +<script> +import { GlCollapsibleListbox, GlIcon } from '@gitlab/ui'; +import { TYPE_ISSUE, TYPE_INCIDENT } from '~/issues/constants'; +import { visitUrl } from '~/lib/utils/url_utility'; +import Tracking from '~/tracking'; +import { __ } from '~/locale'; + +export default { + i18n: { + selectType: __('Select type'), + issuableType: { + [TYPE_ISSUE]: __('Issue'), + [TYPE_INCIDENT]: __('Incident'), + }, + }, + components: { + GlCollapsibleListbox, + GlIcon, + }, + mixins: [Tracking.mixin()], + props: { + selectedType: { + required: false, + default: '', + type: String, + }, + isIssueAllowed: { + required: false, + default: false, + type: Boolean, + }, + isIncidentAllowed: { + required: false, + default: false, + type: Boolean, + }, + issuePath: { + required: false, + default: '', + type: String, + }, + incidentPath: { + required: false, + default: '', + type: String, + }, + }, + data() { + return { + selected: this.selectedType, + }; + }, + computed: { + toggleText() { + return this.selectedType + ? this.$options.i18n.issuableType[this.selectedType] + : this.$options.i18n.selectType; + }, + dropdownItems() { + const issueItem = this.isIssueAllowed + ? { + value: TYPE_ISSUE, + text: __('Issue'), + icon: 'issue-type-issue', + href: this.issuePath, + } + : null; + const incidentItem = this.isIncidentAllowed + ? { + value: TYPE_INCIDENT, + text: __('Incident'), + icon: 'issue-type-incident', + href: this.incidentPath, + tracking: { + action: 'select_issue_type_incident', + label: 'select_issue_type_incident_dropdown_option', + }, + } + : null; + + return [issueItem, incidentItem].filter(Boolean); + }, + }, + methods: { + selectType(type) { + const selectedItem = this.dropdownItems.find((item) => item.value === type); + if (selectedItem.tracking) { + const { action, label } = selectedItem.tracking; + this.track(action, { label }); + } + + visitUrl(selectedItem.href); + }, + }, +}; +</script> + +<template> + <gl-collapsible-listbox + v-model="selected" + :header-text="$options.i18n.selectType" + :toggle-text="toggleText" + :items="dropdownItems" + block + class="js-issuable-type-filter-dropdown-wrap" + @select="selectType" + > + <template #list-item="{ item }"> + <gl-icon :name="item.icon" :size="16" /> + {{ item.text }} + </template> + </gl-collapsible-listbox> +</template> diff --git a/app/assets/javascripts/issues/new/index.js b/app/assets/javascripts/issues/new/index.js index 91599502996..84a170e6564 100644 --- a/app/assets/javascripts/issues/new/index.js +++ b/app/assets/javascripts/issues/new/index.js @@ -1,8 +1,10 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import { parseBoolean } from '~/lib/utils/common_utils'; import TitleSuggestions from './components/title_suggestions.vue'; import TypePopover from './components/type_popover.vue'; +import TypeSelect from './components/type_select.vue'; export function initTitleSuggestions() { const el = document.getElementById('js-suggestions'); @@ -56,3 +58,28 @@ export function initTypePopover() { render: (createElement) => createElement(TypePopover), }); } + +export function initTypeSelect() { + const el = document.getElementById('js-type-select'); + + if (!el) { + return undefined; + } + + const { selectedType, isIssueAllowed, isIncidentAllowed, issuePath, incidentPath } = el.dataset; + + return new Vue({ + el, + name: 'TypeSelectRoot', + render: (createElement) => + createElement(TypeSelect, { + props: { + selectedType, + isIssueAllowed: parseBoolean(isIssueAllowed), + isIncidentAllowed: parseBoolean(isIncidentAllowed), + issuePath, + incidentPath, + }, + }), + }); +} diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js index 3130fe42c3c..c9f43e43b2d 100644 --- a/app/assets/javascripts/persistent_user_callouts.js +++ b/app/assets/javascripts/persistent_user_callouts.js @@ -24,6 +24,7 @@ const PERSISTENT_USER_CALLOUTS = [ '.js-geo-migrate-hashed-storage-callout', '.js-unlimited-members-during-trial-alert', '.js-branch-rules-info-callout', + '.js-license-check-deprecation-alert', ]; const initCallouts = () => { diff --git a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue index b654c6e4903..3872869f73d 100644 --- a/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue +++ b/app/assets/javascripts/sidebar/components/date/sidebar_date_widget.vue @@ -328,6 +328,7 @@ export default { v-if="!isLoading" ref="datePicker" class="gl-relative" + :value="parsedDate" :min-date="minDate" :max-date="maxDate" :default-date="parsedDate" diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 799777977ed..cbdc55d66c1 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -29,10 +29,6 @@ border-bottom: 1px solid $white-dark; color: $gl-text-color; - &.oneline-block { - line-height: 42px; - } - &.white { background-color: $white; } @@ -89,10 +85,6 @@ padding: $gl-padding 0; border-bottom: 1px solid $white-dark; - &.oneline-block { - line-height: 36px; - } - > .controls { float: right; } diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index a2e8d268455..0536dc73e61 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -386,6 +386,16 @@ module IssuablesHelper end end + def issuable_type_selector_data(issuable) + { + selected_type: issuable.issue_type, + is_issue_allowed: create_issue_type_allowed?(@project, :issue).to_s, + is_incident_allowed: create_issue_type_allowed?(@project, :incident).to_s, + issue_path: new_project_issue_path(@project), + incident_path: new_project_issue_path(@project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }) + } + end + private def sidebar_gutter_collapsed? diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 39399c2919b..e82f09a0a97 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -44,14 +44,6 @@ module IssuesHelper end end - def work_item_type_icon(issue_type) - if WorkItems::Type.base_types.include?(issue_type) - "issue-type-#{issue_type.to_s.dasherize}" - else - 'issue-type-issue' - end - end - def confidential_icon(issue) sprite_icon('eye-slash', css_class: 'gl-vertical-align-text-bottom') if issue.confidential? end diff --git a/app/models/achievements/achievement.rb b/app/models/achievements/achievement.rb index 95606e50ad4..a436e32b35b 100644 --- a/app/models/achievements/achievement.rb +++ b/app/models/achievements/achievement.rb @@ -4,9 +4,6 @@ module Achievements class Achievement < ApplicationRecord include Avatarable include StripAttribute - include IgnorableColumns - - ignore_column :revokable, remove_with: '15.11', remove_after: '2023-04-22' belongs_to :namespace, inverse_of: :achievements, optional: false diff --git a/app/models/concerns/has_user_type.rb b/app/models/concerns/has_user_type.rb index 0b1c6780db8..795ba94776b 100644 --- a/app/models/concerns/has_user_type.rb +++ b/app/models/concerns/has_user_type.rb @@ -4,7 +4,8 @@ module HasUserType extend ActiveSupport::Concern USER_TYPES = { - human: nil, + human_deprecated: nil, + human: 0, support_bot: 1, alert_bot: 2, visual_review_bot: 3, @@ -36,11 +37,15 @@ module HasUserType # `service_account` allows instance/namespaces to configure a user for external integrations/automations # `service_user` is an internal, `gitlab-com`-specific user type for integrations like suggested reviewers - NON_INTERNAL_USER_TYPES = %w[human project_bot service_user service_account].freeze + NON_INTERNAL_USER_TYPES = %w[human human_deprecated project_bot service_user service_account].freeze INTERNAL_USER_TYPES = (USER_TYPES.keys - NON_INTERNAL_USER_TYPES).freeze included do - scope :humans, -> { where(user_type: :human) } + enum user_type: USER_TYPES + + scope :humans, -> { where(user_type: :human).or(where(user_type: :human_deprecated)) } + # Override default scope to include temporary human type. See https://gitlab.com/gitlab-org/gitlab/-/issues/386474 + scope :human, -> { humans } scope :bots, -> { where(user_type: BOT_USER_TYPES) } scope :without_bots, -> { humans.or(where(user_type: USER_TYPES.keys - BOT_USER_TYPES)) } scope :non_internal, -> { humans.or(where(user_type: NON_INTERNAL_USER_TYPES)) } @@ -48,10 +53,8 @@ module HasUserType scope :without_project_bot, -> { humans.or(where(user_type: USER_TYPES.keys - ['project_bot'])) } scope :human_or_service_user, -> { humans.or(where(user_type: :service_user)) } - enum user_type: USER_TYPES - def human? - super || user_type.nil? + super || human_deprecated? || user_type.nil? end end diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb index 2e613768873..3ebb2126f4d 100644 --- a/app/models/personal_access_token.rb +++ b/app/models/personal_access_token.rb @@ -41,8 +41,8 @@ class PersonalAccessToken < ApplicationRecord scope :for_users, -> (users) { where(user: users) } scope :preload_users, -> { preload(:user) } scope :order_expires_at_asc_id_desc, -> { reorder(expires_at: :asc, id: :desc) } - scope :project_access_token, -> { includes(:user).where(user: { user_type: :project_bot }) } - scope :owner_is_human, -> { includes(:user).where(user: { user_type: :human }) } + scope :project_access_token, -> { includes(:user).references(:user).merge(User.project_bot) } + scope :owner_is_human, -> { includes(:user).references(:user).merge(User.human) } scope :last_used_before, -> (date) { where("last_used_at <= ?", date) } scope :last_used_after, -> (date) { where("last_used_at >= ?", date) } diff --git a/app/models/users/project_callout.rb b/app/models/users/project_callout.rb index c73b3a4ee71..e7229abd147 100644 --- a/app/models/users/project_callout.rb +++ b/app/models/users/project_callout.rb @@ -15,7 +15,8 @@ module Users storage_enforcement_banner_first_enforcement_threshold: 4, # EE-only storage_enforcement_banner_second_enforcement_threshold: 5, # EE-only storage_enforcement_banner_third_enforcement_threshold: 6, # EE-only - storage_enforcement_banner_fourth_enforcement_threshold: 7 # EE-only + storage_enforcement_banner_fourth_enforcement_threshold: 7, # EE-only + license_check_deprecation_alert: 8 # EE-only } validates :project, presence: true diff --git a/app/views/jira_connect/branches/new.html.haml b/app/views/jira_connect/branches/new.html.haml index 482012b2848..bb27e89abb9 100644 --- a/app/views/jira_connect/branches/new.html.haml +++ b/app/views/jira_connect/branches/new.html.haml @@ -1,4 +1,3 @@ -- @hide_breadcrumbs = true - @hide_top_links = true - @content_class = 'limit-container-width' - page_title _('Create branch') diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 09fa8575106..3c242d81874 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -10,6 +10,7 @@ - content_for :flash_message do = dispensable_render_if_exists "projects/storage_enforcement_alert", context: @project = dispensable_render_if_exists "shared/namespace_storage_limit_alert", context: @project + = dispensable_render_if_exists "projects/deprecate_license_check_alert", project: @project - content_for :project_javascripts do - project = @target_project || @project diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index 780bb3404cc..0a87ae145ac 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -1,7 +1,7 @@ - diff_file = local_assigns.fetch(:diff_file, nil) - file_hash = hexdigest(diff_file.file_path) -.diff-content +.diff-content.gl-rounded-bottom-base - if diff_file.has_renderable? .hidden{ id: "#raw-diff-#{file_hash}", data: { file_hash: file_hash, diff_toggle_entity: 'rawViewer' } } = render 'projects/diffs/viewer', viewer: diff_file.viewer diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 03e26fd4456..2cbca618520 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -8,7 +8,7 @@ - page = local_assigns.fetch(:page, nil) - diff_files = conditionally_paginate_diff_files(diffs, paginate: paginate_diffs, page: page, per: paginate_diffs_per_page) -.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed +.files-changed.diff-files-changed.js-diff-files-changed.gl-py-3 .files-changed-inner .inline-parallel-buttons.gl-display-none.gl-md-display-flex - if !diffs_expanded? && diff_files.any?(&:collapsed?) diff --git a/app/views/shared/issuable/form/_type_selector.html.haml b/app/views/shared/issuable/form/_type_selector.html.haml index 6d4cd83d55b..2350864f0a6 100644 --- a/app/views/shared/issuable/form/_type_selector.html.haml +++ b/app/views/shared/issuable/form/_type_selector.html.haml @@ -5,28 +5,8 @@ = _('Type') #js-type-popover - .issuable-form-select-holder.selectbox.form-group.gl-mb-0.gl-display-block - .dropdown.js-issuable-type-filter-dropdown-wrap - %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.dropdown-toggle-text.is-default - = issuable.issue_type.capitalize || _("Select type") - = sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon") - .dropdown-menu.dropdown-menu-selectable.dropdown-select - .dropdown-title.gl-display-flex - %span.gl-ml-auto - = _("Select type") - %button.dropdown-title-button.dropdown-menu-close.gl-ml-auto{ type: 'button', "aria-label" => _('Close') } - = sprite_icon('close', size: 16, css_class: 'dropdown-menu-close-icon') - .dropdown-content{ data: { testid: 'issue-type-select-dropdown' } } - %ul - - if create_issue_type_allowed?(@project, :issue) - %li.js-filter-issuable-type - = link_to new_project_issue_path(@project), class: ("is-active" if issuable.issue?) do - #{sprite_icon(work_item_type_icon(:issue), css_class: 'gl-icon')} #{_('Issue')} - - if create_issue_type_allowed?(@project, :incident) - %li.js-filter-issuable-type{ data: { track: { action: "select_issue_type_incident", label: "select_issue_type_incident_dropdown_option" } } } - = link_to new_project_issue_path(@project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }), class: ("is-active" if issuable.incident?) do - #{sprite_icon(work_item_type_icon(:incident), css_class: 'gl-icon')} #{_('Incident')} + .issuable-form-select-holder.form-group.gl-mb-0.gl-display-block + #js-type-select{ data: issuable_type_selector_data(issuable) } - if issuable.incident? %p.form-text.text-muted diff --git a/db/post_migrate/20230322203927_change_user_type_default.rb b/db/post_migrate/20230322203927_change_user_type_default.rb new file mode 100644 index 00000000000..792e9717c9f --- /dev/null +++ b/db/post_migrate/20230322203927_change_user_type_default.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class ChangeUserTypeDefault < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + change_column_default :users, :user_type, 0 + end + + def down + change_column_default :users, :user_type, nil + end +end diff --git a/db/schema_migrations/20230322203927 b/db/schema_migrations/20230322203927 new file mode 100644 index 00000000000..5484ff427e4 --- /dev/null +++ b/db/schema_migrations/20230322203927 @@ -0,0 +1 @@ +b91efba4aad15d32dc227620239a330246748c73d6afed946c3fd1baaa125932
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 8471d2c0734..5fb3ddb1b11 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -23415,7 +23415,7 @@ CREATE TABLE users ( last_name character varying(255), static_object_token character varying(255), role smallint, - user_type smallint, + user_type smallint DEFAULT 0, static_object_token_encrypted text, otp_secret_expires_at timestamp with time zone, onboarding_in_progress boolean DEFAULT false NOT NULL, diff --git a/doc/api/packages/composer.md b/doc/api/packages/composer.md index e75dbfeb92f..857f87a751e 100644 --- a/doc/api/packages/composer.md +++ b/doc/api/packages/composer.md @@ -18,7 +18,7 @@ package registry, see the [Composer package registry documentation](../../user/p NOTE: These endpoints do not adhere to the standard API authentication methods. See the [Composer package registry documentation](../../user/packages/composer_repository/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. ## Base repository request diff --git a/doc/api/packages/conan.md b/doc/api/packages/conan.md index c37296ad664..0dc6f68ed4c 100644 --- a/doc/api/packages/conan.md +++ b/doc/api/packages/conan.md @@ -17,7 +17,7 @@ package registry, see the [Conan package registry documentation](../../user/pack NOTE: These endpoints do not adhere to the standard API authentication methods. -See each route for details on how credentials are expected to be passed. +See each route for details on how credentials are expected to be passed. Undocumented authentication methods might be removed in the future. NOTE: The Conan registry is not FIPS compliant and is disabled when [FIPS mode](../../development/fips_compliance.md) is enabled. diff --git a/doc/api/packages/debian.md b/doc/api/packages/debian.md index 71ae3583f62..267ebb59c19 100644 --- a/doc/api/packages/debian.md +++ b/doc/api/packages/debian.md @@ -24,7 +24,7 @@ package registry, see the [Debian registry documentation](../../user/packages/de NOTE: These endpoints do not adhere to the standard API authentication methods. See the [Debian registry documentation](../../user/packages/debian_repository/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. ## Enable the Debian API diff --git a/doc/api/packages/go_proxy.md b/doc/api/packages/go_proxy.md index 08a7138a82a..1cfc0f0b296 100644 --- a/doc/api/packages/go_proxy.md +++ b/doc/api/packages/go_proxy.md @@ -20,7 +20,7 @@ For instructions on how to work with the Go Proxy, see the [Go Proxy package doc NOTE: These endpoints do not adhere to the standard API authentication methods. See the [Go Proxy package documentation](../../user/packages/go_proxy/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. ## List diff --git a/doc/api/packages/helm.md b/doc/api/packages/helm.md index 0e9a72c2389..d69f524c47c 100644 --- a/doc/api/packages/helm.md +++ b/doc/api/packages/helm.md @@ -19,7 +19,7 @@ Package Registry, see the [Helm registry documentation](../../user/packages/helm NOTE: These endpoints do not adhere to the standard API authentication methods. See the [Helm registry documentation](../../user/packages/helm_repository/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. ## Download a chart index diff --git a/doc/api/packages/maven.md b/doc/api/packages/maven.md index 4086b68b750..733f4735ba2 100644 --- a/doc/api/packages/maven.md +++ b/doc/api/packages/maven.md @@ -18,7 +18,7 @@ package registry, see the [Maven package registry documentation](../../user/pack NOTE: These endpoints do not adhere to the standard API authentication methods. See [Maven package registry documentation](../../user/packages/maven_repository/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. ## Download a package file at the instance-level diff --git a/doc/api/packages/npm.md b/doc/api/packages/npm.md index 7e8732d9553..bf48fbc8f65 100644 --- a/doc/api/packages/npm.md +++ b/doc/api/packages/npm.md @@ -18,7 +18,7 @@ package registry, see the [npm package registry documentation](../../user/packag NOTE: These endpoints do not adhere to the standard API authentication methods. See the [npm package registry documentation](../../user/packages/npm_registry/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. ## Download a package diff --git a/doc/api/packages/nuget.md b/doc/api/packages/nuget.md index ebcb51be447..a0761b56645 100644 --- a/doc/api/packages/nuget.md +++ b/doc/api/packages/nuget.md @@ -18,7 +18,7 @@ package registry, see the [NuGet package registry documentation](../../user/pack NOTE: These endpoints do not adhere to the standard API authentication methods. See the [NuGet package registry documentation](../../user/packages/nuget_repository/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. ## Package index diff --git a/doc/api/packages/pypi.md b/doc/api/packages/pypi.md index e9546f50e36..4e7c59adf3a 100644 --- a/doc/api/packages/pypi.md +++ b/doc/api/packages/pypi.md @@ -18,7 +18,7 @@ package registry, see the [PyPI package registry documentation](../../user/packa NOTE: These endpoints do not adhere to the standard API authentication methods. See the [PyPI package registry documentation](../../user/packages/pypi_repository/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. NOTE: [Twine 3.4.2](https://twine.readthedocs.io/en/stable/changelog.html?highlight=FIPS#id28) or greater diff --git a/doc/api/packages/rubygems.md b/doc/api/packages/rubygems.md index c2d05f24085..17338161a7b 100644 --- a/doc/api/packages/rubygems.md +++ b/doc/api/packages/rubygems.md @@ -19,7 +19,7 @@ package registry, see the [Ruby gems registry documentation](../../user/packages NOTE: These endpoints do not adhere to the standard API authentication methods. See the [Ruby gems registry documentation](../../user/packages/rubygems_registry/index.md) -for details on which headers and token types are supported. +for details on which headers and token types are supported. Undocumented authentication methods might be removed in the future. ## Enable the Ruby gems API diff --git a/doc/api/packages/terraform-modules.md b/doc/api/packages/terraform-modules.md index 6b3d6b477b2..dd62dc6c09e 100644 --- a/doc/api/packages/terraform-modules.md +++ b/doc/api/packages/terraform-modules.md @@ -10,7 +10,7 @@ This is the API documentation for [Terraform Modules](../../user/packages/terraf WARNING: This API is used by the [Terraform CLI](https://www.terraform.io/) -and is generally not meant for manual consumption. +and is generally not meant for manual consumption. Undocumented authentication methods might be removed in the future. For instructions on how to upload and install Terraform modules from the GitLab infrastructure registry, see the [Terraform modules registry documentation](../../user/packages/terraform_module_registry/index.md). diff --git a/doc/ci/pipelines/job_artifacts.md b/doc/ci/pipelines/job_artifacts.md index fb7bf2d823b..e26682c4562 100644 --- a/doc/ci/pipelines/job_artifacts.md +++ b/doc/ci/pipelines/job_artifacts.md @@ -213,6 +213,20 @@ artifacts: - "*.txt" ``` +## View all job artifacts + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254938) in GitLab 15.11 [with a flag](../../administration/feature_flags.md) named `artifacts_management_page`. Disabled by default. + +You can view all artifacts stored in a project from the **CI/CD > Artifacts** page. +This list displays all jobs and their associated artifacts. Expand an entry to access +all artifacts associated with a job, including: + +- Artifacts created with the `artifacts:` keyword. +- [Report artifacts](../yaml/artifacts_reports.md) +- Job logs and metadata, which are stored internally as separate artifacts. + +You can download or delete individual artifacts from this list. + ## Download job artifacts You can download job artifacts or view the job archive: diff --git a/doc/ci/variables/index.md b/doc/ci/variables/index.md index 24eee4253a8..716954203eb 100644 --- a/doc/ci/variables/index.md +++ b/doc/ci/variables/index.md @@ -212,9 +212,6 @@ To add an instance variable: - **Mask variable** Optional. If selected, the variable's **Value** is not shown in job logs. The variable is not saved if the value does not meet the [masking requirements](#mask-a-cicd-variable). -The instance variables that are available in a project are listed in the project's -**Settings > CI/CD > Variables** section. - ## CI/CD variable security Code pushed to the `.gitlab-ci.yml` file could compromise your variables. Variables could diff --git a/doc/development/pipelines/index.md b/doc/development/pipelines/index.md index 51cdd12a8c1..b56259dbdaf 100644 --- a/doc/development/pipelines/index.md +++ b/doc/development/pipelines/index.md @@ -541,6 +541,13 @@ By default, all tests run with [multiple databases](../database/multiple_databas We also run tests with a single database in nightly scheduled pipelines, and in merge requests that touch database-related files. +Single database tests run in two modes: + +1. **Single database with one connection**. Where GitLab connects to all the tables using one connection pool. +This runs through all the jobs that end with `-single-db` +1. **Single database with two connections**. Where GitLab connects to `gitlab_main`, `gitlab_ci` database tables +using different database connections. This runs through all the jobs that end with `-single-db-ci-connection`. + If you want to force tests to run with a single database, you can add the `pipeline:run-single-db` label to the merge request. ## Monitoring diff --git a/doc/user/group/saml_sso/example_saml_config.md b/doc/user/group/saml_sso/example_saml_config.md index 7778648e985..0121df4fdb3 100644 --- a/doc/user/group/saml_sso/example_saml_config.md +++ b/doc/user/group/saml_sso/example_saml_config.md @@ -58,6 +58,8 @@ Attribute mapping: NOTE: Using the **Group ID** source attribute requires users to enter the group ID or object ID when configuring SAML group links. If available, use the **sAMAccountName** source attribute for the friendly group name instead. +[Azure AD limits the number of groups that can be sent in a SAML response to 150](https://support.esri.com/en/technical-article/000022190). If a user is a member of more than 150 groups, Azure does not include that user's group claim in the SAML response. + ## Google Workspace Basic SAML app configuration: diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index 7ac9c4c83a2..347b8cd0a2d 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -21,7 +21,7 @@ You can configure SAML SSO for the top-level group only. ## Configure your identity provider 1. [Configure your SAML identity provider](#set-up-identity-provider). -1. Configure the SAML response to include a [NameID](#nameid) that uniquely identifies each user. +1. Configure the SAML response to include a [**NameID**](#nameid) that uniquely identifies each user. 1. Configure the required [user attributes](#user-attributes), ensuring you include the user's email address. 1. While the default is enabled for most SAML providers, ensure the app is set to have service provider initiated calls to link existing GitLab accounts. @@ -66,7 +66,7 @@ To set up SSO with Azure as your identity provider: 1. You should set the following attributes: - **Unique User Identifier (Name identifier)** to `user.objectID`. - - **nameid-format** to `persistent`. For more information, see the [NameID documentation](#nameid). + - **nameid-format** to `persistent`. For more information, see [**NameID**](#nameid). - **Additional claims** to [supported attributes](#user-attributes). 1. Optional. If you use [Group Sync](#group-sync), customize the name of the @@ -106,8 +106,8 @@ To set up Google Workspace as your identity provider: - For **Primary email**: `email`. - For **First name**: `first_name`. - For **Last name**: `last_name`. - - For **Name ID format**: `EMAIL`. For more information, see the [NameID format documentation](#nameid-format). - - For **NameID**: `Basic Information > Primary email`. For more information, see the [NameID documentation](#nameid). + - For **Name ID format**: `EMAIL`. For more information, see the [**NameID** format documentation](#nameid-format). + - For **NameID**: `Basic Information > Primary email`. For more information, see [**NameID**](#nameid). On the GitLab SAML SSO page, when you select **Verify SAML Configuration**, disregard the warning that recommends setting the **NameID** format to `persistent`. @@ -136,7 +136,7 @@ To set up SSO with Okta as your identity provider: 1. Set these values: - For **Application username (NameID)**: **Custom** `user.getInternalProperty("id")`. - - For **Name ID Format**: `Persistent`. For more information, see the [NameID documentation](#nameid). + - For **Name ID Format**: `Persistent`. For more information, see [**NameID**](#nameid). The Okta GitLab application available in the App Catalog only supports [SCIM](scim_setup.md). Support for SAML is proposed in [issue 216173](https://gitlab.com/gitlab-org/gitlab/-/issues/216173). @@ -169,7 +169,7 @@ To set up OneLogin as your identity provider: | **GitLab single sign-on URL** | **Login URL** | | **Identity provider single sign-on URL** | **SAML 2.0 Endpoint** | -1. For **NameID**, use `OneLogin ID`. For more information, see the [NameID documentation](#nameid). +1. For **NameID**, use `OneLogin ID`. For more information, see [**NameID**](#nameid). ### Set up identity provider using metadata @@ -306,8 +306,8 @@ After you have configured your identity provider, you can: To change the identity provider: -- If the `NameID` is not identical in the existing and new identity providers, [change the NameID for users](#change-nameid-for-one-or-more-users). -- If the `NameID` is identical, users do not have to make any changes. +- If the **NameID** value is not identical in the existing and new identity providers, [change the **NameID** for users](#change-nameid-for-one-or-more-users). +- If the **NameID** value is identical, users do not have to make any changes. #### Migrate to a different identity provider @@ -318,7 +318,7 @@ users cannot access any of the SAML groups. To mitigate this, you can disable To migrate identity providers: 1. [Configure](#configure-your-identity-provider) the group with the new identity provider. -1. [Change the NameID for users](#change-nameid-for-one-or-more-users). +1. [Change the **NameID** for users](#change-nameid-for-one-or-more-users). #### Change email domains @@ -327,7 +327,7 @@ To migrate users to a new email domain, tell users to: 1. Add their new email as the primary email to their accounts and verify it. 1. Optional. Remove their old email from the account. -If the NameID is configured with the email address, [change the NameID for users](#change-nameid-for-one-or-more-users). +If the **NameID** is configured with the email address, [change the **NameID** for users](#change-nameid-for-one-or-more-users). ## User access and management @@ -367,7 +367,7 @@ On subsequent visits, you should be able to go [sign in to GitLab.com with SAML] 1. From the list of apps, select the "GitLab.com" app. (The name is set by the administrator of the identity provider.) 1. You are then signed in to GitLab.com and redirected to the group. -### Change NameID for one or more users +### Change **NameID** for one or more users > Update of SAML identities using the SAML API [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227841) in GitLab 15.5. @@ -466,7 +466,7 @@ To rescind a user's access to the group when also using SCIM, refer to [Remove a Users can unlink SAML for a group from their profile page. This can be helpful if: - You no longer want a group to be able to sign you in to GitLab.com. -- Your SAML NameID has changed and so GitLab can no longer find your user. +- Your SAML **NameID** has changed and so GitLab can no longer find your user. WARNING: Unlinking an account removes all roles assigned to that user in the group. @@ -493,25 +493,38 @@ The [Generated passwords for users created through integrated authentication](.. ## NameID -GitLab.com uses the SAML NameID to identify users. The NameID element: +GitLab.com uses the SAML **NameID** to identify users. The **NameID** is: -- Is a required field in the SAML response. -- Must be unique to each user. -- Must be a persistent value that never changes, such as a randomly generated unique user ID. -- Is case sensitive. The NameID must match exactly on subsequent login attempts, so should not rely on user input that could change between upper and lower case. -- Should not be an email address or username. We strongly recommend against these as it's hard to - guarantee it doesn't ever change, for example, when a person's name changes. Email addresses are - also case-insensitive, which can result in users being unable to sign in. +- A required field in the SAML response. +- Case sensitive. -The relevant field name and recommended value for supported providers are in the [provider specific notes](#set-up-identity-provider). +The **NameID** must: + +- Be unique to each user. +- Be a persistent value that never changes, such as a randomly generated unique user ID. +- Match exactly on subsequent sign-in attempts, so it should not rely on user input + that could change between upper and lower case. + +The **NameID** should not be an email address or username because: + +- Email addresses and usernames are more likely to change over time. For example, + when a person's name changes. +- Email addresses are case-insensitive, which can result in users being unable to + sign in. + +For more information on the recommended value and format for specific identity +providers, see the [set up identity provider documentation](#set-up-identity-provider). WARNING: -Once users have signed into GitLab using the SSO SAML setup, changing the `NameID` breaks the configuration and potentially locks users out of the GitLab group. +After users have signed into GitLab using SSO SAML, changing the **NameID** value +breaks the configuration and could lock users out of the GitLab group. + +### **NameID** format -### NameID Format +Set the **NameID** format to `Persistent`, unless you are using a field, like email, that +requires a different format. -We recommend setting the NameID format to `Persistent` unless using a field (such as email) that requires a different format. -Most NameID formats can be used, except `Transient` due to the temporary nature of this format. +You can use any format except `Transient`. ## Related topics diff --git a/doc/user/packages/generic_packages/index.md b/doc/user/packages/generic_packages/index.md index 932de0bcde6..bad099e6733 100644 --- a/doc/user/packages/generic_packages/index.md +++ b/doc/user/packages/generic_packages/index.md @@ -22,6 +22,8 @@ do not support the other available mechanisms. The `user-id` is not checked and may be any value, and the `password` must be either a [personal access token](../../../api/rest/index.md#personalprojectgroup-access-tokens), a [CI/CD job token](../../../ci/jobs/ci_job_token.md), or a [deploy token](../../project/deploy_tokens/index.md). +Do not use authentication methods other than the methods documented here. Undocumented authentication methods might be removed in the future. + ## Publish a package file When you publish a package file, if the package does not exist, it is created. diff --git a/doc/user/packages/maven_repository/index.md b/doc/user/packages/maven_repository/index.md index 1899cdc213f..29de1940b63 100644 --- a/doc/user/packages/maven_repository/index.md +++ b/doc/user/packages/maven_repository/index.md @@ -22,6 +22,8 @@ You need an token to publish a package. There are different tokens available dep Create a token and save it to use later in the process. +Do not use authentication methods other than the methods documented here. Undocumented authentication methods might be removed in the future. + ### Edit the `settings.xml` Add the following section to your diff --git a/doc/user/packages/npm_registry/index.md b/doc/user/packages/npm_registry/index.md index 6ce4aab930e..1aea718d7a8 100644 --- a/doc/user/packages/npm_registry/index.md +++ b/doc/user/packages/npm_registry/index.md @@ -23,6 +23,8 @@ You need an token to publish a package. There are different tokens available dep Create a token and save it to use later in the process. +Do not use authentication methods other than the methods documented here. Undocumented authentication methods might be removed in the future. + ### Naming convention Depending on how the package is installed, you may need to adhere to the naming convention. diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index 4595e0eebb9..c97ce9ec593 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -39,6 +39,8 @@ Some features such as [publishing](#publish-a-nuget-package) a package are only When asking for versions of a given NuGet package name, the GitLab Package Registry returns a maximum of 300 most recent versions. +Do not use authentication methods other than the methods documented here. Undocumented authentication methods might be removed in the future. + WARNING: Because of how NuGet handles credentials, the Package Registry rejects anonymous requests on the group-level endpoint. To work around this limitation, set up [authentication](#add-the-package-registry-as-a-source-for-nuget-packages). diff --git a/doc/user/packages/pypi_repository/index.md b/doc/user/packages/pypi_repository/index.md index e5b7f06f6ca..d23966f26fe 100644 --- a/doc/user/packages/pypi_repository/index.md +++ b/doc/user/packages/pypi_repository/index.md @@ -34,6 +34,8 @@ To do this, you can use: `read_package_registry`, `write_package_registry`, or both. - A [CI job token](#authenticate-with-a-ci-job-token). +Do not use authentication methods other than the methods documented here. Undocumented authentication methods might be removed in the future. + ### Authenticate with a personal access token To authenticate with a personal access token, edit the `~/.pypirc` file and add: diff --git a/doc/user/packages/terraform_module_registry/index.md b/doc/user/packages/terraform_module_registry/index.md index 66a42f398b0..038eaabf6d4 100644 --- a/doc/user/packages/terraform_module_registry/index.md +++ b/doc/user/packages/terraform_module_registry/index.md @@ -18,6 +18,8 @@ To authenticate to the Terraform module registry, you need either: - A [personal access token](../../../api/rest/index.md#personalprojectgroup-access-tokens) with at least `read_api` rights. - A [CI/CD job token](../../../ci/jobs/ci_job_token.md). +Do not use authentication methods other than the methods documented here. Undocumented authentication methods might be removed in the future. + ## Publish a Terraform Module When you publish a Terraform Module, if it does not exist, it is created. diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index d52f119ba09..c54cf583247 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -135,11 +135,13 @@ To add links to other accounts: 1. On the top bar, in the upper-right corner, select your avatar. 1. Select **Edit profile**. -1. In the **Main settings** section, add your information from: - - Discord ([User ID](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-)) - - LinkedIn - - Skype - - Twitter +1. In the **Main settings** section, add your: + - Discord [user ID](https://support.discord.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-). + - LinkedIn profile name. + - Skype username. + - Twitter @username. + + Your user ID or username must be 500 characters or less. 1. Select **Update profile settings**. ## Show private contributions on your user profile page @@ -164,7 +166,7 @@ To specify your pronouns: 1. On the top bar, in the upper-right corner, select your avatar. 1. Select **Edit profile**. -1. In the **Pronouns** text box, enter your pronouns. +1. In the **Pronouns** text box, enter your pronouns. The text must be 50 characters or less. 1. Select **Update profile settings**. ## Add your name pronunciation @@ -178,7 +180,7 @@ To add your name pronunciation: 1. On the top bar, in the upper-right corner, select your avatar. 1. Select **Edit profile**. -1. In the **Pronunciation** text box, enter how your name is pronounced. +1. In the **Pronunciation** text box, enter how your name is pronounced. The pronunciation must be plain text and 255 characters or less. 1. Select **Update profile settings**. ## Set your current status diff --git a/doc/user/project/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md index 356e4e1b194..02a563f59b5 100644 --- a/doc/user/project/members/share_project_with_groups.md +++ b/doc/user/project/members/share_project_with_groups.md @@ -28,12 +28,14 @@ To invite a group to a project, you must be at least one of the following: In addition: -- The group you're inviting must have a more restrictive - [visibility level](../../public_access.md#project-and-group-visibility) - than the project. For example, you can invite: - - A private group to a public project. - - An internal group to a public project. - - A private group to an internal project. +- The [visibility level](../../public_access.md#project-and-group-visibility) of the group you're inviting +must be at least as restrictive as that of the project. For example, you can invite: + - A _private_ group to a _private_ project + - A _private_ group to an _internal_ project. + - A _private_ group to a _public_ project. + - An _internal_ group to an _internal_ project. + - An _internal_ group to a _public_ project. + - A _public_ group to a _public_ project. - The group or subgroup must be in the project's [namespace](../../namespace/index.md). For example, a project in the namespace `group/subgroup01/project`: diff --git a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml index 2f7c16f0904..aeadc89095b 100644 --- a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - AUTO_BUILD_IMAGE_VERSION: 'v1.30.0' + AUTO_BUILD_IMAGE_VERSION: 'v1.31.0' build: stage: build diff --git a/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml index 2f7c16f0904..aeadc89095b 100644 --- a/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Build.latest.gitlab-ci.yml @@ -1,5 +1,5 @@ variables: - AUTO_BUILD_IMAGE_VERSION: 'v1.30.0' + AUTO_BUILD_IMAGE_VERSION: 'v1.31.0' build: stage: build diff --git a/locale/gitlab.pot b/locale/gitlab.pot index bc35cbe6477..ead55c0a94a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -847,6 +847,9 @@ msgstr "" msgid "%{level_name} is not allowed since the fork source project has lower visibility." msgstr "" +msgid "%{license_check_docs_link_start}License-Check%{link_end} is enabled for this project. This feature has been %{deprecation_docs_link_url}deprecated%{link_end} in GitLab 15.9 and is planned for %{removal_docs_link_url}removal%{link_end} in 16.0. You can create a %{scan_result_policy_link_start}scan result policy%{link_end} to continue enforcing your license approval requirements." +msgstr "" + msgid "%{linkStart} Learn more%{linkEnd}." msgstr "" @@ -25630,6 +25633,9 @@ msgstr "" msgid "License overview" msgstr "" +msgid "License-Check has been %{deprecation_docs_link_url}deprecated%{link_end} in GitLab 15.9 and is planned for %{removal_docs_link_url}removal%{link_end} in 16.0. You can create a %{scan_result_policy_link_start}scan result policy%{link_end} to continue enforcing your license approval requirements." +msgstr "" + msgid "LicenseCompliance|%{docLinkStart}License Approvals%{docLinkEnd} are active" msgstr "" @@ -39152,6 +39158,9 @@ msgstr "" msgid "SecurityOrchestration|License scanner finds any license %{matching} %{licenses}%{detection} in an open merge request targeting %{branches}." msgstr "" +msgid "SecurityOrchestration|License-Check has been deprecated" +msgstr "" + msgid "SecurityOrchestration|New policy" msgstr "" diff --git a/package.json b/package.json index 27bfe88b2b4..30bc8c89dfe 100644 --- a/package.json +++ b/package.json @@ -210,7 +210,7 @@ "yaml": "^2.0.0-10" }, "devDependencies": { - "@gitlab/eslint-plugin": "18.2.0", + "@gitlab/eslint-plugin": "18.2.1", "@gitlab/stylelint-config": "4.1.0", "@graphql-eslint/eslint-plugin": "3.16.1", "@testing-library/dom": "^7.16.2", diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb index 85ca3d91a1a..a9605b214bd 100644 --- a/spec/features/issues/form_spec.rb +++ b/spec/features/issues/form_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do include ActionView::Helpers::JavaScriptHelper + include ListboxHelpers let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } @@ -249,19 +250,15 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do describe 'displays issue type options in the dropdown' do shared_examples 'type option is visible' do |label:, identifier:| it "shows #{identifier} option", :aggregate_failures do - page.within('[data-testid="issue-type-select-dropdown"]') do - expect(page).to have_selector(%([data-testid="issue-type-#{identifier}-icon"])) - expect(page).to have_content(label) - end + wait_for_requests + expect_listbox_item(label) end end shared_examples 'type option is missing' do |label:, identifier:| it "does not show #{identifier} option", :aggregate_failures do - page.within('[data-testid="issue-type-select-dropdown"]') do - expect(page).not_to have_selector(%([data-testid="issue-type-#{identifier}-icon"])) - expect(page).not_to have_content(label) - end + wait_for_requests + expect_no_listbox_item(label) end end diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb index c5d0791dc57..2377a2ba3c7 100644 --- a/spec/features/issues/user_creates_issue_spec.rb +++ b/spec/features/issues/user_creates_issue_spec.rb @@ -184,7 +184,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do end it 'pre-fills the issue type dropdown with issue type' do - expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-toggle-text')).to have_content('Issue') + expect(find('.js-issuable-type-filter-dropdown-wrap .gl-button-text')).to have_content('Issue') end it 'does not hide the milestone select' do @@ -200,7 +200,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do end it 'does not pre-fill the issue type dropdown with incident type' do - expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-toggle-text')).not_to have_content('Incident') + expect(find('.js-issuable-type-filter-dropdown-wrap .gl-button-text')).not_to have_content('Incident') end it 'shows the milestone select' do @@ -257,7 +257,7 @@ RSpec.describe "User creates issue", feature_category: :team_planning do end it 'pre-fills the issue type dropdown with incident type' do - expect(find('.js-issuable-type-filter-dropdown-wrap .dropdown-toggle-text')).to have_content('Incident') + expect(find('.js-issuable-type-filter-dropdown-wrap .gl-button-text')).to have_content('Incident') end it 'hides the epic select' do diff --git a/spec/features/jira_connect/branches_spec.rb b/spec/features/jira_connect/branches_spec.rb index c90c0d2dda9..25dc14a1dc9 100644 --- a/spec/features/jira_connect/branches_spec.rb +++ b/spec/features/jira_connect/branches_spec.rb @@ -75,7 +75,7 @@ RSpec.describe 'Create GitLab branches from Jira', :js, feature_category: :integ select_listbox_item(source_branch) fill_in 'Branch name', with: new_branch - click_on 'Create branch' + click_button 'Create branch' expect(page).to have_text('New branch was successfully created. You can now close this window and return to Jira.') diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 91401d19fd1..527a146ff73 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -94,6 +94,36 @@ RSpec.describe 'Environment', feature_category: :projects do expect(page).to have_link("#{build.name} (##{build.id})") end end + + context 'with related deployable present' do + let_it_be(:previous_pipeline) { create(:ci_pipeline, project: project) } + + let_it_be(:previous_build) do + create(:ci_build, :success, pipeline: previous_pipeline, environment: environment.name) + end + + let_it_be(:previous_deployment) do + create(:deployment, :success, environment: environment, deployable: previous_build) + end + + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:build) { create(:ci_build, pipeline: pipeline, environment: environment.name) } + + let_it_be(:deployment) do + create(:deployment, :success, environment: environment, deployable: build) + end + + before do + visit_environment(environment) + end + + it 'shows deployment information and buttons', :js do + wait_for_requests + expect(page).to have_button('Re-deploy to environment') + expect(page).to have_button('Rollback environment') + expect(page).to have_link("#{build.name} (##{build.id})") + end + end end end diff --git a/spec/frontend/editor/components/source_editor_toolbar_button_spec.js b/spec/frontend/editor/components/source_editor_toolbar_button_spec.js index 79692ab4557..a94ebdfd827 100644 --- a/spec/frontend/editor/components/source_editor_toolbar_button_spec.js +++ b/spec/frontend/editor/components/source_editor_toolbar_button_spec.js @@ -33,7 +33,7 @@ describe('Source Editor Toolbar button', () => { it('does not render the button if the props have not been passed', () => { createComponent({}); - expect(findButton().vm).toBeUndefined(); + expect(findButton().exists()).toBe(false); }); it('renders a default button without props', async () => { @@ -107,34 +107,31 @@ describe('Source Editor Toolbar button', () => { }); describe('click handler', () => { - let clickEvent; - - beforeEach(() => { - clickEvent = new Event('click'); - }); - it('fires the click handler on the button when available', async () => { - const spy = jest.fn(); + const clickSpy = jest.fn(); + const clickEvent = new Event('click'); createComponent({ button: { - onClick: spy, + onClick: clickSpy, }, }); - expect(spy).not.toHaveBeenCalled(); + expect(wrapper.emitted('click')).toEqual(undefined); findButton().vm.$emit('click', clickEvent); await nextTick(); - expect(spy).toHaveBeenCalledWith(clickEvent); + + expect(wrapper.emitted('click')).toEqual([[clickEvent]]); + expect(clickSpy).toHaveBeenCalledWith(clickEvent); }); + it('emits the "click" event, passing the event itself', async () => { createComponent(); - jest.spyOn(wrapper.vm, '$emit'); - expect(wrapper.vm.$emit).not.toHaveBeenCalled(); + expect(wrapper.emitted('click')).toEqual(undefined); - findButton().vm.$emit('click', clickEvent); + findButton().vm.$emit('click'); await nextTick(); - expect(wrapper.vm.$emit).toHaveBeenCalledWith('click', clickEvent); + expect(wrapper.emitted('click')).toHaveLength(1); }); }); }); diff --git a/spec/frontend/environments/environment_details/components/deployment_actions_spec.js b/spec/frontend/environments/environment_details/components/deployment_actions_spec.js index 725c8c6479e..4aac25bc8b3 100644 --- a/spec/frontend/environments/environment_details/components/deployment_actions_spec.js +++ b/spec/frontend/environments/environment_details/components/deployment_actions_spec.js @@ -1,8 +1,15 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlButton } from '@gitlab/ui'; import DeploymentActions from '~/environments/environment_details/components/deployment_actions.vue'; import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { translations } from '~/environments/environment_details/constants'; import ActionsComponent from '~/environments/components/environment_actions.vue'; +import createMockApollo from '../../../__helpers__/mock_apollo_helper'; +import waitForPromises from '../../../__helpers__/wait_for_promises'; describe('~/environments/environment_details/components/deployment_actions.vue', () => { + Vue.use(VueApollo); let wrapper; const actionsData = [ @@ -14,34 +21,112 @@ describe('~/environments/environment_details/components/deployment_actions.vue', }, ]; - const createWrapper = ({ actions }) => { + const rollbackData = { + id: '123', + name: 'enironment-name', + lastDeployment: { + commit: { + shortSha: 'abcd1234', + }, + isLast: true, + }, + retryUrl: 'deployment/retry', + }; + + const mockSetEnvironmentToRollback = jest.fn(); + const mockResolvers = { + Mutation: { + setEnvironmentToRollback: mockSetEnvironmentToRollback, + }, + }; + const createWrapper = ({ actions, rollback }) => { + const mockApollo = createMockApollo([], mockResolvers); return mountExtended(DeploymentActions, { + apolloProvider: mockApollo, propsData: { actions, + rollback, }, }); }; - describe('when there is no actions provided', () => { - beforeEach(() => { - wrapper = createWrapper({ actions: [] }); + const findRollbackButton = () => wrapper.findComponent(GlButton); + + describe('deployment actions', () => { + describe('when there is no actions provided', () => { + beforeEach(() => { + wrapper = createWrapper({ actions: [] }); + }); + + it('should not render actions component', () => { + const actionsComponent = wrapper.findComponent(ActionsComponent); + expect(actionsComponent.exists()).toBe(false); + }); }); - it('should not render actions component', () => { - const actionsComponent = wrapper.findComponent(ActionsComponent); - expect(actionsComponent.exists()).toBe(false); + describe('when there are actions provided', () => { + beforeEach(() => { + wrapper = createWrapper({ actions: actionsData }); + }); + + it('should render actions component', () => { + const actionsComponent = wrapper.findComponent(ActionsComponent); + expect(actionsComponent.exists()).toBe(true); + expect(actionsComponent.props().actions).toBe(actionsData); + }); }); }); - describe('when there are actions provided', () => { - beforeEach(() => { - wrapper = createWrapper({ actions: actionsData }); + describe('rollback action', () => { + describe('when there is no rollback data available', () => { + it('should not show a rollback button', () => { + wrapper = createWrapper({ actions: [] }); + const button = findRollbackButton(); + expect(button.exists()).toBe(false); + }); }); - it('should render actions component', () => { - const actionsComponent = wrapper.findComponent(ActionsComponent); - expect(actionsComponent.exists()).toBe(true); - expect(actionsComponent.props().actions).toBe(actionsData); - }); + describe.each([ + { isLast: true, buttonTitle: translations.redeployButtonTitle, icon: 'repeat' }, + { isLast: false, buttonTitle: translations.rollbackButtonTitle, icon: 'redo' }, + ])( + `when there is a rollback data available and the deployment isLast=$isLast`, + ({ isLast, buttonTitle, icon }) => { + let rollback; + beforeEach(() => { + const lastDeployment = { ...rollbackData.lastDeployment, isLast }; + rollback = { ...rollbackData }; + rollback.lastDeployment = lastDeployment; + wrapper = createWrapper({ actions: [], rollback }); + }); + + it('should show the rollback button', () => { + const button = findRollbackButton(); + expect(button.exists()).toBe(true); + }); + + it(`the rollback button should have "${icon}" icon`, () => { + const button = findRollbackButton(); + expect(button.props().icon).toBe(icon); + }); + + it(`the rollback button should have "${buttonTitle}" title`, () => { + const button = findRollbackButton(); + expect(button.attributes().title).toBe(buttonTitle); + }); + + it(`the rollback button click should send correct mutation`, async () => { + const button = findRollbackButton(); + button.vm.$emit('click'); + await waitForPromises(); + expect(mockSetEnvironmentToRollback).toHaveBeenCalledWith( + expect.anything(), + { environment: rollback }, + expect.anything(), + expect.anything(), + ); + }); + }, + ); }); }); diff --git a/spec/frontend/environments/environment_details/page_spec.js b/spec/frontend/environments/environment_details/page_spec.js index 3a1a3238abe..0e1d7221a7d 100644 --- a/spec/frontend/environments/environment_details/page_spec.js +++ b/spec/frontend/environments/environment_details/page_spec.js @@ -15,14 +15,31 @@ describe('~/environments/environment_details/page.vue', () => { let wrapper; + const emptyEnvironmentToRollbackData = { id: '', name: '', lastDeployment: null, retryUrl: '' }; + const environmentToRollbackMock = jest.fn(); + + const mockResolvers = { + Query: { + environmentToRollback: environmentToRollbackMock, + }, + }; + const defaultWrapperParameters = { resolvedData: resolvedEnvironmentDetails, + environmentToRollbackData: emptyEnvironmentToRollbackData, }; - const createWrapper = ({ resolvedData } = defaultWrapperParameters) => { - const mockApollo = createMockApollo([ - [getEnvironmentDetails, jest.fn().mockResolvedValue(resolvedData)], - ]); + const createWrapper = ({ + resolvedData, + environmentToRollbackData, + } = defaultWrapperParameters) => { + const mockApollo = createMockApollo( + [[getEnvironmentDetails, jest.fn().mockResolvedValue(resolvedData)]], + mockResolvers, + ); + environmentToRollbackMock.mockReturnValue( + environmentToRollbackData || emptyEnvironmentToRollbackData, + ); return mountExtended(EnvironmentsDetailPage, { apolloProvider: mockApollo, diff --git a/spec/frontend/environments/helpers/__snapshots__/deployment_data_transformation_helper_spec.js.snap b/spec/frontend/environments/helpers/__snapshots__/deployment_data_transformation_helper_spec.js.snap index 326a28bd769..0462b614ee2 100644 --- a/spec/frontend/environments/helpers/__snapshots__/deployment_data_transformation_helper_spec.js.snap +++ b/spec/frontend/environments/helpers/__snapshots__/deployment_data_transformation_helper_spec.js.snap @@ -31,6 +31,29 @@ Object { "label": "deploy-prod (#860)", "webPath": "/gitlab-org/pipelinestest/-/jobs/860", }, + "rollback": Object { + "id": "gid://gitlab/Deployment/76", + "lastDeployment": Object { + "commit": Object { + "author": Object { + "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png", + "id": "gid://gitlab/User/1", + "name": "Administrator", + "webUrl": "http://gdk.test:3000/root", + }, + "authorEmail": "admin@example.com", + "authorGravatar": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon", + "authorName": "Administrator", + "id": "gid://gitlab/CommitPresenter/0cb48dd5deddb7632fd7c3defb16075fc6c3ca74", + "message": "Update .gitlab-ci.yml file", + "shortId": "0cb48dd5", + "webUrl": "http://gdk.test:3000/gitlab-org/pipelinestest/-/commit/0cb48dd5deddb7632fd7c3defb16075fc6c3ca74", + }, + "isLast": false, + }, + "name": undefined, + "retryUrl": "/gitlab-org/pipelinestest/-/jobs/860/retry", + }, "status": "success", "triggerer": Object { "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png", @@ -62,6 +85,7 @@ Object { "deployed": "2022-10-17T07:44:43Z", "id": "31", "job": undefined, + "rollback": null, "status": "success", "triggerer": Object { "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png", @@ -93,6 +117,7 @@ Object { "deployed": "", "id": "31", "job": null, + "rollback": null, "status": "success", "triggerer": Object { "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png", diff --git a/spec/frontend/issues/new/components/type_select_spec.js b/spec/frontend/issues/new/components/type_select_spec.js new file mode 100644 index 00000000000..a25ace10fe7 --- /dev/null +++ b/spec/frontend/issues/new/components/type_select_spec.js @@ -0,0 +1,141 @@ +import { GlCollapsibleListbox, GlIcon } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; +import * as urlUtility from '~/lib/utils/url_utility'; +import TypeSelect from '~/issues/new/components/type_select.vue'; +import { TYPE_ISSUE, TYPE_INCIDENT } from '~/issues/constants'; +import { __ } from '~/locale'; + +const issuePath = 'issues/new'; +const incidentPath = 'issues/new?issuable_template=incident'; +const tracking = { + action: 'select_issue_type_incident', + label: 'select_issue_type_incident_dropdown_option', +}; + +const defaultProps = { + selectedType: '', + isIssueAllowed: true, + isIncidentAllowed: true, + issuePath, + incidentPath, +}; + +const issue = { + value: TYPE_ISSUE, + text: __('Issue'), + icon: 'issue-type-issue', + href: issuePath, +}; +const incident = { + value: TYPE_INCIDENT, + text: __('Incident'), + icon: 'issue-type-incident', + href: incidentPath, + tracking, +}; + +describe('Issue type select component', () => { + let wrapper; + let trackingSpy; + let navigationSpy; + + const createComponent = (props = {}) => { + wrapper = mount(TypeSelect, { + propsData: { ...defaultProps, ...props }, + }); + }; + + const findListbox = () => wrapper.findComponent(GlCollapsibleListbox); + const findAllIcons = () => wrapper.findAllComponents(GlIcon); + const findListboxItemIcon = () => findAllIcons().at(2); + + describe('initial state', () => { + it('renders listbox with the correct header text', () => { + createComponent(); + + expect(findListbox().props('headerText')).toBe(TypeSelect.i18n.selectType); + }); + + it.each` + selectedType | toggleText + ${''} | ${TypeSelect.i18n.selectType} + ${TYPE_ISSUE} | ${TypeSelect.i18n.issuableType[TYPE_ISSUE]} + ${TYPE_INCIDENT} | ${TypeSelect.i18n.issuableType[TYPE_INCIDENT]} + `( + 'renders listbox with the correct toggle text when selectedType is "$selectedType"', + ({ selectedType, toggleText }) => { + createComponent({ selectedType }); + + expect(findListbox().props('toggleText')).toBe(toggleText); + }, + ); + + it.each` + isIssueAllowed | isIncidentAllowed | items + ${true} | ${true} | ${[issue, incident]} + ${true} | ${false} | ${[issue]} + ${false} | ${true} | ${[incident]} + `( + 'renders listbox with the correct items when isIssueAllowed is "$isIssueAllowed" and isIncidentAllowed is "$isIncidentAllowed"', + ({ isIssueAllowed, isIncidentAllowed, items }) => { + createComponent({ isIssueAllowed, isIncidentAllowed }); + + expect(findListbox().props('items')).toMatchObject(items); + }, + ); + + it.each` + isIssueAllowed | isIncidentAllowed | icon + ${true} | ${false} | ${issue.icon} + ${false} | ${true} | ${incident.icon} + `( + 'renders listbox item with the correct $icon icon', + ({ isIssueAllowed, isIncidentAllowed, icon }) => { + createComponent({ isIssueAllowed, isIncidentAllowed }); + findListbox().vm.$emit('shown'); + + expect(findListboxItemIcon().props('name')).toBe(icon); + }, + ); + }); + + describe('on type selected', () => { + beforeEach(() => { + navigationSpy = jest.spyOn(urlUtility, 'visitUrl').mockReturnValue({}); + trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); + }); + + afterEach(() => { + unmockTracking(); + navigationSpy.mockRestore(); + }); + + it.each` + selectedType | expectedUrl + ${TYPE_ISSUE} | ${issuePath} + ${TYPE_INCIDENT} | ${incidentPath} + `('navigates to the $selectedType issuable page', ({ selectedType, expectedUrl }) => { + createComponent(); + findListbox().vm.$emit('select', selectedType); + + expect(navigationSpy).toHaveBeenCalledWith(expectedUrl); + }); + + it("doesn't call tracking APIs when tracking is not available for the issuable type", () => { + createComponent(); + findListbox().vm.$emit('select', TYPE_ISSUE); + + expect(trackingSpy).not.toHaveBeenCalled(); + }); + + it('calls tracking APIs when tracking is available for the issuable type', () => { + createComponent(); + findListbox().vm.$emit('select', TYPE_INCIDENT); + + expect(trackingSpy).toHaveBeenCalledWith(undefined, tracking.action, { + label: tracking.label, + }); + }); + }); +}); diff --git a/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js b/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js index be3fee148da..261bfe14fd8 100644 --- a/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js +++ b/spec/frontend/sidebar/components/date/sidebar_date_widget_spec.js @@ -135,7 +135,7 @@ describe('Sidebar date Widget', () => { it('uses a correct prop to set the initial date and first day of the week for GlDatePicker', () => { expect(findDatePicker().props()).toMatchObject({ - value: null, + value: new Date(date), autocomplete: 'off', defaultDate: expect.any(Object), firstDay: window.gon.first_day_of_week, diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 7549e37d4fd..960a7e03e49 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -695,4 +695,37 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do end end end + + describe '#issuable_type_selector_data' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:project) { create(:project) } + + where(:issuable_type, :issuable_display_type, :is_issue_allowed, :is_incident_allowed) do + :issue | 'issue' | true | false + :incident | 'incident' | false | true + end + + with_them do + let(:issuable) { build_stubbed(issuable_type) } + + before do + allow(helper).to receive(:create_issue_type_allowed?).with(project, :issue).and_return(is_issue_allowed) + allow(helper).to receive(:create_issue_type_allowed?).with(project, :incident).and_return(is_incident_allowed) + assign(:project, project) + end + + it 'returns the correct data for the issuable type selector' do + expected_data = { + selected_type: issuable_display_type, + is_issue_allowed: is_issue_allowed.to_s, + is_incident_allowed: is_incident_allowed.to_s, + issue_path: new_project_issue_path(project), + incident_path: new_project_issue_path(project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }) + } + + expect(helper.issuable_type_selector_data(issuable)).to match(expected_data) + end + end + end end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 994a1ff4f75..087e8d0ecb7 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -6,18 +6,6 @@ RSpec.describe IssuesHelper do let_it_be(:project) { create(:project) } let_it_be_with_reload(:issue) { create(:issue, project: project) } - describe '#work_item_type_icon' do - it 'returns icon of all standard base types' do - WorkItems::Type.base_types.each do |type| - expect(work_item_type_icon(type[0])).to eq "issue-type-#{type[0].to_s.dasherize}" - end - end - - it 'defaults to issue icon if type is unknown' do - expect(work_item_type_icon('invalid')).to eq 'issue-type-issue' - end - end - describe '#award_user_list' do it 'returns a comma-separated list of the first X users' do user = build_stubbed(:user, name: 'Joe') diff --git a/spec/models/concerns/has_user_type_spec.rb b/spec/models/concerns/has_user_type_spec.rb index 03d2c267098..1b8199fec55 100644 --- a/spec/models/concerns/has_user_type_spec.rb +++ b/spec/models/concerns/has_user_type_spec.rb @@ -5,8 +5,9 @@ require 'spec_helper' RSpec.describe User, feature_category: :system_access do specify 'types consistency checks', :aggregate_failures do expect(described_class::USER_TYPES.keys) - .to match_array(%w[human ghost alert_bot project_bot support_bot service_user security_bot visual_review_bot - migration_bot automation_bot security_policy_bot admin_bot suggested_reviewers_bot service_account]) + .to match_array(%w[human human_deprecated ghost alert_bot project_bot support_bot service_user security_bot + visual_review_bot migration_bot automation_bot security_policy_bot admin_bot suggested_reviewers_bot + service_account]) expect(described_class::USER_TYPES).to include(*described_class::BOT_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::NON_INTERNAL_USER_TYPES) expect(described_class::USER_TYPES).to include(*described_class::INTERNAL_USER_TYPES) @@ -22,7 +23,13 @@ RSpec.describe User, feature_category: :system_access do describe '.humans' do it 'includes humans only' do - expect(described_class.humans).to match_array([human]) + expect(described_class.humans).to match_array([human, human_deprecated]) + end + end + + describe '.human' do + it 'includes humans only' do + expect(described_class.human).to match_array([human, human_deprecated]) end end @@ -69,6 +76,7 @@ RSpec.describe User, feature_category: :system_access do describe '#human?' do it 'is true for humans only' do expect(human).to be_human + expect(human_deprecated).to be_human expect(alert_bot).not_to be_human expect(User.new).to be_human end diff --git a/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb index 3f1a98ca08e..7bd7500d546 100644 --- a/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb +++ b/spec/support/shared_examples/quick_actions/issue/promote_to_incident_quick_action_shared_examples.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true RSpec.shared_examples 'promote_to_incident quick action' do + include ListboxHelpers + describe '/promote_to_incident' do context 'when issue can be promoted' do it 'promotes issue to incident' do @@ -52,9 +54,11 @@ RSpec.shared_examples 'promote_to_incident quick action' do context 'when incident is selected for issue type' do it 'promotes issue to incident' do visit new_project_issue_path(project) + wait_for_requests + fill_in('Title', with: 'Title') find('.js-issuable-type-filter-dropdown-wrap').click - click_link('Incident') + select_listbox_item(_('Incident')) fill_in('Description', with: '/promote_to_incident') click_button('Create issue') diff --git a/spec/workers/users/deactivate_dormant_users_worker_spec.rb b/spec/workers/users/deactivate_dormant_users_worker_spec.rb index 1fb936b1fc2..bd6c6dfc6b2 100644 --- a/spec/workers/users/deactivate_dormant_users_worker_spec.rb +++ b/spec/workers/users/deactivate_dormant_users_worker_spec.rb @@ -35,6 +35,7 @@ RSpec.describe Users::DeactivateDormantUsersWorker, feature_category: :subscript where(:user_type, :expected_state) do :human | 'deactivated' + :human_deprecated | 'deactivated' :support_bot | 'active' :alert_bot | 'active' :visual_review_bot | 'active' @@ -57,11 +58,13 @@ RSpec.describe Users::DeactivateDormantUsersWorker, feature_category: :subscript it 'does not deactivate non-active users' do human_user = create(:user, user_type: :human, state: :blocked, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date) + human_user2 = create(:user, user_type: :human_deprecated, state: :blocked, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date) service_user = create(:user, user_type: :service_user, state: :blocked, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date) worker.perform expect(human_user.reload.state).to eq('blocked') + expect(human_user2.reload.state).to eq('blocked') expect(service_user.reload.state).to eq('blocked') end diff --git a/yarn.lock b/yarn.lock index 7515813b8c8..a330462aa5d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1085,10 +1085,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e" integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg== -"@gitlab/eslint-plugin@18.2.0": - version "18.2.0" - resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-18.2.0.tgz#82810648a2273acfa5fd04fec9bf5dfa0f736c63" - integrity sha512-ynj2KyRkNykvZKqNDYInh5hZZRYxzZdOB6VUFOAjxW/nu2LDXquepqa+BDF956LDAhWMG0BGFqhqa8GRjxdi2w== +"@gitlab/eslint-plugin@18.2.1": + version "18.2.1" + resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-18.2.1.tgz#aa7dccb9812bf2b3424cdd841fc4dfc1c9800c73" + integrity sha512-AJKXFuu+87jrkoD4iguFn5/Zxq+u6JqRgE6G1R4A04KRMLXiwP/pweD5Gy4yCW1c6TbBI6NRJ1Q1GOvmYNkTiQ== dependencies: "@babel/core" "^7.17.0" "@babel/eslint-parser" "^7.17.0" |