diff --git a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml index 2d04bf86a22..9169a82792b 100644 --- a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml +++ b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml @@ -14,24 +14,12 @@ Layout/LineEndStringConcatenationIndentation: - 'app/graphql/mutations/concerns/mutations/validate_time_estimate.rb' - 'app/graphql/mutations/container_registry/protection/rule/create.rb' - 'app/graphql/mutations/container_registry/protection/rule/delete.rb' - - 'app/graphql/mutations/container_registry/protection/rule/update.rb' - - 'app/graphql/mutations/design_management/upload.rb' - - 'app/graphql/mutations/issues/bulk_update.rb' - - 'app/graphql/mutations/issues/update.rb' - - 'app/graphql/mutations/members/projects/bulk_update.rb' - - 'app/graphql/mutations/merge_requests/update.rb' - 'app/graphql/resolvers/ci/config_resolver.rb' - 'app/graphql/resolvers/ci/runners_resolver.rb' - 'app/graphql/resolvers/ci/template_resolver.rb' - 'app/graphql/resolvers/concerns/project_search_arguments.rb' - 'app/graphql/resolvers/design_management/designs_resolver.rb' - 'app/graphql/resolvers/groups_resolver.rb' - - 'app/graphql/types/ci/detailed_status_type.rb' - - 'app/graphql/types/ci/job_token_scope/direction_enum.rb' - - 'app/graphql/types/ci/job_token_scope_type.rb' - - 'app/graphql/types/ci/job_trace_type.rb' - - 'app/graphql/types/ci/job_type.rb' - - 'app/graphql/types/ci/runner_membership_filter_enum.rb' - 'app/graphql/types/ci/runner_type.rb' - 'app/graphql/types/commit_type.rb' - 'app/graphql/types/container_registry/protection/rule_type.rb' diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 80c9cbf13c8..e252164f0d5 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -faa2e2cf138777ba493edc43a01cc905d83d99be +7207df70c6dcd76de3ef92993b57b33c7ab5eb8a diff --git a/app/assets/javascripts/api/projects_api.js b/app/assets/javascripts/api/projects_api.js index e85553cc292..558520faf94 100644 --- a/app/assets/javascripts/api/projects_api.js +++ b/app/assets/javascripts/api/projects_api.js @@ -10,6 +10,7 @@ const PROJECT_IMPORT_MEMBERS_PATH = '/api/:version/projects/:id/import_project_m const PROJECT_REPOSITORY_SIZE_PATH = '/api/:version/projects/:id/repository_size'; const PROJECT_TRANSFER_LOCATIONS_PATH = 'api/:version/projects/:id/transfer_locations'; const PROJECT_SHARE_LOCATIONS_PATH = 'api/:version/projects/:id/share_locations'; +const PROJECT_UPLOADS_PATH = '/api/:version/projects/:id/uploads'; export function getProjects(query, options, callback = () => {}) { const url = buildApiUrl(PROJECTS_PATH); @@ -91,3 +92,30 @@ export const getProjectShareLocations = (projectId, params = {}, axiosOptions = return axios.get(url, { params: { ...defaultParams, ...params }, ...axiosOptions }); }; + +/** + * Uploads an image to a project and returns the share URL. + * + * @param {Object} options - The options for uploading the image. + * @param {string} options.filename - The name of the file to be uploaded. + * @param {Blob} options.blobData - The blob data of the image to be uploaded. + * @param {string} options.projectId - The ID of the project. + * @returns {Promise} The share URL of the uploaded image + */ + +export async function uploadImageToProject({ filename, blobData, projectId }) { + const url = buildApiUrl(PROJECT_UPLOADS_PATH).replace(':id', projectId); + + const formData = new FormData(); + formData.append('file', blobData, filename); + + const result = await axios.post(url, formData, { + headers: { 'Content-Type': 'multipart/form-data' }, + }); + + if (!result.data?.full_path) { + return Promise.reject(new Error(`Image failed to upload`)); + } + + return new URL(result.data.full_path, document.baseURI).href; +} diff --git a/app/assets/javascripts/lib/utils/image_utils.js b/app/assets/javascripts/lib/utils/image_utils.js index 53906a5ab87..2a240a39945 100644 --- a/app/assets/javascripts/lib/utils/image_utils.js +++ b/app/assets/javascripts/lib/utils/image_utils.js @@ -1,3 +1,4 @@ +import { domToBlob } from 'modern-screenshot'; import vector from './vector'; import { readFileAsDataURL } from './file_utility'; @@ -64,3 +65,7 @@ export const getRetinaDimensions = async (pngFile) => { return null; } }; + +export function domElementToBlob(domElement) { + return domToBlob(domElement); +} diff --git a/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue b/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue index ed1bd120d25..b217986c4e5 100644 --- a/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue +++ b/app/assets/javascripts/work_items/components/shared/work_item_link_child_contents.vue @@ -16,7 +16,7 @@ import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip import WorkItemLinkChildMetadata from 'ee_else_ce/work_items/components/shared/work_item_link_child_metadata.vue'; import WorkItemTypeIcon from '../work_item_type_icon.vue'; import WorkItemStateBadge from '../work_item_state_badge.vue'; -import { findLinkedItemsWidget } from '../../utils'; +import { findLinkedItemsWidget, getDisplayReference } from '../../utils'; import { STATE_OPEN, WIDGET_TYPE_ASSIGNEES, WIDGET_TYPE_LABELS } from '../../constants'; import WorkItemRelationshipIcons from './work_item_relationship_icons.vue'; @@ -54,16 +54,15 @@ export default { type: Boolean, required: true, }, + workItemFullPath: { + type: String, + required: true, + }, showLabels: { type: Boolean, required: false, default: true, }, - workItemFullPath: { - type: String, - required: false, - default: '', - }, showWeight: { type: Boolean, required: false, @@ -103,6 +102,15 @@ export default { childItemType() { return this.childItem.workItemType.name; }, + childItemIid() { + return this.childItem.iid; + }, + childItemWebUrl() { + return this.childItem.webUrl; + }, + childItemFullPath() { + return this.childItem.namespace?.fullPath; + }, stateTimestamp() { return this.isChildItemOpen ? this.childItem.createdAt : this.childItem.closedAt; }, @@ -116,12 +124,7 @@ export default { return this.showLabels && this.labels.length; }, displayReference() { - // The reference is replaced by work item fullpath in case the project and group are same. - // e.g., gitlab-org/gitlab-test#45 will be shown as #45 - if (new RegExp(`${this.workItemFullPath}#`, 'g').test(this.childItem.reference)) { - return this.childItem.reference.replace(new RegExp(`${this.workItemFullPath}`, 'g'), ''); - } - return this.childItem.reference; + return getDisplayReference(this.workItemFullPath, this.childItem.reference); }, linkedChildWorkItems() { return findLinkedItemsWidget(this.childItem).linkedItems?.nodes || []; @@ -190,6 +193,9 @@ export default { v-if="isChildItemOpen && linkedChildWorkItems.length" :work-item-type="childItemType" :linked-work-items="linkedChildWorkItems" + :work-item-full-path="childItemFullPath" + :work-item-iid="childItemIid" + :work-item-web-url="childItemWebUrl" /> -import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { GlIcon } from '@gitlab/ui'; import { n__, sprintf } from '~/locale'; import { LINKED_CATEGORIES_MAP, sprintfWorkItem } from '../../constants'; +import workItemLinkedItemsQuery from '../../graphql/work_item_linked_items.query.graphql'; +import { findLinkedItemsWidget } from '../../utils'; +import WorkItemRelationshipPopover from './work_item_relationship_popover.vue'; export default { components: { GlIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, + WorkItemRelationshipPopover, }, props: { linkedWorkItems: { @@ -19,6 +20,46 @@ export default { type: String, required: true, }, + workItemWebUrl: { + type: String, + required: true, + }, + workItemFullPath: { + type: String, + required: true, + }, + workItemIid: { + type: String, + required: true, + }, + }, + apollo: { + childItemLinkedItems: { + skip() { + return this.skipQuery; + }, + query() { + return workItemLinkedItemsQuery; + }, + variables() { + return { + fullPath: this.workItemFullPath, + iid: this.workItemIid, + }; + }, + update({ workspace }) { + if (!workspace?.workItem) return []; + + this.skipQuery = true; + return findLinkedItemsWidget(workspace.workItem).linkedItems?.nodes || []; + }, + }, + }, + data() { + return { + skipQuery: true, + childItemLinkedItems: [], + }; }, computed: { itemsBlockedBy() { @@ -31,6 +72,12 @@ export default { return item.linkType === LINKED_CATEGORIES_MAP.BLOCKS; }); }, + itemsBlockedByIconId() { + return `relationship-blocked-by-icon-${this.workItemIid}`; + }, + itemsBlocksIconId() { + return `relationship-blocks-icon-${this.workItemIid}`; + }, blockedByLabel() { const message = sprintf( n__( @@ -53,33 +100,74 @@ export default { ); return sprintfWorkItem(message, this.workItemType); }, + isLoading() { + return this.$apollo.queries.childItemLinkedItems.loading; + }, + childBlockedByItems() { + return this.childItemLinkedItems.filter((item) => { + return item.linkType === LINKED_CATEGORIES_MAP.IS_BLOCKED_BY; + }); + }, + childBlocksItems() { + return this.childItemLinkedItems.filter((item) => { + return item.linkType === LINKED_CATEGORIES_MAP.BLOCKS; + }); + }, + }, + methods: { + handleMouseEnter() { + this.skipQuery = false; + }, }, }; diff --git a/app/assets/javascripts/work_items/components/shared/work_item_relationship_popover.vue b/app/assets/javascripts/work_items/components/shared/work_item_relationship_popover.vue new file mode 100644 index 00000000000..01b709657dd --- /dev/null +++ b/app/assets/javascripts/work_items/components/shared/work_item_relationship_popover.vue @@ -0,0 +1,123 @@ + + + diff --git a/app/assets/javascripts/work_items/components/shared/work_item_relationship_popover_metadata.vue b/app/assets/javascripts/work_items/components/shared/work_item_relationship_popover_metadata.vue new file mode 100644 index 00000000000..f2f054efab5 --- /dev/null +++ b/app/assets/javascripts/work_items/components/shared/work_item_relationship_popover_metadata.vue @@ -0,0 +1,84 @@ + + + diff --git a/app/assets/javascripts/work_items/graphql/work_item_linked_items.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_linked_items.fragment.graphql index ded0145f224..a7ffdaf354c 100644 --- a/app/assets/javascripts/work_items/graphql/work_item_linked_items.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item_linked_items.fragment.graphql @@ -13,6 +13,10 @@ fragment WorkItemLinkedItemsFragment on WorkItem { id iid confidential + namespace { + id + fullPath + } workItemType { id name diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js index 4f17a2699cc..b790b2f74f0 100644 --- a/app/assets/javascripts/work_items/utils.js +++ b/app/assets/javascripts/work_items/utils.js @@ -153,6 +153,15 @@ export const markdownPreviewPath = ({ fullPath, iid, isGroup = false }) => { return `${domain}/${basePath}/-/preview_markdown?target_type=WorkItem&target_id=${iid}`; }; +export const getDisplayReference = (workItemFullPath, workitemReference) => { + // The reference is replaced by work item fullpath in case the project and group are same. + // e.g., gitlab-org/gitlab-test#45 will be shown as #45 + if (new RegExp(`${workItemFullPath}#`, 'g').test(workitemReference)) { + return workitemReference.replace(new RegExp(`${workItemFullPath}`, 'g'), ''); + } + return workitemReference; +}; + export const isReference = (input) => { /** * The regular expression checks if the `value` is diff --git a/app/assets/stylesheets/page_bundles/issues_list.scss b/app/assets/stylesheets/page_bundles/issues_list.scss index 17780c5e4ff..1b2445381af 100644 --- a/app/assets/stylesheets/page_bundles/issues_list.scss +++ b/app/assets/stylesheets/page_bundles/issues_list.scss @@ -22,11 +22,6 @@ .title { margin-bottom: 2px; } - - .icon-merge-request-unmerged { - height: 13px; - margin-bottom: 3px; - } } } @@ -34,15 +29,3 @@ opacity: 0.3; pointer-events: none; } - -.work-item-labels { - // stylelint-disable-next-line gitlab/no-gl-class - .gl-token { - padding-left: $gl-spacing-scale-1; - } - - // stylelint-disable-next-line gitlab/no-gl-class - .gl-token-close { - display: none; - } -} diff --git a/app/assets/stylesheets/themes/dark_mode_overrides.scss b/app/assets/stylesheets/themes/dark_mode_overrides.scss index 85e43ca2bec..94ed81c7eb9 100644 --- a/app/assets/stylesheets/themes/dark_mode_overrides.scss +++ b/app/assets/stylesheets/themes/dark_mode_overrides.scss @@ -82,11 +82,6 @@ background-color: mix($gray-50, $orange-50, 75%) !important; } -.gl-dark .issue-sticky-header-text .badge-muted { - background-color: var(--gray-100); - color: var(--gray-700); -} - .gl-dark { .gl-filtered-search-suggestion-active { background-color: $gray-100; diff --git a/app/graphql/mutations/container_registry/protection/rule/update.rb b/app/graphql/mutations/container_registry/protection/rule/update.rb index 24716849cef..a3d1a9b462a 100644 --- a/app/graphql/mutations/container_registry/protection/rule/update.rb +++ b/app/graphql/mutations/container_registry/protection/rule/update.rb @@ -7,8 +7,8 @@ module Mutations class Update < ::Mutations::BaseMutation graphql_name 'UpdateContainerRegistryProtectionRule' description 'Updates a container registry protection rule to restrict access to project containers. ' \ - 'You can prevent users without certain roles from altering containers. ' \ - 'Available only when feature flag `container_registry_protected_containers` is enabled.' + 'You can prevent users without certain roles from altering containers. ' \ + 'Available only when feature flag `container_registry_protected_containers` is enabled.' authorize :admin_container_image diff --git a/app/graphql/mutations/design_management/upload.rb b/app/graphql/mutations/design_management/upload.rb index 1f05b619e1b..422c7eecb16 100644 --- a/app/graphql/mutations/design_management/upload.rb +++ b/app/graphql/mutations/design_management/upload.rb @@ -18,7 +18,7 @@ module Mutations field :skipped_designs, [Types::DesignManagement::DesignType], null: false, description: "Any designs that were skipped from the upload due to there " \ - "being no change to their content since their last version" + "being no change to their content since their last version" def resolve(project_path:, iid:, files:) issue = authorized_find!(project_path: project_path, iid: iid) diff --git a/app/graphql/mutations/issues/bulk_update.rb b/app/graphql/mutations/issues/bulk_update.rb index c5ffd3095bb..4f46b98710c 100644 --- a/app/graphql/mutations/issues/bulk_update.rb +++ b/app/graphql/mutations/issues/bulk_update.rb @@ -10,23 +10,23 @@ module Mutations MAX_ISSUES = 100 description 'Allows updating several properties for a set of issues. ' \ - 'Does nothing if the `bulk_update_issues_mutation` feature flag is disabled.' + 'Does nothing if the `bulk_update_issues_mutation` feature flag is disabled.' argument :parent_id, ::Types::GlobalIDType[::IssueParent], required: true, description: 'Global ID of the parent to which the bulk update will be scoped. ' \ - 'The parent can be a project. The parent can also be a group (Premium and Ultimate only). ' \ - 'Example `IssueParentID` are `"gid://gitlab/Project/1"` and `"gid://gitlab/Group/1"`.' + 'The parent can be a project. The parent can also be a group (Premium and Ultimate only). ' \ + 'Example `IssueParentID` are `"gid://gitlab/Project/1"` and `"gid://gitlab/Group/1"`.' argument :ids, [::Types::GlobalIDType[::Issue]], required: true, description: 'Global ID array of the issues that will be updated. ' \ - "IDs that the user can\'t update will be ignored. A max of #{MAX_ISSUES} can be provided." + "IDs that the user can\'t update will be ignored. A max of #{MAX_ISSUES} can be provided." argument :assignee_ids, [::Types::GlobalIDType[::User]], required: false, description: 'Global ID array of the users that will be assigned to the given issues. ' \ - 'Existing assignees will be replaced with the ones on this list.' + 'Existing assignees will be replaced with the ones on this list.' argument :milestone_id, ::Types::GlobalIDType[::Milestone], required: false, diff --git a/app/graphql/mutations/issues/update.rb b/app/graphql/mutations/issues/update.rb index 1f45c0bfa2e..4f2656ce02a 100644 --- a/app/graphql/mutations/issues/update.rb +++ b/app/graphql/mutations/issues/update.rb @@ -35,7 +35,7 @@ module Mutations argument :time_estimate, GraphQL::Types::String, required: false, description: 'Estimated time to complete the issue. ' \ - 'Use `null` or `0` to remove the current estimate.' + 'Use `null` or `0` to remove the current estimate.' def resolve(project_path:, iid:, **args) issue = authorized_find!(project_path: project_path, iid: iid) diff --git a/app/graphql/mutations/members/projects/bulk_update.rb b/app/graphql/mutations/members/projects/bulk_update.rb index 9bf7968670e..db40c94f1a1 100644 --- a/app/graphql/mutations/members/projects/bulk_update.rb +++ b/app/graphql/mutations/members/projects/bulk_update.rb @@ -6,7 +6,7 @@ module Mutations class BulkUpdate < BulkUpdateBase graphql_name 'ProjectMemberBulkUpdate' description 'Updates multiple members of a project. ' \ - 'To use this mutation, you must have at least the Maintainer role.' + 'To use this mutation, you must have at least the Maintainer role.' authorize :admin_project_member diff --git a/app/graphql/mutations/merge_requests/update.rb b/app/graphql/mutations/merge_requests/update.rb index 54e56a44609..9f057e83d60 100644 --- a/app/graphql/mutations/merge_requests/update.rb +++ b/app/graphql/mutations/merge_requests/update.rb @@ -29,7 +29,7 @@ module Mutations argument :time_estimate, GraphQL::Types::String, required: false, description: 'Estimated time to complete the merge request. ' \ - 'Use `null` or `0` to remove the current estimate.' + 'Use `null` or `0` to remove the current estimate.' def resolve(project_path:, iid:, **args) merge_request = authorized_find!(project_path: project_path, iid: iid) diff --git a/app/graphql/types/ci/detailed_status_type.rb b/app/graphql/types/ci/detailed_status_type.rb index 02f0a870b40..a4073cae067 100644 --- a/app/graphql/types/ci/detailed_status_type.rb +++ b/app/graphql/types/ci/detailed_status_type.rb @@ -28,7 +28,7 @@ module Types description: 'Icon of the status.', deprecated: { reason: 'The `icon` attribute is deprecated. Use `name` to ' \ - 'identify the status to display instead', + 'identify the status to display instead', milestone: '16.4' } field :id, GraphQL::Types::String, null: false, diff --git a/app/graphql/types/ci/job_token_scope/direction_enum.rb b/app/graphql/types/ci/job_token_scope/direction_enum.rb index f52cf891af8..8025fa641a0 100644 --- a/app/graphql/types/ci/job_token_scope/direction_enum.rb +++ b/app/graphql/types/ci/job_token_scope/direction_enum.rb @@ -14,7 +14,7 @@ module Types value 'INBOUND', value: :inbound, description: 'Target projects in the inbound allowlist can access the scope project ' \ - 'through their job tokens.' + 'through their job tokens.' end end end diff --git a/app/graphql/types/ci/job_token_scope_type.rb b/app/graphql/types/ci/job_token_scope_type.rb index ddc823f8fe7..96176a4ca02 100644 --- a/app/graphql/types/ci/job_token_scope_type.rb +++ b/app/graphql/types/ci/job_token_scope_type.rb @@ -27,30 +27,30 @@ module Types Types::ProjectType.connection_type, null: false, description: "Allowlist of projects that can access the current project " \ - "by authenticating with a CI/CD job token.", + "by authenticating with a CI/CD job token.", method: :inbound_projects field :groups_allowlist, Types::GroupType.connection_type, null: false, description: "Allowlist of groups that can access the current project " \ - "by authenticating with a CI/CD job token.", + "by authenticating with a CI/CD job token.", method: :groups field :inbound_allowlist_count, GraphQL::Types::Int, null: false, description: "Count of projects that can access the current project " \ - "by authenticating with a CI/CD job token. " \ - "The count does not include nested projects.", + "by authenticating with a CI/CD job token. " \ + "The count does not include nested projects.", method: :inbound_projects_count field :groups_allowlist_count, GraphQL::Types::Int, null: false, description: "Count of groups that can access the current project " \ - "by authenticating with a CI/CD job token. " \ - "The count does not include subgroups.", + "by authenticating with a CI/CD job token. " \ + "The count does not include subgroups.", method: :groups_count end end diff --git a/app/graphql/types/ci/job_trace_type.rb b/app/graphql/types/ci/job_trace_type.rb index 62fb9340b53..c905c9dd6ac 100644 --- a/app/graphql/types/ci/job_trace_type.rb +++ b/app/graphql/types/ci/job_trace_type.rb @@ -11,8 +11,8 @@ module Types field :html_summary, GraphQL::Types::String, null: false, alpha: { milestone: '15.11' }, description: 'HTML summary that contains the tail lines of the trace. ' \ - "Returns at most #{MAX_SIZE_KB}KB of raw bytes from the trace. " \ - 'The returned string might start with an unexpected invalid UTF-8 code point due to truncation.' do + "Returns at most #{MAX_SIZE_KB}KB of raw bytes from the trace. " \ + 'The returned string might start with an unexpected invalid UTF-8 code point due to truncation.' do argument :last_lines, Integer, required: false, default_value: 10, description: 'Number of tail lines to return, up to a maximum of 100 lines.' diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index 304c6f422dc..2c86410103d 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -93,7 +93,7 @@ module Types field :previous_stage_jobs_or_needs, Types::Ci::JobNeedUnion.connection_type, null: true, description: 'Jobs that must complete before the job runs. Returns `BuildNeed`, ' \ - 'which is the needed jobs if the job uses the `needs` keyword, or the previous stage jobs otherwise.', + 'which is the needed jobs if the job uses the `needs` keyword, or the previous stage jobs otherwise.', deprecated: { reason: 'Replaced by previousStageJobs and needs fields', milestone: '16.4' } field :ref_name, GraphQL::Types::String, null: true, description: 'Ref name of the job.' diff --git a/app/graphql/types/ci/runner_membership_filter_enum.rb b/app/graphql/types/ci/runner_membership_filter_enum.rb index c0bf3abf4bf..3da895b7a51 100644 --- a/app/graphql/types/ci/runner_membership_filter_enum.rb +++ b/app/graphql/types/ci/runner_membership_filter_enum.rb @@ -18,7 +18,7 @@ module Types value 'ALL_AVAILABLE', description: "Include all runners. This list includes runners for all projects in the group " \ - "and subgroups, as well as for the parent groups and instance.", + "and subgroups, as well as for the parent groups and instance.", value: :all_available, alpha: { milestone: '15.5' } end diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 8ffffdc798c..82c7023537f 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -33,6 +33,20 @@ module AuthHelper Gitlab::Auth.omniauth_enabled? end + def enabled_button_based_providers_for_signup + if Gitlab.config.omniauth.allow_single_sign_on.is_a?(Array) + enabled_button_based_providers & Gitlab.config.omniauth.allow_single_sign_on + elsif Gitlab.config.omniauth.allow_single_sign_on + enabled_button_based_providers + else + [] + end + end + + def signup_button_based_providers_enabled? + omniauth_enabled? && enabled_button_based_providers_for_signup.any? + end + def provider_has_custom_icon?(name) icon_for_provider(name.to_s) end diff --git a/app/helpers/groups/group_members_helper.rb b/app/helpers/groups/group_members_helper.rb index 0fb7dc3805a..1bba9d789f5 100644 --- a/app/helpers/groups/group_members_helper.rb +++ b/app/helpers/groups/group_members_helper.rb @@ -14,7 +14,7 @@ module Groups::GroupMembersHelper banned:, include_relations:, search:, - pending_members:, + pending_members_count:, placeholder_users: ) { diff --git a/app/helpers/projects/project_members_helper.rb b/app/helpers/projects/project_members_helper.rb index d9bb03c2185..68690d47062 100644 --- a/app/helpers/projects/project_members_helper.rb +++ b/app/helpers/projects/project_members_helper.rb @@ -19,7 +19,7 @@ module Projects::ProjectMembersHelper private def project_members_app_data( - project, members:, invited:, access_requests:, include_relations:, search:, pending_members:) # rubocop:disable Lint/UnusedMethodArgument -- Argument used in EE + project, members:, invited:, access_requests:, include_relations:, search:, pending_members_count:) # rubocop:disable Lint/UnusedMethodArgument -- Argument used in EE { user: project_members_list_data(project, members, { param_name: :page, params: { search_groups: nil } }), group: project_group_links_list_data(project, include_relations, search), diff --git a/app/models/user.rb b/app/models/user.rb index 52bc70c7ab0..fc9e64be86a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1297,13 +1297,21 @@ class User < ApplicationRecord direct_groups_cte = Gitlab::SQL::CTE.new(:direct_groups, groups) direct_groups_cte_alias = direct_groups_cte.table.alias(Group.table_name) + groups_from_authorized_projects = Group.id_in(authorized_projects.select(:namespace_id)) + groups_from_shares = Group.joins(:shared_with_group_links) + .where(group_group_links: { shared_with_group_id: Group.from(direct_groups_cte_alias) }) + + if Feature.enabled?(:fix_user_authorized_groups, self) + groups_from_authorized_projects = groups_from_authorized_projects.self_and_ancestors + groups_from_shares = groups_from_shares.self_and_descendants + end + Group .with(direct_groups_cte.to_arel) .from_union([ Group.from(direct_groups_cte_alias).self_and_descendants, - Group.id_in(authorized_projects.select(:namespace_id)), - Group.joins(:shared_with_group_links) - .where(group_group_links: { shared_with_group_id: Group.from(direct_groups_cte_alias) }) + groups_from_authorized_projects, + groups_from_shares ]) end end diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index c8636bacd5f..6ab13fc51af 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -13,12 +13,12 @@ url: registration_path(resource_name, ::Onboarding::Status.registration_path_params(params: params)), button_text: _('Continue') - - if omniauth_enabled? && button_based_providers_enabled? + - if signup_button_based_providers_enabled? .gl-text-center.gl-pt-5 %label.gl-font-normal = _('Continue with:') .gl-flex.gl-flex-col.gl-gap-3 - - enabled_button_based_providers.each do |provider| + - enabled_button_based_providers_for_signup.each do |provider| = render 'devise/shared/omniauth_provider_button', href: omniauth_authorize_path(:user, provider, ::Onboarding::Status.registration_path_params(params: params)), provider: provider, diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 87ba30b8835..9ff3f0421e4 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -32,7 +32,7 @@ banned: @banned || [], include_relations: @include_relations, search: params[:search_groups], - pending_members: @pending_promotion_members, + pending_members_count: @pending_promotion_members_count, placeholder_users: @placeholder_users_count ).to_json } } = gl_loading_icon(css_class: 'gl-my-5', size: 'md') diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 8a4e9de95e1..f0c38b19cfe 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -46,5 +46,5 @@ access_requests: @requesters, include_relations: @include_relations, search: params[:search_groups], - pending_members: @pending_promotion_members) } } + pending_members_count: @pending_promotion_members_count) } } = gl_loading_icon(css_class: 'gl-my-5', size: 'md') diff --git a/config/feature_flags/gitlab_com_derisk/fix_user_authorized_groups.yml b/config/feature_flags/gitlab_com_derisk/fix_user_authorized_groups.yml new file mode 100644 index 00000000000..1605cb21014 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/fix_user_authorized_groups.yml @@ -0,0 +1,9 @@ +--- +name: fix_user_authorized_groups +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/482866 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165276 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/483175 +milestone: '17.5' +group: group::project management +type: gitlab_com_derisk +default_enabled: false diff --git a/doc/administration/admin_area.md b/doc/administration/admin_area.md index 50f342718ce..f8e7f99eec3 100644 --- a/doc/administration/admin_area.md +++ b/doc/administration/admin_area.md @@ -179,7 +179,7 @@ The following data is included in the export: - Type - Path - Access level ([Project](../user/permissions.md#project-members-permissions) and [Group](../user/permissions.md#group-members-permissions)) -- Date of last activity. For a list of activities that populate this column, see the [Users API documentation](../api/users.md#get-a-list-of-a-users-activity). +- Date of last activity. For a list of activities that populate this column, see the [Users API documentation](../api/users.md#list-a-users-activity). Only the first 100,000 user accounts are exported. diff --git a/doc/api/group_service_accounts.md b/doc/api/group_service_accounts.md index 3659f0fb9b5..6bc995a18f0 100644 --- a/doc/api/group_service_accounts.md +++ b/doc/api/group_service_accounts.md @@ -141,7 +141,7 @@ Parameters: | `user_id` | integer | yes | The ID of a service account user. | | `name` | string | yes | The name of the personal access token. | | `scopes` | array | yes | Array of scopes of the personal access token. See [personal access token scopes](../user/profile/personal_access_tokens.md#personal-access-token-scopes) for possible values. | -| `expires_at` | date | no | The personal access token expiry date. When left blank, the token follows the [standard rule of expiry for personal access tokens](../user/profile/personal_access_tokens.md#access-token-expiration). | +| `expires_at` | date | no | The personal access token expiry date. When left blank, the token follows the [standard rule of expiry for personal access tokens](../user/profile/personal_access_tokens.md#access-token-expiration). To specify no expiration date, set this value to `null`. | Example request: diff --git a/doc/api/invitations.md b/doc/api/invitations.md index 0af0a7d6d43..ca52754630b 100644 --- a/doc/api/invitations.md +++ b/doc/api/invitations.md @@ -73,6 +73,24 @@ When there was any error sending the email: } ``` +NOTE: + +When the ability to **Manage Non-Billable Promotions** is enabled, the newly invited member will be pending administrator approval if the role is billable. To enable **Manage Non-Billable Promotions**: + +1. Enable `enable_member_promotion_management` Application Setting +1. Enable `member_promotion_management` Feature Flag + +Example response: + +```json +{ + "queued_users": { + "username_1": "Request queued for administrator approval." + }, + "status": "success" +} +``` + ## List all invitations pending for a group or project Gets a list of invited group or project members viewable by the authenticated user. diff --git a/doc/api/members.md b/doc/api/members.md index 673449405b9..7b2de9db0a7 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -626,6 +626,49 @@ Example response: } ``` +NOTE: + +When the ability to **Manage Non-Billable Promotions** is enabled, the newly added member will be pending administrator approval if the role is billable. To enable **Manage Non-Billable Promotions**: + +1. Enable `enable_member_promotion_management` Application Setting +1. Enable `member_promotion_management` Feature Flag + +Example of queueing a single user: + +```shell +curl --request POST --header "PRIVATE-TOKEN: " \ + --data "user_id=1&access_level=30" "https://gitlab.example.com/api/v4/groups/:id/members" +curl --request POST --header "PRIVATE-TOKEN: " \ + --data "user_id=1&access_level=30" "https://gitlab.example.com/api/v4/projects/:id/members" +``` + +```json +{ + "message":{ + "username_1":"Request queued for administrator approval." + } +} +``` + +Example of queueing multiple users: + +```shell +curl --request POST --header "PRIVATE-TOKEN: " \ + --data "user_id=1,2&access_level=30" "https://gitlab.example.com/api/v4/groups/:id/members" +curl --request POST --header "PRIVATE-TOKEN: " \ + --data "user_id=1,2&access_level=30" "https://gitlab.example.com/api/v4/projects/:id/members" +``` + +```json +{ + "queued_users": { + "username_1": "Request queued for administrator approval.", + "username_2": "Request queued for administrator approval." + }, + "status": "success" +} +``` + ## Edit a member of a group or project Updates a member of a group or project. @@ -674,6 +717,23 @@ Example response: } ``` +NOTE: + +When the ability to **Manage Non-Billable Promotions** is enabled, if the role is changed to a billable one, the request will be sent to administrator approval. To enable **Manage Non-Billable Promotions**: + +1. Enable `enable_member_promotion_management` Application Setting +1. Enable `member_promotion_management` Feature Flag + +Example response: + +```json +{ + "message":{ + "username_1":"Request queued for administrator approval." + } +} +``` + ### Set override flag for a member of a group By default, the access level of LDAP group members is set to the value specified diff --git a/doc/api/users.md b/doc/api/users.md index 245e28e2a83..f81f23bdf7c 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -547,196 +547,7 @@ You can include the user's [custom attributes](custom_attributes.md) in the resp GET /users/:id?with_custom_attributes=true ``` -## Create a user - -DETAILS: -**Tier:** Free, Premium, Ultimate -**Offering:** Self-managed, GitLab Dedicated - -> - Ability to create an auditor user was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366404) in GitLab 15.3. - -Create a user. - -Prerequisites: - -- You must be an administrator. - -When you create a user, you must specify at least one of the following: - -- `password` -- `reset_password` -- `force_random_password` - -If `reset_password` and `force_random_password` are both `false`, then `password` is required. - -`force_random_password` and `reset_password` take priority over `password`. Also, `reset_password` and -`force_random_password` can be used together. - -NOTE: -`private_profile` defaults to the value of the -[Set profiles of new users to private by default](../administration/settings/account_and_limit_settings.md#set-profiles-of-new-users-to-private-by-default) setting. -`bio` defaults to `""` instead of `null`. - -```plaintext -POST /users -``` - -Supported attributes: - -| Attribute | Required | Description | -|:-------------------------------------|:---------|:------------| -| `admin` | No | User is an administrator. Valid values are `true` or `false`. Defaults to false. | -| `auditor` | No | User is an auditor. Valid values are `true` or `false`. Defaults to false. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366404) in GitLab 15.3. Premium and Ultimate only. | -| `avatar` | No | Image file for user's avatar | -| `bio` | No | User's biography | -| `can_create_group` | No | User can create top-level groups - true or false | -| `color_scheme_id` | No | User's color scheme for the file viewer (for more information, see the [user preference documentation](../user/profile/preferences.md#change-the-syntax-highlighting-theme)) | -| `commit_email` | No | User's commit email address | -| `email` | Yes | Email | -| `extern_uid` | No | External UID | -| `external` | No | Flags the user as external - true or false (default) | -| `extra_shared_runners_minutes_limit` | No | Can be set by administrators only. Additional compute minutes for this user. Premium and Ultimate only. | -| `force_random_password` | No | Set user password to a random value - true or false (default) | -| `group_id_for_saml` | No | ID of group where SAML has been configured | -| `linkedin` | No | LinkedIn | -| `location` | No | User's location | -| `name` | Yes | Name | -| `note` | No | Administrator notes for this user | -| `organization` | No | Organization name | -| `password` | No | Password | -| `private_profile` | No | User's profile is private - true or false. The default value is determined by [this](../administration/settings/account_and_limit_settings.md#set-profiles-of-new-users-to-private-by-default) setting. | -| `projects_limit` | No | Number of projects user can create | -| `pronouns` | No | User's pronouns | -| `provider` | No | External provider name | -| `public_email` | No | User's public email address | -| `reset_password` | No | Send user password reset link - true or false(default) | -| `shared_runners_minutes_limit` | No | Can be set by administrators only. Maximum number of monthly compute minutes for this user. Can be `nil` (default; inherit system default), `0` (unlimited), or `> 0`. Premium and Ultimate only. | -| `skip_confirmation` | No | Skip confirmation - true or false (default) | -| `skype` | No | Skype ID | -| `theme_id` | No | GitLab theme for the user (for more information, see the [user preference documentation](../user/profile/preferences.md#change-the-color-theme) for more information) | -| `twitter` | No | X (formerly Twitter) account | -| `discord` | No | Discord account | -| `username` | Yes | Username | -| `view_diffs_file_by_file` | No | Flag indicating the user sees only one file diff per page | -| `website_url` | No | Website URL | - -## Modify a user - -DETAILS: -**Tier:** Free, Premium, Ultimate -**Offering:** Self-managed, GitLab Dedicated - -> - Ability to modify an auditor user was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366404) in GitLab 15.3. - -Modify an existing user. - -Prerequisites: - -- You must be an administrator. - -The `email` field is the user's primary email address. You can only change this field to an already-added secondary -email address for that user. To add more email addresses to the same user, use the [add email endpoint](user_email_addresses.md#add-an-email-address). - -```plaintext -PUT /users/:id -``` - -Supported attributes: - -| Attribute | Required | Description | -|:-------------------------------------|:---------|:------------| -| `admin` | No | User is an administrator. Valid values are `true` or `false`. Defaults to false. | -| `auditor` | No | User is an auditor. Valid values are `true` or `false`. Defaults to false. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366404) in GitLab 15.3.(default) Premium and Ultimate only. | -| `avatar` | No | Image file for user's avatar | -| `bio` | No | User's biography | -| `can_create_group` | No | User can create groups - true or false | -| `color_scheme_id` | No | User's color scheme for the file viewer (for more information, see the [user preference documentation](../user/profile/preferences.md#change-the-syntax-highlighting-theme) for more information) | -| `commit_email` | No | User's commit email. Set to `_private` to use the private commit email. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/375148) in GitLab 15.5. | -| `email` | No | Email | -| `extern_uid` | No | External UID | -| `external` | No | Flags the user as external - true or false (default) | -| `extra_shared_runners_minutes_limit` | No | Can be set by administrators only. Additional compute minutes for this user. Premium and Ultimate only. | -| `group_id_for_saml` | No | ID of group where SAML has been configured | -| `id` | Yes | ID of the user | -| `linkedin` | No | LinkedIn | -| `location` | No | User's location | -| `name` | No | Name | -| `note` | No | Administration notes for this user | -| `organization` | No | Organization name | -| `password` | No | Password | -| `private_profile` | No | User's profile is private - true or false. | -| `projects_limit` | No | Limit projects each user can create | -| `pronouns` | No | Pronouns | -| `provider` | No | External provider name | -| `public_email` | No | Public email of the user (must be already verified) | -| `shared_runners_minutes_limit` | No | Can be set by administrators only. Maximum number of monthly compute minutes for this user. Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0`. Premium and Ultimate only. | -| `skip_reconfirmation` | No | Skip reconfirmation - true or false (default) | -| `skype` | No | Skype ID | -| `theme_id` | No | GitLab theme for the user (for more information, see the [user preference documentation](../user/profile/preferences.md#change-the-color-theme) for more information) | -| `twitter` | No | X (formerly Twitter) account | -| `discord` | No | Discord account | -| `username` | No | Username | -| `view_diffs_file_by_file` | No | Flag indicating the user sees only one file diff per page | -| `website_url` | No | Website URL | - -If you update a user's password, they are forced to change it when they next sign in. - -Returns a `404` error, even in cases where a `409` (Conflict) would be more appropriate. -For example, when renaming the email address to an existing one. - -## Delete authentication identity from a user - -DETAILS: -**Tier:** Free, Premium, Ultimate -**Offering:** Self-managed, GitLab Dedicated - -Delete a user's authentication identity using the provider name associated with that identity. - -Prerequisites: - -- You must be an administrator. - -```plaintext -DELETE /users/:id/identities/:provider -``` - -Supported attributes: - -| Attribute | Type | Required | Description | -|:-----------|:--------|:---------|:------------| -| `id` | integer | yes | ID of a user | -| `provider` | string | yes | External provider name | - -## Delete a user - -DETAILS: -**Tier:** Free, Premium, Ultimate -**Offering:** Self-managed, GitLab Dedicated - -Delete a user. - -Prerequisites: - -- You must be an administrator. - -Returns: - -- `204 No Content` status code if the operation was successful. -- `404` if the resource was not found. -- `409` if the user cannot be soft deleted. - -```plaintext -DELETE /users/:id -``` - -Supported attributes: - -| Attribute | Type | Required | Description | -|:--------------|:--------|:---------|:------------| -| `id` | integer | yes | ID of a user | -| `hard_delete` | boolean | no | If true, contributions that would usually be [moved to Ghost User](../user/profile/account/delete_account.md#associated-records) are deleted instead, as well as groups owned solely by this user. | - -## List the current user +## Get the current user Get the current user. @@ -879,6 +690,172 @@ parameters: - `provisioned_by_group_id` - `using_license_seat` +## Create a user + +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** Self-managed, GitLab Dedicated + +> - Ability to create an auditor user was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366404) in GitLab 15.3. + +Create a user. + +Prerequisites: + +- You must be an administrator. + +When you create a user, you must specify at least one of the following: + +- `password` +- `reset_password` +- `force_random_password` + +If `reset_password` and `force_random_password` are both `false`, then `password` is required. + +`force_random_password` and `reset_password` take priority over `password`. Also, `reset_password` and +`force_random_password` can be used together. + +NOTE: +`private_profile` defaults to the value of the +[Set profiles of new users to private by default](../administration/settings/account_and_limit_settings.md#set-profiles-of-new-users-to-private-by-default) setting. +`bio` defaults to `""` instead of `null`. + +```plaintext +POST /users +``` + +Supported attributes: + +| Attribute | Required | Description | +|:-------------------------------------|:---------|:------------| +| `admin` | No | User is an administrator. Valid values are `true` or `false`. Defaults to false. | +| `auditor` | No | User is an auditor. Valid values are `true` or `false`. Defaults to false. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366404) in GitLab 15.3. Premium and Ultimate only. | +| `avatar` | No | Image file for user's avatar | +| `bio` | No | User's biography | +| `can_create_group` | No | User can create top-level groups - true or false | +| `color_scheme_id` | No | User's color scheme for the file viewer (for more information, see the [user preference documentation](../user/profile/preferences.md#change-the-syntax-highlighting-theme)) | +| `commit_email` | No | User's commit email address | +| `email` | Yes | Email | +| `extern_uid` | No | External UID | +| `external` | No | Flags the user as external - true or false (default) | +| `extra_shared_runners_minutes_limit` | No | Can be set by administrators only. Additional compute minutes for this user. Premium and Ultimate only. | +| `force_random_password` | No | Set user password to a random value - true or false (default) | +| `group_id_for_saml` | No | ID of group where SAML has been configured | +| `linkedin` | No | LinkedIn | +| `location` | No | User's location | +| `name` | Yes | Name | +| `note` | No | Administrator notes for this user | +| `organization` | No | Organization name | +| `password` | No | Password | +| `private_profile` | No | User's profile is private - true or false. The default value is determined by [a setting](../administration/settings/account_and_limit_settings.md#set-profiles-of-new-users-to-private-by-default). | +| `projects_limit` | No | Number of projects user can create | +| `pronouns` | No | User's pronouns | +| `provider` | No | External provider name | +| `public_email` | No | User's public email address | +| `reset_password` | No | Send user password reset link - true or false(default) | +| `shared_runners_minutes_limit` | No | Can be set by administrators only. Maximum number of monthly compute minutes for this user. Can be `nil` (default; inherit system default), `0` (unlimited), or `> 0`. Premium and Ultimate only. | +| `skip_confirmation` | No | Skip confirmation - true or false (default) | +| `skype` | No | Skype ID | +| `theme_id` | No | GitLab theme for the user (for more information, see the [user preference documentation](../user/profile/preferences.md#change-the-color-theme) for more information) | +| `twitter` | No | X (formerly Twitter) account | +| `discord` | No | Discord account | +| `username` | Yes | Username | +| `view_diffs_file_by_file` | No | Flag indicating the user sees only one file diff per page | +| `website_url` | No | Website URL | + +## Modify a user + +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** Self-managed, GitLab Dedicated + +> - Ability to modify an auditor user was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366404) in GitLab 15.3. + +Modify an existing user. + +Prerequisites: + +- You must be an administrator. + +The `email` field is the user's primary email address. You can only change this field to an already-added secondary +email address for that user. To add more email addresses to the same user, use the [add email endpoint](user_email_addresses.md#add-an-email-address). + +```plaintext +PUT /users/:id +``` + +Supported attributes: + +| Attribute | Required | Description | +|:-------------------------------------|:---------|:------------| +| `admin` | No | User is an administrator. Valid values are `true` or `false`. Defaults to false. | +| `auditor` | No | User is an auditor. Valid values are `true` or `false`. Defaults to false. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/366404) in GitLab 15.3.(default) Premium and Ultimate only. | +| `avatar` | No | Image file for user's avatar | +| `bio` | No | User's biography | +| `can_create_group` | No | User can create groups - true or false | +| `color_scheme_id` | No | User's color scheme for the file viewer (for more information, see the [user preference documentation](../user/profile/preferences.md#change-the-syntax-highlighting-theme) for more information) | +| `commit_email` | No | User's commit email. Set to `_private` to use the private commit email. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/375148) in GitLab 15.5. | +| `email` | No | Email | +| `extern_uid` | No | External UID | +| `external` | No | Flags the user as external - true or false (default) | +| `extra_shared_runners_minutes_limit` | No | Can be set by administrators only. Additional compute minutes for this user. Premium and Ultimate only. | +| `group_id_for_saml` | No | ID of group where SAML has been configured | +| `id` | Yes | ID of the user | +| `linkedin` | No | LinkedIn | +| `location` | No | User's location | +| `name` | No | Name | +| `note` | No | Administration notes for this user | +| `organization` | No | Organization name | +| `password` | No | Password | +| `private_profile` | No | User's profile is private - true or false. | +| `projects_limit` | No | Limit projects each user can create | +| `pronouns` | No | Pronouns | +| `provider` | No | External provider name | +| `public_email` | No | Public email of the user (must be already verified) | +| `shared_runners_minutes_limit` | No | Can be set by administrators only. Maximum number of monthly compute minutes for this user. Can be `nil` (default; inherit system default), `0` (unlimited) or `> 0`. Premium and Ultimate only. | +| `skip_reconfirmation` | No | Skip reconfirmation - true or false (default) | +| `skype` | No | Skype ID | +| `theme_id` | No | GitLab theme for the user (for more information, see the [user preference documentation](../user/profile/preferences.md#change-the-color-theme) for more information) | +| `twitter` | No | X (formerly Twitter) account | +| `discord` | No | Discord account | +| `username` | No | Username | +| `view_diffs_file_by_file` | No | Flag indicating the user sees only one file diff per page | +| `website_url` | No | Website URL | + +If you update a user's password, they are forced to change it when they next sign in. + +Returns a `404` error, even in cases where a `409` (Conflict) would be more appropriate. +For example, when renaming the email address to an existing one. + +## Delete a user + +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** Self-managed, GitLab Dedicated + +Delete a user. + +Prerequisites: + +- You must be an administrator. + +Returns: + +- `204 No Content` status code if the operation was successful. +- `404` if the resource was not found. +- `409` if the user cannot be soft deleted. + +```plaintext +DELETE /users/:id +``` + +Supported attributes: + +| Attribute | Type | Required | Description | +|:--------------|:--------|:---------|:------------| +| `id` | integer | yes | ID of a user | +| `hard_delete` | boolean | no | If true, contributions that would usually be [moved to Ghost User](../user/profile/account/delete_account.md#associated-records) are deleted instead, and also groups owned solely by this user. | + ## Get your user status Get your user status. @@ -1039,6 +1016,52 @@ Supported attributes: | `show_whitespace_in_diffs` | Yes | Flag indicating the user sees whitespace changes in diffs. | | `pass_user_identities_to_ci_jwt` | Yes | Flag indicating the user passes their external identities as CI information. This attribute does not contain enough information to identify or authorize the user in an external system. The attribute is internal to GitLab, and must not be passed to third-party services. For more information and examples, see [Token Payload](../ci/secrets/id_token_authentication.md#token-payload). | +## Upload an avatar for yourself + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148130) in GitLab 17.0. + +Upload an avatar for yourself. + +Prerequisites: + +- You must be authenticated. + +```plaintext +PUT /user/avatar +``` + +Supported attributes: + +| Attribute | Type | Required | Description | +|:----------|:-------|:---------|:------------| +| `avatar` | string | Yes | The file to be uploaded. The ideal image size is 192 x 192 pixels. The maximum file size allowed is 200 KiB. | + +To upload an avatar from your file system, use the `--form` argument. This causes +cURL to post data using the header `Content-Type: multipart/form-data`. The +`file=` parameter must point to an image file on your file system and be +preceded by `@`. For example: + +Example request: + +```shell +curl --request PUT --header "PRIVATE-TOKEN: " \ + --form "avatar=@avatar.png" \ + --url "https://gitlab.example.com/api/v4/user/avatar" +``` + +Example response: + +```json +{ + "avatar_url": "http://gdk.test:3000/uploads/-/system/user/avatar/76/avatar.png", +} +``` + +Returns: + +- `200` if successful. +- `400 Bad Request` for file sizes greater than 200 KiB. + ## Get a count of your assigned issues, merge requests, and reviews Get a count of your assigned issues, merge requests, and reviews. @@ -1111,7 +1134,7 @@ Example response: } ``` -## Get a list of a user's activity +## List a user's activity DETAILS: **Tier:** Free, Premium, Ultimate @@ -1174,7 +1197,7 @@ Example response: `last_activity_at` is deprecated. Use `last_activity_on` instead. -## Get a list of projects and groups that a user is a member of +## List projects and groups that a user is a member of DETAILS: **Tier:** Free, Premium, Ultimate @@ -1327,48 +1350,25 @@ Example response: } ``` -## Upload an avatar for yourself +## Delete authentication identity from a user -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/148130) in GitLab 17.0. +DETAILS: +**Tier:** Free, Premium, Ultimate +**Offering:** Self-managed, GitLab Dedicated -Upload an avatar for yourself. +Delete a user's authentication identity using the provider name associated with that identity. Prerequisites: -- You must be authenticated. +- You must be an administrator. ```plaintext -PUT /user/avatar +DELETE /users/:id/identities/:provider ``` Supported attributes: -| Attribute | Type | Required | Description | -|:----------|:-------|:---------|:------------| -| `avatar` | string | Yes | The file to be uploaded. The ideal image size is 192 x 192 pixels. The maximum file size allowed is 200 KiB. | - -To upload an avatar from your file system, use the `--form` argument. This causes -cURL to post data using the header `Content-Type: multipart/form-data`. The -`file=` parameter must point to an image file on your file system and be -preceded by `@`. For example: - -Example request: - -```shell -curl --request PUT --header "PRIVATE-TOKEN: " \ - --form "avatar=@avatar.png" \ - --url "https://gitlab.example.com/api/v4/user/avatar" -``` - -Example response: - -```json -{ - "avatar_url": "http://gdk.test:3000/uploads/-/system/user/avatar/76/avatar.png", -} -``` - -Returns: - -- `200` if successful. -- `400 Bad Request` for file sizes greater than 200 KiB. +| Attribute | Type | Required | Description | +|:-----------|:--------|:---------|:------------| +| `id` | integer | yes | ID of a user | +| `provider` | string | yes | External provider name | diff --git a/doc/ci/runners/configure_runners.md b/doc/ci/runners/configure_runners.md index 5f5cec7f503..61373e7ae2e 100644 --- a/doc/ci/runners/configure_runners.md +++ b/doc/ci/runners/configure_runners.md @@ -189,8 +189,8 @@ you use to provision and register new values. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/30942) in GitLab 15.3 [with a flag](../../administration/feature_flags.md) named `enforce_runner_token_expires_at`. Disabled by default. > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/377902) in GitLab 15.5. Feature flag `enforce_runner_token_expires_at` removed. -Each runner has an [runner authentication token](../../api/runners.md#registration-and-authentication-tokens) -to connect with the GitLab instance. +Each runner uses a [runner authentication token](../../api/runners.md#registration-and-authentication-tokens) +to connect to and authenticate with a GitLab instance. To help prevent the token from being compromised, you can have the token rotate automatically at specified intervals. When the tokens are rotated, diff --git a/doc/development/documentation/testing/index.md b/doc/development/documentation/testing/index.md index 91a946636dc..8e1f5eab812 100644 --- a/doc/development/documentation/testing/index.md +++ b/doc/development/documentation/testing/index.md @@ -357,14 +357,16 @@ in the relevant projects: - - -We also run some documentation tests in the: +We also run some documentation tests in these projects: -- GitLab CLI project: -- GitLab Development Kit project: - . -- Gitaly project: . +- GitLab CLI: +- GitLab Development Kit: + +- Gitaly: - GitLab Duo Plugin for JetBrains: -- GitLab Workflow extension for VS Code project: . -- GitLab Plugin for Neovim project: . -- GitLab Language Server project: . -- GitLab Extension for Visual Studio project: . +- GitLab Workflow extension for VS Code: +- GitLab Plugin for Neovim: +- GitLab Language Server: +- GitLab Extension for Visual Studio: +- AI Gateway: +- Prompt Library: diff --git a/doc/development/redis.md b/doc/development/redis.md index ad6910eae51..c1927b985db 100644 --- a/doc/development/redis.md +++ b/doc/development/redis.md @@ -30,7 +30,7 @@ can be used for inter-process communication in cases where [PostgreSQL](sql.md) is less appropriate. For example, transient state or data that is written much more often than it is read. -If [Geo](geo.md) is enabled, each Geo node gets its own, independent Redis +If [Geo](geo.md) is enabled, each Geo site gets its own, independent Redis database. We have [development documentation on adding a new Redis instance](redis/new_redis_instance.md). diff --git a/doc/user/markdown.md b/doc/user/markdown.md index f87c814ccf3..77687585b60 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -50,7 +50,8 @@ You can use GitLab Flavored Markdown in the following areas: You can also use other rich text files in GitLab. You might have to install a dependency to do so. For more information, see the [`gitlab-markup` gem project](https://gitlab.com/gitlab-org/gitlab-markup). -Support for improvements to Markdown preview when using GitLab Flavored Markdown in the Web IDE is proposed in [issue 645](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/645). +Support for GitLab Flavored Markdown preview in the Web IDE is proposed in +[issue 645](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/645). ### Differences between GitLab Flavored Markdown and standard Markdown diff --git a/doc/user/project/repository/branches/protected.md b/doc/user/project/repository/branches/protected.md index 13f20521561..fb0cda0553e 100644 --- a/doc/user/project/repository/branches/protected.md +++ b/doc/user/project/repository/branches/protected.md @@ -37,17 +37,13 @@ When a branch is protected, the default behavior enforces these restrictions on |:-------------------------|:----------------------------------------| | Protect a branch | At least the Maintainer role. | | Push to the branch | Anyone with **Allowed** permission. (1) | -| Force push to the branch | No one. (3) | +| Force push to the branch | No one. | | Delete the branch | No one. (2) | 1. Users with the Developer role can create a project in a group, but might not be allowed to initially push to the [default branch](default.md). 1. No one can delete a protected branch using Git commands, however, users with at least Maintainer role can [delete a protected branch from the UI or API](#delete-a-protected-branch). -1. If the `group_protected_branches` feature flag is enabled _and_ the same branch is - protected at both the group and project levels, force push settings configured - for that branch at the project level are ignored. All other protections continue - to use project level settings. You can implement a [merge request approval policy](../../../application_security/policies/merge_request_approval_policies.md#approval_settings) to prevent protected branches being unprotected or deleted. @@ -136,10 +132,7 @@ To make it available, an administrator can named `group_protected_branches`. On GitLab.com and GitLab Dedicated, this feature is not available. Group owners can create protected branches for a group. These settings are inherited -by all projects in the group and can't be overridden by project settings. If a -specific branch is configured with **Allowed to force push** settings at both the -group and project levels, the **Allowed to force push** setting at the _project_ level -is ignored in favor of the group level setting. +by all projects in the group and can't be overridden by project settings. Prerequisites: @@ -310,11 +303,6 @@ As the most permissive option determines the behavior, the resulting permissions and controls branch behavior as a result. Even though the branch also matched `v1.x` and `v*` (which each have stricter permissions), any user that can push to this branch can also force push. -NOTE: -Force push settings for a branch at the project level are overridden by group level settings -if the `group_protected_branches` feature flag is enabled and a group owner has set -[group level protection for the same branch](#for-all-projects-in-a-group). - ## Require Code Owner approval on a protected branch DETAILS: diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index a23726537bf..814471e18e3 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -25,7 +25,8 @@ For a more basic implementation, see [Web Editor](../repository/web_editor.md). To pair the Web IDE with a remote development environment, see [Remote development](../remote_development/index.md). -Support for improvements to Markdown preview when using GitLab Flavored Markdown in the Web IDE is proposed in [issue 645](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/645). +Support for [GitLab Flavored Markdown](../../markdown.md) preview in the Web IDE is proposed in +[issue 645](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/645). ## Open the Web IDE diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 0bfb79b0470..c37f34169f3 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -577,8 +577,8 @@ module API render_api_error!('201 Created', 201) end - def accepted! - render_api_error!('202 Accepted', 202) + def accepted!(message = '202 Accepted') + render_api_error!(message, 202) end def render_validation_error!(models, status = 400) diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index 1920a79b4c3..eeb3f2e2019 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -105,12 +105,16 @@ module API member = instance.single_member render_validation_error!(member) if member&.invalid? - # if errors occurred besides model validations or authorization failures, - # render those appropriately - if result[:status] == :error - render_structured_api_error!(result, :bad_request) + present_add_single_member_response(result, member) + end + + def present_put_membership_response(result) + updated_member = result[:members].first + + if result[:status] == :success + present_members updated_member else - present_members(member) + render_validation_error!(updated_member) end end @@ -134,6 +138,16 @@ module API def member_already_exists?(source, user_id) source.members.exists?(user_id: user_id) # rubocop: disable CodeReuse/ActiveRecord end + + def present_add_single_member_response(result, member) + # if errors occurred besides model validations or authorization failures, + # render those appropriately + if result[:status] == :error + render_structured_api_error!(result, :bad_request) + else + present_members(member) + end + end end end end diff --git a/lib/api/members.rb b/lib/api/members.rb index 80957eaa616..a72b8958038 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -152,13 +152,7 @@ module API .new(current_user, declared_params(include_missing: false)) .execute(member) - updated_member = result[:members].first - - if result[:status] == :success - present_members updated_member - else - render_validation_error!(updated_member) - end + present_put_membership_response(result) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 55f9b8cb1dc..6c044a82647 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -37282,6 +37282,9 @@ msgstr "" msgid "ObservabilityMetrics|Error: Failed to load metrics details. Try reloading the page." msgstr "" +msgid "ObservabilityMetrics|Error: Unable to create metric snapshot image." +msgstr "" + msgid "ObservabilityMetrics|Failed to load metrics." msgstr "" @@ -62002,6 +62005,11 @@ msgid_plural "WorkItem|%{workItemType} is blocked by %{itemCount} items" msgstr[0] "" msgstr[1] "" +msgid "WorkItem|+%d more item" +msgid_plural "WorkItem|+%d more items" +msgstr[0] "" +msgstr[1] "" + msgid "WorkItem|A non-confidential %{workItemType} cannot be assigned to a confidential parent %{parentWorkItemType}." msgstr "" diff --git a/package.json b/package.json index eb2dfb318ff..22f9f380d66 100644 --- a/package.json +++ b/package.json @@ -184,6 +184,7 @@ "mermaid": "10.7.0", "micromatch": "^4.0.5", "minimatch": "^3.0.4", + "modern-screenshot": "^4.4.39", "monaco-editor": "^0.30.1", "monaco-editor-webpack-plugin": "^6.0.0", "monaco-yaml": "4.0.0", diff --git a/qa/knapsack/master_report.json b/qa/knapsack/master_report.json index c8da6a9e93e..b8dd4169a92 100644 --- a/qa/knapsack/master_report.json +++ b/qa/knapsack/master_report.json @@ -1,258 +1,251 @@ { - "qa/specs/features/api/10_govern/group_access_token_spec.rb": 12.397871947000112, - "qa/specs/features/api/10_govern/project_access_token_spec.rb": 37.23903082000004, - "qa/specs/features/api/1_manage/migration/gitlab_migration_project_spec.rb": 121.145597555, - "qa/specs/features/api/1_manage/rate_limits_spec.rb": 2.453305433999958, - "qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 7.209470354000132, - "qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb": 10.633708354999953, - "qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 9.515109185000028, - "qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 12.95933420700021, - "qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb": 10.712993371000266, - "qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb": 22.123816485999896, - "qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 5.575647242000059, - "qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 8.450835704999918, - "qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 6.0398892079997495, - "qa/specs/features/api/3_create/repository/files_spec.rb": 2.84329607199993, - "qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 5.720794561000048, - "qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 8.671697718000132, - "qa/specs/features/api/3_create/repository/storage_size_spec.rb": 14.04211848500006, - "qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 2.0491826810000475, - "qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 56.58822126100017, - "qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 5.542681564000077, - "qa/specs/features/api/4_verify/file_variable_spec.rb": 29.22528023399991, - "qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 15.794122663999588, - "qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb": 70.58858790399995, - "qa/specs/features/api/9_data_stores/users_spec.rb": 0.4996049840001433, - "qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 10.296608160999995, - "qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 49.564313320999986, - "qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 31.97967125299965, - "qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 8.875357668999982, - "qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 68.2847945389999, - "qa/specs/features/browser_ui/10_govern/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb": 431.209770424, - "qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 181.24613914499992, - "qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 12.759093617999952, - "qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 29.862426868000057, - "qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 36.48008158399989, - "qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 18.323326968999936, - "qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 9.91786422899986, - "qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 62.74245996399986, - "qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 12.972298938999984, - "qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 21.18130115100007, - "qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 12.365103047000048, - "qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 12.350918417000003, - "qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 16.988709157000017, - "qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 13.298123846999943, - "qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 37.74972280200018, - "qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 18.49045557999989, - "qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 11.60536830400008, - "qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 16.35631897600001, - "qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 12.273179357999993, - "qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 12.95962527399979, - "qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 12.479864686999917, - "qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 10.416517890000023, - "qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 55.117152813000075, - "qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 14.865196981999816, - "qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 17.427250706999985, - "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 56.926687345000005, - "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 34.26779686000009, - "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 13.058623059999718, - "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 17.17543610699977, - "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 35.62580278899986, - "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 33.17074543900003, - "qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 11.858483000999968, - "qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb": 96.11224141399998, - "qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 23.108933895000064, - "qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 14.581709606999993, - "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 52.01036585300017, - "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb": 15.210137743000018, - "qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 27.128310960999897, - "qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 29.26279021299979, - "qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 18.322653746000015, - "qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 19.307837559999825, - "qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 18.26698207300001, - "qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 37.257067328000176, - "qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 28.395653977000165, - "qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 26.92004345800001, - "qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 58.52073852400008, - "qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 17.781753611999875, - "qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 12.242755597000041, - "qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 9.960431046000167, - "qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb": 15.371727186000044, - "qa/specs/features/browser_ui/3_create/repository/file/delete_file_via_web_spec.rb": 12.087864284999796, - "qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb": 12.127680071999976, - "qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb": 13.449512228999993, - "qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 29.89524966800002, - "qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 82.36627285899999, - "qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 14.41380467099998, - "qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 14.242686575000107, - "qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 12.413497467999605, - "qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 51.00565575399992, - "qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 24.197618695000074, - "qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 40.23107726799981, - "qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 11.946123556999964, - "qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 24.566063014000065, - "qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 10.99895531600032, - "qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 19.954812592000053, - "qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 11.979494855999747, - "qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 13.269448764000003, - "qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 22.301814552999986, - "qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 35.360918446999904, - "qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 25.735420180000006, - "qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 32.776856023999926, - "qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 47.560282256999926, - "qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 26.71346316800009, - "qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 9.19937098000014, - "qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 8.94432519999998, - "qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 15.440743931000043, - "qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 20.027893337000023, - "qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 22.887218332999964, - "qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 19.402230539000016, - "qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 53.591218443000116, - "qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 20.280463688000054, - "qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 41.50735434999979, - "qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 61.49260960100037, - "qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 17.63215369400018, - "qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 145.06106596300015, - "qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 70.48372362600003, - "qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 38.94410077600014, - "qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 22.367475329999934, - "qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 119.9387103099998, - "qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 184.36148940700002, - "qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 37.10709434699993, - "qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb": 33.05014461699989, - "qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 55.08954772700008, - "qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb": 28.18033253600015, - "qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 16.35037038099972, - "qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 46.55407898899966, - "qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 12.40527249600018, - "qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 31.567785722000053, - "qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 32.14873077800007, - "qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 159.25057481, - "qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 30.228817518999904, - "qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb": 62.828434690999984, - "qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_via_web_only_spec.rb": 20.732332309999947, - "qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 43.38380465799992, - "qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 36.93094494899992, - "qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 27.6984514830001, - "qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 17.473935009999877, - "qa/specs/features/browser_ui/4_verify/runner/fleet_management/group_runner_counts_spec.rb": 11.281532321999748, - "qa/specs/features/browser_ui/4_verify/runner/fleet_management/group_runner_status_counts_spec.rb": 14.362288008000178, - "qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 11.812493389999872, - "qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 12.41056197099988, - "qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 23.068139760000122, - "qa/specs/features/browser_ui/5_package/container_registry/saas/container_registry_spec.rb": 181.211141692, - "qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 29.54853552399993, - "qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 49.92920795300006, - "qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 133.64515306499993, - "qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 155.8863834660001, - "qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 328.023568094, - "qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 138.53979677400002, - "qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 140.927264165, - "qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 169.96996041800003, - "qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 248.15001537699993, - "qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 51.85050848599985, - "qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 14.327700026999992, - "qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 138.50435548600012, - "qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 7.851582360000066, - "qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 23.870582943000045, - "qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 33.497799258999976, - "qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 33.665825194000035, - "qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 16.428518147000204, - "qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb": 57.3846946189999, - "qa/specs/features/browser_ui/9_data_stores/group/transfer_project_spec.rb": 20.150782878999962, - "qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 12.842779954999969, - "qa/specs/features/browser_ui/9_data_stores/project/create_project_badge_spec.rb": 18.237045063000096, - "qa/specs/features/browser_ui/9_data_stores/project/create_project_spec.rb": 18.170617833999813, - "qa/specs/features/browser_ui/9_data_stores/project/dashboard_images_spec.rb": 14.379097446999822, - "qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb": 49.47658440600003, - "qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb": 48.54146214100001, - "qa/specs/features/browser_ui/9_data_stores/project/view_project_activity_spec.rb": 14.59204661900003, - "qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb": 18.60214837700005, - "qa/specs/features/browser_ui/9_data_stores/user/parent_group_access_termination_spec.rb": 23.704223850000062, - "qa/specs/features/browser_ui/9_data_stores/user/user_inherited_access_spec.rb": 24.50105342100005, - "qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 18.616757694999933, - "qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 13.494274295000196, - "qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 46.14057161200003, - "qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 18.077241108000067, - "qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 6.717857566000021, - "qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 62.65099704199997, - "qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 43.97230168299984, - "qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 41.14789498399978, - "qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 16.80834482299997, - "qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 80.20809595699984, - "qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 26.22414726199986, - "qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 105.71841908799979, - "qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 221.70173438400002, - "qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 120.52202147899993, - "qa/specs/features/ee/browser_ui/10_govern/policies_list_spec.rb": 22.321339837000096, - "qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 130.7218652890001, - "qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 31.217049610999993, - "qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 65.474416916, - "qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 52.58500605400002, - "qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 148.30917081100006, - "qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 11.88923071399995, - "qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 173.51753275700003, - "qa/specs/features/ee/browser_ui/10_govern/vulnerability_security_training_spec.rb": 93.80908392499987, - "qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 7.375643481000225, - "qa/specs/features/ee/browser_ui/11_fulfillment/purchase/overage_modal_spec.rb": 360.2599755880001, - "qa/specs/features/ee/browser_ui/11_fulfillment/purchase/purchase_ci_spec.rb": 232.047008001, - "qa/specs/features/ee/browser_ui/11_fulfillment/purchase/upgrade_group_spec.rb": 121.72472706700046, - "qa/specs/features/ee/browser_ui/11_fulfillment/saas_user_limit_experience_spec.rb": 164.46296615700066, - "qa/specs/features/ee/browser_ui/11_fulfillment/utilization/free_namespace_storage_spec.rb": 332.41206052300004, - "qa/specs/features/ee/browser_ui/11_fulfillment/utilization/saas_user_caps_spec.rb": 39.50882987800014, - "qa/specs/features/ee/browser_ui/11_fulfillment/utilization/usage_quotas_seats_spec.rb": 111.24074988800021, - "qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 15.692323979999856, - "qa/specs/features/ee/browser_ui/13_secure/enable_advanced_sast_spec.rb": 75.51378988199997, - "qa/specs/features/ee/browser_ui/13_secure/enable_scanning_from_configuration_spec.rb": 83.86855627799969, - "qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 85.12023525199993, - "qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 26.270618488999844, - "qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 22.40183240800002, - "qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 21.887673334000056, - "qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 10.862288717999945, - "qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 11.189939669000069, - "qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 146.42032209499985, - "qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 15.018310149000172, - "qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 24.6155365489999, - "qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 10.632227265999973, - "qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 18.554999136999868, - "qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 19.554992973999788, - "qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 18.905201614999896, - "qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 9.612566635999997, - "qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 16.21545620400002, - "qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 9.770967304000123, - "qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 10.276463024999885, - "qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 33.215317630999834, - "qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 21.664296121000007, - "qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 12.10210814800007, - "qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 17.60076914399997, - "qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 11.882972971000072, - "qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 14.219959568999911, - "qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 23.512710422999817, - "qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 11.789255166999965, - "qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 34.729818802999944, - "qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 11.174618635999991, - "qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 56.259445724000216, - "qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 81.74281149999979, - "qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 17.38010672700011, - "qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 32.98471905699989, - "qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 12.166596606999974, - "qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 20.807681555000045, - "qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 157.3566193449999, - "qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 90.37105526800008, - "qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 80.31416042700016, - "qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 116.34840886400025, - "qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 54.955738060000044, - "qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 25.12295135799991, - "qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 31.673243391999904, - "qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 226.8815248159999, - "qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 217.52391698899999, - "qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 69.76164416599204, - "qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 16.685150342999805, - "qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 59.09543425900006, - "qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 38.168540809000206, - "qa/specs/features/ee/browser_ui/4_verify/pipelines_for_merged_results_and_merge_trains_spec.rb": 72.239648666, - "qa/specs/features/ee/browser_ui/4_verify/transient/merge_trains_transient_bug_spec.rb": 222.49861122300013, - "qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 11.709183800999995, - "qa/specs/features/ee/browser_ui/9_data_stores/group/prevent_forking_outside_group_spec.rb": 31.651073860999986, - "qa/specs/features/ee/browser_ui/9_data_stores/group/share_group_with_group_spec.rb": 25.22892628600016 + "qa/specs/features/api/10_govern/group_access_token_spec.rb": 22.368781745999968, + "qa/specs/features/api/10_govern/project_access_token_spec.rb": 23.633304253000006, + "qa/specs/features/api/1_manage/rate_limits_spec.rb": 3.1467832400003317, + "qa/specs/features/api/2_plan/closes_issue_via_pushing_a_commit_spec.rb": 7.066938178999862, + "qa/specs/features/api/3_create/merge_request/push_options_labels_spec.rb": 8.327959042000202, + "qa/specs/features/api/3_create/merge_request/push_options_mwps_spec.rb": 8.461132950999854, + "qa/specs/features/api/3_create/merge_request/push_options_remove_source_branch_spec.rb": 26.816687255000033, + "qa/specs/features/api/3_create/merge_request/push_options_target_branch_spec.rb": 8.936014013000204, + "qa/specs/features/api/3_create/merge_request/push_options_title_description_spec.rb": 22.04809043399996, + "qa/specs/features/api/3_create/merge_request/view_merge_requests_spec.rb": 0.07425793599986719, + "qa/specs/features/api/3_create/repository/add_list_delete_branches_spec.rb": 5.418077632999939, + "qa/specs/features/api/3_create/repository/commit_to_templated_project_spec.rb": 9.446346610999626, + "qa/specs/features/api/3_create/repository/default_branch_name_setting_spec.rb": 8.764005738999913, + "qa/specs/features/api/3_create/repository/files_spec.rb": 4.811035699000058, + "qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 4.926575522999883, + "qa/specs/features/api/3_create/repository/push_postreceive_idempotent_spec.rb": 6.722170169000037, + "qa/specs/features/api/3_create/repository/storage_size_spec.rb": 15.720025386000088, + "qa/specs/features/api/3_create/repository/tag_revision_trigger_prereceive_hook_spec.rb": 2.1978715340001145, + "qa/specs/features/api/4_verify/api_variable_inheritance_with_forward_pipeline_variables_spec.rb": 45.16411672999993, + "qa/specs/features/api/4_verify/cancel_pipeline_when_block_user_spec.rb": 10.504823287000363, + "qa/specs/features/api/4_verify/file_variable_spec.rb": 34.60797590999982, + "qa/specs/features/api/4_verify/job_downloads_artifacts_spec.rb": 25.90519738099988, + "qa/specs/features/api/9_data_stores/user_inherited_access_spec.rb": 72.58973470299998, + "qa/specs/features/api/9_data_stores/users_spec.rb": 0.46943177399998604, + "qa/specs/features/browser_ui/10_govern/group/group_access_token_spec.rb": 10.867392854000173, + "qa/specs/features/browser_ui/10_govern/login/2fa_recovery_spec.rb": 44.48518719200001, + "qa/specs/features/browser_ui/10_govern/login/2fa_ssh_recovery_spec.rb": 45.29727074599987, + "qa/specs/features/browser_ui/10_govern/login/log_in_spec.rb": 9.24467735899998, + "qa/specs/features/browser_ui/10_govern/login/log_in_with_2fa_spec.rb": 71.21219507099977, + "qa/specs/features/browser_ui/10_govern/login/login_via_oauth_and_oidc_with_gitlab_as_idp_spec.rb": 434.290598865, + "qa/specs/features/browser_ui/10_govern/login/register_spec.rb": 167.21751289999997, + "qa/specs/features/browser_ui/10_govern/project/project_access_token_spec.rb": 15.002181600999847, + "qa/specs/features/browser_ui/10_govern/user/impersonation_token_spec.rb": 34.3730335500004, + "qa/specs/features/browser_ui/10_govern/user/user_access_termination_spec.rb": 42.11820167299993, + "qa/specs/features/browser_ui/14_analytics/performance_bar_spec.rb": 18.42159530700019, + "qa/specs/features/browser_ui/14_analytics/service_ping_default_enabled_spec.rb": 12.865294517000166, + "qa/specs/features/browser_ui/1_manage/integrations/jenkins/jenkins_build_status_spec.rb": 64.23631289100013, + "qa/specs/features/browser_ui/2_plan/design_management/add_design_content_spec.rb": 13.557514046000051, + "qa/specs/features/browser_ui/2_plan/design_management/archive_design_content_spec.rb": 15.577577431000009, + "qa/specs/features/browser_ui/2_plan/design_management/modify_design_content_spec.rb": 16.93965877000005, + "qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 15.712710897999841, + "qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 11.492959395999605, + "qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 14.383327321999786, + "qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 47.38104187599993, + "qa/specs/features/browser_ui/2_plan/issue/custom_issue_template_spec.rb": 18.069448444000045, + "qa/specs/features/browser_ui/2_plan/issue/export_as_csv_spec.rb": 20.876328290999936, + "qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 21.686897792999844, + "qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 17.499067247999847, + "qa/specs/features/browser_ui/2_plan/issue/mentions_spec.rb": 20.052594967999994, + "qa/specs/features/browser_ui/2_plan/issue/real_time_assignee_spec.rb": 15.281954894000137, + "qa/specs/features/browser_ui/2_plan/issue_boards/focus_mode_spec.rb": 9.612242246999813, + "qa/specs/features/browser_ui/2_plan/milestone/assign_milestone_spec.rb": 58.40833293900005, + "qa/specs/features/browser_ui/2_plan/milestone/create_group_milestone_spec.rb": 12.194349004999822, + "qa/specs/features/browser_ui/2_plan/milestone/create_project_milestone_spec.rb": 16.66858077699999, + "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_creation_spec.rb": 57.8606801320002, + "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_content_manipulation_spec.rb": 36.7572706560004, + "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_directory_management_spec.rb": 13.130093501000374, + "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_file_upload_spec.rb": 26.96827703400004, + "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_list_spec.rb": 47.06445215099984, + "qa/specs/features/browser_ui/2_plan/project_wiki/project_based_page_deletion_spec.rb": 32.69071444200017, + "qa/specs/features/browser_ui/2_plan/related_issues/related_issues_spec.rb": 12.757383355999991, + "qa/specs/features/browser_ui/2_plan/transient/comment_on_discussion_spec.rb": 94.82951411399995, + "qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_a_merge_spec.rb": 28.79146358800017, + "qa/specs/features/browser_ui/3_create/merge_request/cherry_pick/cherry_pick_commit_spec.rb": 15.203331360999982, + "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 34.60106437700006, + "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_via_template_spec.rb": 16.876695956000276, + "qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 20.24183390600001, + "qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 39.35212773400008, + "qa/specs/features/browser_ui/3_create/merge_request/revert/revert_commit_spec.rb": 16.664726423000047, + "qa/specs/features/browser_ui/3_create/merge_request/revert/reverting_merge_request_spec.rb": 29.294655512999725, + "qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 29.818330108000055, + "qa/specs/features/browser_ui/3_create/merge_request/suggestions/batch_suggestion_spec.rb": 49.977204337999865, + "qa/specs/features/browser_ui/3_create/merge_request/suggestions/custom_commit_suggestion_spec.rb": 74.05581426499998, + "qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 25.886177607000036, + "qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 58.32857243300032, + "qa/specs/features/browser_ui/3_create/repository/add_new_branch_rule_spec.rb": 19.121554731999822, + "qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb": 12.009725652999805, + "qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 14.347447782000017, + "qa/specs/features/browser_ui/3_create/repository/file/create_file_via_web_spec.rb": 16.534865420999722, + "qa/specs/features/browser_ui/3_create/repository/file/delete_file_via_web_spec.rb": 13.119983175000016, + "qa/specs/features/browser_ui/3_create/repository/file/edit_file_via_web_spec.rb": 15.378666402999897, + "qa/specs/features/browser_ui/3_create/repository/file/file_with_unusual_name_spec.rb": 15.938765588000024, + "qa/specs/features/browser_ui/3_create/repository/license_detection_spec.rb": 21.63542792399994, + "qa/specs/features/browser_ui/3_create/repository/protected_tags_spec.rb": 79.99040074799996, + "qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_http_spec.rb": 12.59656118699968, + "qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb": 14.377605238999877, + "qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 12.259645183999965, + "qa/specs/features/browser_ui/3_create/repository/push_mirroring_lfs_over_http_spec.rb": 61.57848500299997, + "qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 58.59291245999975, + "qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 40.70022904899997, + "qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 12.597955936999824, + "qa/specs/features/browser_ui/3_create/repository/push_over_ssh_spec.rb": 23.335798235999846, + "qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 11.16296970899998, + "qa/specs/features/browser_ui/3_create/repository/push_to_canary_gitaly_spec.rb": 19.33630398599996, + "qa/specs/features/browser_ui/3_create/repository/ssh_key_support_create_spec.rb": 15.139340509000021, + "qa/specs/features/browser_ui/3_create/repository/ssh_key_support_delete_spec.rb": 15.598802374000115, + "qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 33.16634144599993, + "qa/specs/features/browser_ui/3_create/snippet/add_comment_to_snippet_spec.rb": 43.07075570100005, + "qa/specs/features/browser_ui/3_create/snippet/add_file_to_snippet_spec.rb": 32.04896657299969, + "qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_personal_snippet_spec.rb": 44.12173341700009, + "qa/specs/features/browser_ui/3_create/snippet/clone_push_pull_project_snippet_spec.rb": 45.1201057369999, + "qa/specs/features/browser_ui/3_create/snippet/copy_snippet_file_contents_spec.rb": 35.046242132000316, + "qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_spec.rb": 11.228186354000172, + "qa/specs/features/browser_ui/3_create/snippet/create_personal_snippet_with_multiple_files_spec.rb": 11.679584620000014, + "qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_spec.rb": 17.142693512999813, + "qa/specs/features/browser_ui/3_create/snippet/create_project_snippet_with_multiple_files_spec.rb": 25.128736303000096, + "qa/specs/features/browser_ui/3_create/snippet/delete_file_from_snippet_spec.rb": 22.43362897299994, + "qa/specs/features/browser_ui/3_create/snippet/share_snippet_spec.rb": 20.63210230699997, + "qa/specs/features/browser_ui/3_create/snippet/snippet_index_page_spec.rb": 51.18759698200006, + "qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb": 18.490141503999894, + "qa/specs/features/browser_ui/3_create/web_ide/add_first_file_in_web_ide_spec.rb": 47.41684779299976, + "qa/specs/features/browser_ui/3_create/web_ide/add_new_directory_in_web_ide_spec.rb": 55.95471532700003, + "qa/specs/features/browser_ui/3_create/web_ide/closing_web_ide_with_unsaved_changes_spec.rb": 17.89687930900027, + "qa/specs/features/browser_ui/3_create/web_ide/upload_new_file_in_web_ide_spec.rb": 127.97824422899998, + "qa/specs/features/browser_ui/4_verify/ci_components_catalog/ci_catalog_sorting_spec.rb": 92.95931840900016, + "qa/specs/features/browser_ui/4_verify/ci_components_catalog/run_component_in_project_pipeline_spec.rb": 35.63051462899966, + "qa/specs/features/browser_ui/4_verify/ci_job_artifacts/expose_job_artifacts_in_mr_spec.rb": 28.20214109099993, + "qa/specs/features/browser_ui/4_verify/ci_job_artifacts/job_artifacts_access_keyword_spec.rb": 110.66918478300022, + "qa/specs/features/browser_ui/4_verify/ci_job_artifacts/unlocking_job_artifacts_across_pipelines_spec.rb": 217.47925253999983, + "qa/specs/features/browser_ui/4_verify/ci_project_artifacts/user_can_bulk_delete_artifacts_spec.rb": 26.754828502000237, + "qa/specs/features/browser_ui/4_verify/ci_variable/custom_variable_spec.rb": 31.085558825000135, + "qa/specs/features/browser_ui/4_verify/ci_variable/pipeline_with_protected_variable_spec.rb": 44.06894120200013, + "qa/specs/features/browser_ui/4_verify/ci_variable/prefill_variables_spec.rb": 35.78982095099991, + "qa/specs/features/browser_ui/4_verify/ci_variable/raw_variables_defined_in_yaml_spec.rb": 29.708999760000097, + "qa/specs/features/browser_ui/4_verify/ci_variable/ui_variable_inheritable_when_forward_pipeline_variables_true_spec.rb": 50.36197774100037, + "qa/specs/features/browser_ui/4_verify/pipeline/include_local_config_file_paths_with_wildcard_spec.rb": 13.341353649999746, + "qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_a_project_spec.rb": 42.411010002999774, + "qa/specs/features/browser_ui/4_verify/pipeline/include_multiple_files_from_multiple_projects_spec.rb": 29.5737167530001, + "qa/specs/features/browser_ui/4_verify/pipeline/parent_child_pipelines_independent_relationship_spec.rb": 48.13610776800033, + "qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb": 28.524868175999927, + "qa/specs/features/browser_ui/4_verify/pipeline/pipeline_with_image_pull_policy_spec.rb": 106.57642176700028, + "qa/specs/features/browser_ui/4_verify/pipeline/run_pipeline_with_manual_jobs_spec.rb": 43.70788082299987, + "qa/specs/features/browser_ui/4_verify/pipeline/trigger_child_pipeline_with_manual_spec.rb": 36.24359708399993, + "qa/specs/features/browser_ui/4_verify/pipeline/trigger_matrix_spec.rb": 34.50332342399997, + "qa/specs/features/browser_ui/4_verify/pipeline/update_ci_file_with_pipeline_editor_spec.rb": 24.002371265999955, + "qa/specs/features/browser_ui/4_verify/runner/fleet_management/group_runner_counts_spec.rb": 10.757894206999936, + "qa/specs/features/browser_ui/4_verify/runner/fleet_management/group_runner_status_counts_spec.rb": 10.301866036000092, + "qa/specs/features/browser_ui/4_verify/runner/register_group_runner_spec.rb": 12.12438019199999, + "qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 15.005740251000134, + "qa/specs/features/browser_ui/4_verify/testing/endpoint_coverage_spec.rb": 29.49941567299993, + "qa/specs/features/browser_ui/5_package/infrastructure_registry/terraform_module_registry_spec.rb": 51.479649964000146, + "qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb": 27.352062939999996, + "qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb": 81.84952514700012, + "qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb": 28.553728266000235, + "qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb": 168.17629702600016, + "qa/specs/features/browser_ui/5_package/package_registry/maven/maven_group_level_spec.rb": 371.8446552810001, + "qa/specs/features/browser_ui/5_package/package_registry/maven/maven_project_level_spec.rb": 147.34913712900016, + "qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb": 143.3438839700002, + "qa/specs/features/browser_ui/5_package/package_registry/npm/npm_group_level_spec.rb": 212.5769153199999, + "qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb": 154.38543904599965, + "qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb": 65.49403680400019, + "qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 14.485062738000124, + "qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 95.86104218500031, + "qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 7.291155398999763, + "qa/specs/features/browser_ui/8_monitor/alert_management/alert_settings_create_new_alerts_spec.rb": 28.596902782000143, + "qa/specs/features/browser_ui/8_monitor/alert_management/automatically_creates_incident_for_alert_spec.rb": 51.41727816599996, + "qa/specs/features/browser_ui/8_monitor/alert_management/create_alert_using_authorization_key_spec.rb": 30.579300772999886, + "qa/specs/features/browser_ui/8_monitor/alert_management/recovery_alert_resolves_correct_alert_spec.rb": 17.057656027999656, + "qa/specs/features/browser_ui/9_data_stores/group/group_member_access_request_spec.rb": 62.32897362400013, + "qa/specs/features/browser_ui/9_data_stores/group/transfer_project_spec.rb": 18.02293271999997, + "qa/specs/features/browser_ui/9_data_stores/project/add_project_member_spec.rb": 17.59251898699995, + "qa/specs/features/browser_ui/9_data_stores/project/create_project_badge_spec.rb": 15.446313674000066, + "qa/specs/features/browser_ui/9_data_stores/project/create_project_spec.rb": 18.019042763000016, + "qa/specs/features/browser_ui/9_data_stores/project/dashboard_images_spec.rb": 14.63526036400026, + "qa/specs/features/browser_ui/9_data_stores/project/invite_group_to_project_spec.rb": 50.74090813900011, + "qa/specs/features/browser_ui/9_data_stores/project/project_owner_permissions_spec.rb": 44.86465373500005, + "qa/specs/features/browser_ui/9_data_stores/project/view_project_activity_spec.rb": 10.57122799900003, + "qa/specs/features/browser_ui/9_data_stores/user/follow_user_activity_spec.rb": 17.56207801699975, + "qa/specs/features/browser_ui/9_data_stores/user/parent_group_access_termination_spec.rb": 27.12611723300006, + "qa/specs/features/browser_ui/9_data_stores/user/user_inherited_access_spec.rb": 22.777019481000025, + "qa/specs/features/ee/api/10_govern/compliance_pipeline_spec.rb": 9.899398154999744, + "qa/specs/features/ee/api/10_govern/instance_audit_event_streaming_spec.rb": 13.41729055199994, + "qa/specs/features/ee/api/10_govern/user/minimal_access_user_spec.rb": 52.863767513999846, + "qa/specs/features/ee/api/2_plan/epics_milestone_dates_spec.rb": 24.66561908299991, + "qa/specs/features/ee/api/3_create/code_suggestions_spec.rb": 9.734530240000026, + "qa/specs/features/ee/browser_ui/10_govern/change_vulnerability_status_spec.rb": 78.78103058800025, + "qa/specs/features/ee/browser_ui/10_govern/create_merge_request_with_secure_spec.rb": 41.02528116299982, + "qa/specs/features/ee/browser_ui/10_govern/dismissed_vulnerabilities_in_security_widget_spec.rb": 75.75522501800015, + "qa/specs/features/ee/browser_ui/10_govern/export_vulnerability_report_spec.rb": 21.53629811100018, + "qa/specs/features/ee/browser_ui/10_govern/fix_vulnerability_workflow_spec.rb": 87.46915877299989, + "qa/specs/features/ee/browser_ui/10_govern/group/group_audit_event_streaming_spec.rb": 19.81644356400011, + "qa/specs/features/ee/browser_ui/10_govern/group/group_audit_logs_1_spec.rb": 94.59999707999987, + "qa/specs/features/ee/browser_ui/10_govern/group/restrict_by_ip_address_spec.rb": 244.94209053500003, + "qa/specs/features/ee/browser_ui/10_govern/instance/instance_audit_logs_spec.rb": 149.11489667600017, + "qa/specs/features/ee/browser_ui/10_govern/policies_list_spec.rb": 23.17459256999996, + "qa/specs/features/ee/browser_ui/10_govern/project/project_audit_logs_spec.rb": 142.88678456800017, + "qa/specs/features/ee/browser_ui/10_govern/project_security_dashboard_spec.rb": 32.639712987000166, + "qa/specs/features/ee/browser_ui/10_govern/scan_execution_policy_vulnerabilities_spec.rb": 87.48531807299992, + "qa/specs/features/ee/browser_ui/10_govern/scan_result_policy_vulnerabilities_spec.rb": 100.16423248799993, + "qa/specs/features/ee/browser_ui/10_govern/security_reports_spec.rb": 219.48511522400008, + "qa/specs/features/ee/browser_ui/10_govern/user/minimal_access_user_spec.rb": 10.48683282300044, + "qa/specs/features/ee/browser_ui/10_govern/vulnerability_management_spec.rb": 223.67916750899985, + "qa/specs/features/ee/browser_ui/10_govern/vulnerability_security_training_spec.rb": 90.11761338999986, + "qa/specs/features/ee/browser_ui/11_fulfillment/license/license_spec.rb": 6.416189227000359, + "qa/specs/features/ee/browser_ui/11_fulfillment/utilization/user_registration_billing_spec.rb": 25.087748015999978, + "qa/specs/features/ee/browser_ui/13_secure/enable_advanced_sast_spec.rb": 98.2445041179999, + "qa/specs/features/ee/browser_ui/13_secure/enable_scanning_from_configuration_spec.rb": 50.96775453700002, + "qa/specs/features/ee/browser_ui/13_secure/on_demand_dast_spec.rb": 95.48979141200016, + "qa/specs/features/ee/browser_ui/2_plan/analytics/contribution_analytics_spec.rb": 31.527241697000136, + "qa/specs/features/ee/browser_ui/2_plan/analytics/mr_analytics_spec.rb": 25.38604634799981, + "qa/specs/features/ee/browser_ui/2_plan/analytics/value_stream_analytics_spec.rb": 23.01250367600005, + "qa/specs/features/ee/browser_ui/2_plan/burndown_chart/burndown_chart_spec.rb": 10.041722617999994, + "qa/specs/features/ee/browser_ui/2_plan/custom_email/custom_email_spec.rb": 12.056798871999945, + "qa/specs/features/ee/browser_ui/2_plan/epic/epics_management_spec.rb": 134.200256243, + "qa/specs/features/ee/browser_ui/2_plan/epic/promote_issue_to_epic_spec.rb": 28.479518154999823, + "qa/specs/features/ee/browser_ui/2_plan/epic/roadmap_spec.rb": 10.634338394000224, + "qa/specs/features/ee/browser_ui/2_plan/group_wiki/create_group_wiki_page_spec.rb": 29.492960455999764, + "qa/specs/features/ee/browser_ui/2_plan/group_wiki/delete_group_wiki_page_spec.rb": 9.413657116000195, + "qa/specs/features/ee/browser_ui/2_plan/group_wiki/file_upload_group_wiki_page_spec.rb": 14.95102097400013, + "qa/specs/features/ee/browser_ui/2_plan/insights/default_insights_spec.rb": 24.128866205000122, + "qa/specs/features/ee/browser_ui/2_plan/issue/default_issue_template_spec.rb": 15.553754882000248, + "qa/specs/features/ee/browser_ui/2_plan/issue_boards/configurable_issue_board_spec.rb": 11.234492763999697, + "qa/specs/features/ee/browser_ui/2_plan/issue_boards/configure_issue_board_by_label_spec.rb": 18.468924121000327, + "qa/specs/features/ee/browser_ui/2_plan/issue_boards/create_group_issue_board_spec.rb": 17.354496787000244, + "qa/specs/features/ee/browser_ui/2_plan/issue_boards/group_issue_boards_spec.rb": 18.92236214400009, + "qa/specs/features/ee/browser_ui/2_plan/issue_boards/project_issue_boards_spec.rb": 39.630014759000005, + "qa/specs/features/ee/browser_ui/2_plan/issue_boards/read_only_board_configuration_spec.rb": 24.101322554000035, + "qa/specs/features/ee/browser_ui/2_plan/issue_boards/sum_of_issues_weights_spec.rb": 12.440506615000004, + "qa/specs/features/ee/browser_ui/2_plan/issues_analytics/issues_analytics_spec.rb": 21.37253022599998, + "qa/specs/features/ee/browser_ui/2_plan/issues_weight/issue_weight_visualization_spec.rb": 12.460709697999846, + "qa/specs/features/ee/browser_ui/2_plan/iterations/assign_group_iteration_spec.rb": 13.901354654999977, + "qa/specs/features/ee/browser_ui/2_plan/iterations/create_group_iteration_spec.rb": 33.006181079000044, + "qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/four_assignees_spec.rb": 12.56144702499978, + "qa/specs/features/ee/browser_ui/2_plan/multiple_assignees_for_issues/more_than_four_assignees_spec.rb": 32.16122686400013, + "qa/specs/features/ee/browser_ui/2_plan/scoped_labels/editing_scoped_labels_spec.rb": 13.211152739000227, + "qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb": 57.75658679099979, + "qa/specs/features/ee/browser_ui/3_create/merge_request/approval_rules_spec.rb": 96.94270416100017, + "qa/specs/features/ee/browser_ui/3_create/merge_request/default_merge_request_template_spec.rb": 33.12321659999998, + "qa/specs/features/ee/browser_ui/3_create/repository/assign_code_owners_spec.rb": 28.152587653999944, + "qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb": 16.502650385000152, + "qa/specs/features/ee/browser_ui/3_create/repository/code_owners_with_protected_branch_and_squashed_commits_spec.rb": 25.722663802999932, + "qa/specs/features/ee/browser_ui/3_create/repository/file_locking_spec.rb": 200.75515444999974, + "qa/specs/features/ee/browser_ui/3_create/repository/group_file_template_spec.rb": 92.8232680189999, + "qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_root_group_spec.rb": 81.4973934489999, + "qa/specs/features/ee/browser_ui/3_create/repository/merge_with_code_owner_in_subgroup_spec.rb": 141.39839299200003, + "qa/specs/features/ee/browser_ui/3_create/repository/project_templates_spec.rb": 72.26127357299993, + "qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_http_spec.rb": 29.386763341999995, + "qa/specs/features/ee/browser_ui/3_create/repository/pull_mirroring_over_ssh_with_key_spec.rb": 45.32656895399987, + "qa/specs/features/ee/browser_ui/3_create/repository/push_rules_spec.rb": 244.24686534299963, + "qa/specs/features/ee/browser_ui/3_create/repository/restrict_push_protected_branch_spec.rb": 224.68295732599972, + "qa/specs/features/ee/browser_ui/3_create/web_ide/code_suggestions_in_web_ide_spec.rb": 70.40918833, + "qa/specs/features/ee/browser_ui/4_verify/multi-project_pipelines_spec.rb": 16.11996115000011, + "qa/specs/features/ee/browser_ui/4_verify/parent_child_pipelines_dependent_relationship_spec.rb": 46.88418564700032, + "qa/specs/features/ee/browser_ui/4_verify/pipeline_subscription_with_group_owned_project_spec.rb": 36.758229692000214, + "qa/specs/features/ee/browser_ui/4_verify/pipelines_for_merged_results_and_merge_trains_spec.rb": 79.00419656800022, + "qa/specs/features/ee/browser_ui/4_verify/transient/merge_trains_transient_bug_spec.rb": 102.89802900000018, + "qa/specs/features/ee/browser_ui/8_monitor/incident_management/incident_quick_action_spec.rb": 14.259584643000153, + "qa/specs/features/ee/browser_ui/9_data_stores/group/prevent_forking_outside_group_spec.rb": 38.22584831700033, + "qa/specs/features/ee/browser_ui/9_data_stores/group/share_group_with_group_spec.rb": 24.902903569000046 } \ No newline at end of file diff --git a/qa/qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb index ea7bad5205a..b836bfa8f48 100644 --- a/qa/qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb +++ b/qa/qa/specs/features/browser_ui/10_govern/login/log_into_mattermost_via_gitlab_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Govern', :orchestrated, :mattermost, product_group: :authentication do describe 'Mattermost login' do - it 'user logs into Mattermost using GitLab OAuth', + it 'user logs into Mattermost using GitLab OAuth', :blocking, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347891' do Flow::Login.sign_in diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb index 905e25f5107..7b93edeffcc 100644 --- a/spec/features/merge_request/user_sees_diff_spec.rb +++ b/spec/features/merge_request/user_sees_diff_spec.rb @@ -47,7 +47,7 @@ RSpec.describe 'Merge request > User sees diff', :js, feature_category: :code_re visit "#{diffs_project_merge_request_path(project, merge_request)}##{line_code}" end - it 'shows the linked line' do + it 'shows the linked line', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/496702' do expect(page).to have_selector("[id='#{line_code}']", visible: true, obscured: false) end end diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index c1ca19e29ec..44a0b1bbc0a 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -409,7 +409,7 @@ RSpec.describe GroupsFinder, feature_category: :groups_and_projects do ) end - it "returns project's parent group if user is member of project" do + it "returns project's ancestor groups if user is member of project" do project = create(:project, :private, namespace: private_sub_subgroup) project.add_developer(user) @@ -419,7 +419,8 @@ RSpec.describe GroupsFinder, feature_category: :groups_and_projects do public_subgroup2, internal_sub_subgroup, public_sub_subgroup, - private_sub_subgroup + private_sub_subgroup, + private_subgroup2 ) end diff --git a/spec/frontend/api/projects_api_spec.js b/spec/frontend/api/projects_api_spec.js index 7dd686d04cf..76e98165f92 100644 --- a/spec/frontend/api/projects_api_spec.js +++ b/spec/frontend/api/projects_api_spec.js @@ -219,4 +219,89 @@ describe('~/api/projects_api.js', () => { expect(mock.history.get[0].mockOption).toBe(axiosOptions.mockOption); }); }); + + describe('uploadImageToProject', () => { + const mockProjectId = 123; + const mockFilename = 'test.jpg'; + const mockBlobData = new Blob(['test']); + + beforeEach(() => { + window.gon = { relative_url_root: '', api_version: 'v7' }; + jest.spyOn(axios, 'post'); + }); + + it('should upload an image and return the share URL', async () => { + const mockResponse = { + full_path: '/-/project/123/uploads/abcd/test.jpg', + }; + + mock.onPost().replyOnce(HTTP_STATUS_OK, mockResponse); + + const result = await projectsApi.uploadImageToProject({ + filename: mockFilename, + blobData: mockBlobData, + projectId: mockProjectId, + }); + + expect(axios.post).toHaveBeenCalledWith( + `/api/v7/projects/${mockProjectId}/uploads`, + expect.any(FormData), + expect.objectContaining({ + headers: { 'Content-Type': 'multipart/form-data' }, + }), + ); + expect(result).toBe('http://test.host/-/project/123/uploads/abcd/test.jpg'); + }); + + it('should throw an error if filename is missing', async () => { + await expect( + projectsApi.uploadImageToProject({ + blobData: mockBlobData, + projectId: mockProjectId, + }), + ).rejects.toThrow('Request failed with status code 404'); + }); + + it('should throw an error if blobData is missing', async () => { + await expect( + projectsApi.uploadImageToProject({ + filename: mockFilename, + projectId: mockProjectId, + }), + ).rejects.toThrow("is not of type 'Blob'"); + }); + + it('should throw an error if projectId is missing', async () => { + await expect( + projectsApi.uploadImageToProject({ + filename: mockFilename, + blobData: mockBlobData, + }), + ).rejects.toThrow('Request failed with status code 404'); + }); + + it('should throw an error if the upload fails', async () => { + mock.onPost().replyOnce(500); + + await expect( + projectsApi.uploadImageToProject({ + filename: mockFilename, + blobData: mockBlobData, + projectId: mockProjectId, + }), + ).rejects.toThrow('Request failed with status code 500'); + }); + + it('should throw an error if the response does not have a link', async () => { + mock.onPost().replyOnce(HTTP_STATUS_OK, {}); + + await expect( + projectsApi.uploadImageToProject({ + filename: mockFilename, + blobData: mockBlobData, + projectId: mockProjectId, + }), + ).rejects.toThrow('Image failed to upload'); + }); + }); }); diff --git a/spec/frontend/work_items/components/shared/work_item_relationship_icons_spec.js b/spec/frontend/work_items/components/shared/work_item_relationship_icons_spec.js index 1cc680c66e9..69623d408a9 100644 --- a/spec/frontend/work_items/components/shared/work_item_relationship_icons_spec.js +++ b/spec/frontend/work_items/components/shared/work_item_relationship_icons_spec.js @@ -1,28 +1,49 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import WorkItemRelationshipIcons from '~/work_items/components/shared/work_item_relationship_icons.vue'; import { LINKED_CATEGORIES_MAP } from '~/work_items/constants'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { createMockDirective } from 'helpers/vue_mock_directive'; +import workItemLinkedItemsQuery from '~/work_items/graphql/work_item_linked_items.query.graphql'; -import { mockLinkedItems } from '../../mock_data'; +import { mockLinkedItems, workItemLinkedItemsResponse } from '../../mock_data'; describe('WorkItemRelationshipIcons', () => { + Vue.use(VueApollo); + let wrapper; - const createComponent = () => { + const workItemLinkedItemsSuccessHandler = jest + .fn() + .mockResolvedValue(workItemLinkedItemsResponse); + + const createComponent = async ({ + workItemType = 'Task', + workItemLinkedItemsHandler = workItemLinkedItemsSuccessHandler, + } = {}) => { + const mockApollo = createMockApollo([[workItemLinkedItemsQuery, workItemLinkedItemsHandler]]); + wrapper = shallowMountExtended(WorkItemRelationshipIcons, { + apolloProvider: mockApollo, directives: { GlTooltip: createMockDirective('gl-tooltip'), }, propsData: { + workItemType, + workItemIid: '1', + workItemFullPath: 'gitlab-org/gitlab-test', + workItemWebUrl: '/gitlab-org/gitlab-test/-/work_items/1', linkedWorkItems: mockLinkedItems.linkedItems.nodes, - workItemType: 'Task', }, }); + + await waitForPromises(); }; const findBlockedIcon = () => wrapper.findByTestId('relationship-blocked-by-icon'); const findBlockingIcon = () => wrapper.findByTestId('relationship-blocks-icon'); - const findTooltip = (icon) => getBinding(icon.element, 'gl-tooltip'); const blockedItems = mockLinkedItems.linkedItems.nodes.filter( (item) => item.linkType === LINKED_CATEGORIES_MAP.IS_BLOCKED_BY, @@ -64,12 +85,18 @@ describe('WorkItemRelationshipIcons', () => { expect(findBlockingIcon().text()).toContain(blockedItems.length.toString()); }); - it('renders tooltips with correct text', () => { + it('does not query child link items if the icons are not hovered', () => { createComponent(); - expect(findTooltip(findBlockingIcon())).toBeDefined(); - expect(findTooltip(findBlockedIcon())).toBeDefined(); - expect(findBlockedIcon().attributes('title')).toBe('Task is blocked by 1 item'); - expect(findBlockingIcon().attributes('title')).toBe('Task blocks 1 item'); + expect(workItemLinkedItemsSuccessHandler).not.toHaveBeenCalled(); + }); + + it('triggers child link items query on hover', async () => { + createComponent(); + + await findBlockedIcon().trigger('mouseenter'); + await waitForPromises(); + + expect(workItemLinkedItemsSuccessHandler).toHaveBeenCalled(); }); }); diff --git a/spec/frontend/work_items/components/shared/work_item_relationship_popover_metadata_spec.js b/spec/frontend/work_items/components/shared/work_item_relationship_popover_metadata_spec.js new file mode 100644 index 00000000000..9a5e93f7e66 --- /dev/null +++ b/spec/frontend/work_items/components/shared/work_item_relationship_popover_metadata_spec.js @@ -0,0 +1,60 @@ +import { GlAvatarsInline } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ItemMilestone from '~/issuable/components/issue_milestone.vue'; +import WorkItemPopoverMetadata from '~/work_items/components/shared/work_item_relationship_popover_metadata.vue'; +import { workItemTask } from '../../mock_data'; + +describe('WorkItemPopoverMetadata', () => { + let wrapper; + + const mockMilestone = workItemTask.widgets.find( + (widget) => widget.type === 'MILESTONE', + ).milestone; + const mockAssignees = workItemTask.widgets.find((widget) => widget.type === 'ASSIGNEES').assignees + .nodes; + + const createComponent = () => { + wrapper = shallowMountExtended(WorkItemPopoverMetadata, { + propsData: { + workItem: workItemTask, + workItemFullPath: 'gitlab-org/gitlab-test', + }, + scopedSlots: { + 'weight-metadata': `
Test weight metada
`, + 'additional-metadata': `
Test metadata
`, + }, + }); + }; + + const findItemMilestone = () => wrapper.findComponent(ItemMilestone); + const findMetadataSlot = () => wrapper.findByTestId('additional-metadata-slot'); + const findWeightMetadataSlot = () => wrapper.findByTestId('weight-metadata-slot'); + const findAvatars = () => wrapper.findComponent(GlAvatarsInline); + + beforeEach(() => { + createComponent(); + }); + + it('renders scoped slot contents', () => { + expect(findWeightMetadataSlot().text()).toBe('Test weight metada'); + expect(findMetadataSlot().text()).toBe('Test metadata'); + }); + + it('renders work item milestone', () => { + expect(findItemMilestone().exists()).toBe(true); + expect(findItemMilestone().props('milestone')).toEqual(mockMilestone); + }); + + it('renders avatars for assignees', () => { + expect(findAvatars().exists()).toBe(true); + expect(findAvatars().props()).toMatchObject({ + avatars: mockAssignees, + maxVisible: 3, + avatarSize: 16, + collapsed: true, + badgeSrOnlyText: '', + badgeTooltipProp: 'name', + badgeTooltipMaxChars: null, + }); + }); +}); diff --git a/spec/frontend/work_items/components/shared/work_item_relationship_popover_spec.js b/spec/frontend/work_items/components/shared/work_item_relationship_popover_spec.js new file mode 100644 index 00000000000..49b955e01a9 --- /dev/null +++ b/spec/frontend/work_items/components/shared/work_item_relationship_popover_spec.js @@ -0,0 +1,92 @@ +import { GlPopover, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { createMockDirective } from 'helpers/vue_mock_directive'; +import WorkItemRelationshipPopover from '~/work_items/components/shared/work_item_relationship_popover.vue'; +import { mockLinkedItems } from '../../mock_data'; + +describe('WorkItemRelationshipPopover', () => { + let wrapper; + + const linkedItems = mockLinkedItems.linkedItems.nodes; + const linkedItemsAboveLimit = [ + ...mockLinkedItems.linkedItems.nodes, + { + linkId: 'gid://gitlab/WorkItems::RelatedWorkItemLink/11', + linkType: 'blocks', + workItem: { + id: 'gid://gitlab/WorkItem/648', + iid: '57', + confidential: true, + workItemType: { + id: 'gid://gitlab/WorkItems::Type/6', + name: 'Objective', + iconName: 'issue-type-objective', + __typename: 'WorkItemType', + }, + namespace: { + id: 'gid://gitlab/Group/1', + fullPath: 'test-project-path', + __typename: 'Namespace', + }, + reference: 'test-project-path#57', + title: 'Multilevel Objective 3', + state: 'OPEN', + createdAt: '2023-03-28T10:50:16Z', + closedAt: null, + webUrl: '/gitlab-org/gitlab-test/-/work_items/57', + widgets: [], + __typename: 'WorkItem', + }, + __typename: 'LinkedWorkItemType', + }, + ]; + const target = 'blocking-icon'; + const workItemWebUrl = '/gitlab-org/gitlab-test/-/work_items/1'; + + const createComponent = ({ linkedWorkItems = linkedItems, loading = false } = {}) => { + wrapper = shallowMountExtended(WorkItemRelationshipPopover, { + directives: { + GlTooltip: createMockDirective('gl-tooltip'), + }, + propsData: { + linkedWorkItems, + title: 'Blocking', + target, + workItemFullPath: 'gitlab-org/gitlab-test', + workItemWebUrl, + loading, + }, + }); + }; + + const findPopover = () => wrapper.findComponent(GlPopover); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findMoreRelatedItemsLink = () => wrapper.findByTestId('more-related-items-link'); + + beforeEach(() => { + createComponent(); + }); + + it('displays relationship popover on hover and focus', () => { + expect(findPopover().exists()).toBe(true); + expect(findPopover().props('triggers')).toBe('hover focus'); + expect(findPopover().props('target')).toBe(target); + }); + + it('displays loading icon if loading prop = true', () => { + createComponent({ loading: true }); + expect(findLoadingIcon().exists()).toBe(true); + }); + + it('displays linked items within the popover', () => { + linkedItems.forEach((item) => { + expect(findPopover().text()).toContain(item.workItem.title); + }); + }); + + it('truncates linked items if the default display limit of 3 is exceeded', () => { + createComponent({ linkedWorkItems: linkedItemsAboveLimit }); + expect(findMoreRelatedItemsLink().exists()).toBe(true); + expect(findMoreRelatedItemsLink().attributes('href')).toBe(`${workItemWebUrl}#linkeditems`); + }); +}); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index 4417314dea1..7adc6604486 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -608,6 +608,11 @@ export const mockBlockingLinkedItem = { iconName: 'issue-type-task', __typename: 'WorkItemType', }, + namespace: { + id: 'gid://gitlab/Group/1', + fullPath: 'test-project-path', + __typename: 'Namespace', + }, reference: 'test-project-path#1', title: 'Task 1201', state: 'OPEN', @@ -642,6 +647,11 @@ export const mockBlockedByLinkedItem = { iconName: 'issue-type-task', __typename: 'WorkItemType', }, + namespace: { + id: 'gid://gitlab/Group/1', + fullPath: 'test-project-path', + __typename: 'Namespace', + }, reference: 'test-project-path#1', title: 'Task 1201', state: 'OPEN', @@ -666,6 +676,11 @@ export const mockBlockedByLinkedItem = { iconName: 'issue-type-task', __typename: 'WorkItemType', }, + namespace: { + id: 'gid://gitlab/Group/1', + fullPath: 'test-project-path', + __typename: 'Namespace', + }, reference: 'test-project-path#1', title: 'Task 1202', state: 'OPEN', @@ -700,6 +715,11 @@ export const mockLinkedItems = { iconName: 'issue-type-task', __typename: 'WorkItemType', }, + namespace: { + id: 'gid://gitlab/Group/1', + fullPath: 'test-project-path', + __typename: 'Namespace', + }, reference: 'test-project-path#83', title: 'Task 1201', state: 'OPEN', @@ -724,6 +744,11 @@ export const mockLinkedItems = { iconName: 'issue-type-objective', __typename: 'WorkItemType', }, + namespace: { + id: 'gid://gitlab/Group/2', + fullPath: 'test-project-path', + __typename: 'Namespace', + }, reference: 'test-project-path#55', title: 'Multilevel Objective 1', state: 'OPEN', @@ -748,6 +773,11 @@ export const mockLinkedItems = { iconName: 'issue-type-objective', __typename: 'WorkItemType', }, + namespace: { + id: 'gid://gitlab/Group/3', + fullPath: 'test-project-path', + __typename: 'Namespace', + }, reference: 'test-project-path#56', title: 'Multilevel Objective 2', state: 'OPEN', @@ -827,6 +857,11 @@ export const workItemSingleLinkedItemResponse = { iconName: 'issue-type-task', __typename: 'WorkItemType', }, + namespace: { + id: 'gid://gitlab/Group/1', + fullPath: 'test-project-path', + __typename: 'Namespace', + }, reference: 'test-project-path#1', title: 'Task 1201', state: 'OPEN', @@ -1782,6 +1817,7 @@ export const workItemTask = { widgets: [ workItemObjectiveMetadataWidgets.ASSIGNEES, workItemObjectiveMetadataWidgets.LINKED_ITEMS, + workItemObjectiveMetadataWidgets.MILESTONE, { type: 'HIERARCHY', hasChildren: false, diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index 0574048ef82..1eaff242c26 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -5,6 +5,43 @@ require "spec_helper" RSpec.describe AuthHelper, feature_category: :system_access do include LoginHelpers + describe "#enabled_button_based_providers_for_signup" do + [[true, %w[github gitlab]], + [false, []], + [['github'], ['github']], + [[], []]].each do |(allow_single_sign_on, result)| + context "when allow_single_sign_on is #{allow_single_sign_on}" do + before do + allow(helper).to receive(:enabled_button_based_providers) { %w[github gitlab] } + stub_omniauth_config(allow_single_sign_on: allow_single_sign_on) + end + + it "returns #{result}" do + expect(helper.enabled_button_based_providers_for_signup).to eq(result) + end + end + end + end + + describe "#signup_button_based_providers_enabled?" do + [[true, true, true], + [true, ['github'], true], + [false, true, false], + [true, false, false], + [true, [], false]].each do |(omniauth_enable, allow_single_sign_on, result)| + context "when omniauth is #{omniauth_enable} and allow_single_sign_on is #{allow_single_sign_on}" do + before do + allow(Gitlab::Auth).to receive(:omniauth_enabled?).and_return(omniauth_enable) + stub_omniauth_config(allow_single_sign_on: allow_single_sign_on) + end + + it "returns #{result}" do + expect(helper.signup_button_based_providers_enabled?).to eq(result) + end + end + end + end + describe "button_based_providers" do it 'returns all enabled providers from devise' do allow(helper).to receive(:auth_providers) { [:twitter, :github] } diff --git a/spec/helpers/groups/group_members_helper_spec.rb b/spec/helpers/groups/group_members_helper_spec.rb index ed60630560e..b2e8381d8c4 100644 --- a/spec/helpers/groups/group_members_helper_spec.rb +++ b/spec/helpers/groups/group_members_helper_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Groups::GroupMembersHelper, feature_category: :groups_and_project banned: [], include_relations: [:inherited, :direct], search: nil, - pending_members: [], + pending_members_count: [], placeholder_users: { pagination: { total_items: 3, @@ -121,7 +121,7 @@ RSpec.describe Groups::GroupMembersHelper, feature_category: :groups_and_project banned: [], include_relations: include_relations, search: nil, - pending_members: [], + pending_members_count: [], placeholder_users: {} ) end diff --git a/spec/helpers/projects/project_members_helper_spec.rb b/spec/helpers/projects/project_members_helper_spec.rb index f874e5d338a..191795a9d4c 100644 --- a/spec/helpers/projects/project_members_helper_spec.rb +++ b/spec/helpers/projects/project_members_helper_spec.rb @@ -32,7 +32,7 @@ RSpec.describe Projects::ProjectMembersHelper, feature_category: :groups_and_pro access_requests: present_members(access_requests), include_relations: [:inherited, :direct], search: nil, - pending_members: [] + pending_members_count: [] ) ) end @@ -142,7 +142,7 @@ RSpec.describe Projects::ProjectMembersHelper, feature_category: :groups_and_pro access_requests: present_members(access_requests), include_relations: include_relations, search: nil, - pending_members: [] + pending_members_count: [] ) ) end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0bfe9fbfbff..79ddaf7e8b6 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4920,7 +4920,8 @@ RSpec.describe User, feature_category: :user_profile do let_it_be(:private_group) { create(:group) } let_it_be(:child_group) { create(:group, parent: private_group) } - let_it_be(:project_group) { create(:group) } + let_it_be(:project_group_parent) { create(:group) } + let_it_be(:project_group) { create(:group, parent: project_group_parent) } let_it_be(:project) { create(:project, group: project_group) } before_all do @@ -4930,10 +4931,22 @@ RSpec.describe User, feature_category: :user_profile do subject { user.authorized_groups } - it { is_expected.to contain_exactly private_group, child_group, project_group } + it { is_expected.to contain_exactly private_group, child_group, project_group, project_group_parent } + + context 'when fix_user_authorized_groups is disabled' do + before do + stub_feature_flags(fix_user_authorized_groups: false) + end + + it 'omits ancestor groups of projects' do + is_expected.to include project_group + is_expected.not_to include project_group_parent + end + end context 'with shared memberships' do let_it_be(:shared_group) { create(:group) } + let_it_be(:shared_group_descendant) { create(:group, parent: shared_group) } let_it_be(:other_group) { create(:group) } let_it_be(:shared_with_project_group) { create(:group) } @@ -4943,8 +4956,19 @@ RSpec.describe User, feature_category: :user_profile do create(:group_group_link, shared_group: shared_with_project_group, shared_with_group: project_group) end - it { is_expected.to include shared_group } + it { is_expected.to include shared_group, shared_group_descendant } it { is_expected.not_to include other_group, shared_with_project_group } + + context 'when fix_user_authorized_groups is disabled' do + before do + stub_feature_flags(fix_user_authorized_groups: false) + end + + it 'omits subgroups of shared groups' do + is_expected.to include shared_group + is_expected.not_to include shared_group_descendant + end + end end context 'when a new column is added to namespaces table' do diff --git a/spec/requests/api/group_milestones_spec.rb b/spec/requests/api/group_milestones_spec.rb index 7b4075b3aeb..35b665fccb6 100644 --- a/spec/requests/api/group_milestones_spec.rb +++ b/spec/requests/api/group_milestones_spec.rb @@ -94,6 +94,10 @@ RSpec.describe API::GroupMilestones, feature_category: :team_planning do let(:user) { create(:user) } before do + # When a group has a project, users that have access to the group will get access to ancestor groups + # See https://gitlab.com/groups/gitlab-org/-/epics/9424 + group.projects.delete_all + group.add_guest(user) end diff --git a/yarn.lock b/yarn.lock index b0929211b33..f2701648356 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10619,6 +10619,11 @@ mock-apollo-client@1.2.0: resolved "https://registry.yarnpkg.com/mock-apollo-client/-/mock-apollo-client-1.2.0.tgz#72543df0d74577d29be1b34cecba8898c7e71451" integrity sha512-zCVHv3p7zvUmen9zce9l965ZrI6rMbrm2/oqGaTerVYOaYskl/cVgTG/L7iIToTIpI7onk/f6tu8hxPXZdyy/g== +modern-screenshot@^4.4.39: + version "4.4.39" + resolved "https://registry.yarnpkg.com/modern-screenshot/-/modern-screenshot-4.4.39.tgz#4c8b7a9ecb899e68b6d4111abfa71043f326847a" + integrity sha512-p+I4yLZUDnoJMa5zoi+71nLQmoLQ6WRU4W8vZu1BZk2PlIYOz5mGnj9/7t2lGWKYeOr4zo6pajhY0/9TS5Zcdw== + monaco-editor-webpack-plugin@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-6.0.0.tgz#628956ce1851afa2a5f6c88d0ecbb24e9a444898" @@ -13130,16 +13135,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13192,7 +13188,7 @@ string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13206,13 +13202,6 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -14904,7 +14893,7 @@ worker-loader@^3.0.8: loader-utils "^2.0.0" schema-utils "^3.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -14922,15 +14911,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"