Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									4df10dee37
								
							
						
					
					
						commit
						495c2f372a
					
				|  | @ -58,9 +58,6 @@ export default { | ||||||
|     'app/assets/javascripts/ci/pipelines_page/components/pipelines_artifacts.vue', |     'app/assets/javascripts/ci/pipelines_page/components/pipelines_artifacts.vue', | ||||||
|     'app/assets/javascripts/ci/pipelines_page/pipelines.vue', |     'app/assets/javascripts/ci/pipelines_page/pipelines.vue', | ||||||
|     'app/assets/javascripts/ci/reports/components/report_section.vue', |     'app/assets/javascripts/ci/reports/components/report_section.vue', | ||||||
|     'app/assets/javascripts/clusters_list/components/agents.vue', |  | ||||||
|     'app/assets/javascripts/clusters_list/components/delete_agent_button.vue', |  | ||||||
|     'app/assets/javascripts/clusters_list/components/install_agent_modal.vue', |  | ||||||
|     'app/assets/javascripts/commit/pipelines/legacy_pipelines_table_wrapper.vue', |     'app/assets/javascripts/commit/pipelines/legacy_pipelines_table_wrapper.vue', | ||||||
|     'app/assets/javascripts/content_editor/components/bubble_menus/media_bubble_menu.vue', |     'app/assets/javascripts/content_editor/components/bubble_menus/media_bubble_menu.vue', | ||||||
|     'app/assets/javascripts/content_editor/components/content_editor.vue', |     'app/assets/javascripts/content_editor/components/content_editor.vue', | ||||||
|  | @ -76,7 +73,6 @@ export default { | ||||||
|     'app/assets/javascripts/custom_emoji/pages/index.vue', |     'app/assets/javascripts/custom_emoji/pages/index.vue', | ||||||
|     'app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue', |     'app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue', | ||||||
|     'app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue', |     'app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue', | ||||||
|     'app/assets/javascripts/deployments/components/deployment_header.vue', |  | ||||||
|     'app/assets/javascripts/design_management/components/design_description/description_form.vue', |     'app/assets/javascripts/design_management/components/design_description/description_form.vue', | ||||||
|     'app/assets/javascripts/design_management/components/design_notes/design_discussion.vue', |     'app/assets/javascripts/design_management/components/design_notes/design_discussion.vue', | ||||||
|     'app/assets/javascripts/design_management/components/design_notes/design_note.vue', |     'app/assets/javascripts/design_management/components/design_notes/design_note.vue', | ||||||
|  | @ -92,15 +88,6 @@ export default { | ||||||
|     'app/assets/javascripts/diffs/components/diff_view.vue', |     'app/assets/javascripts/diffs/components/diff_view.vue', | ||||||
|     'app/assets/javascripts/diffs/components/image_diff_overlay.vue', |     'app/assets/javascripts/diffs/components/image_diff_overlay.vue', | ||||||
|     'app/assets/javascripts/emoji/components/picker.vue', |     'app/assets/javascripts/emoji/components/picker.vue', | ||||||
|     'app/assets/javascripts/environments/components/canary_update_modal.vue', |  | ||||||
|     'app/assets/javascripts/environments/components/deployment.vue', |  | ||||||
|     'app/assets/javascripts/environments/components/empty_state.vue', |  | ||||||
|     'app/assets/javascripts/environments/components/enable_review_app_modal.vue', |  | ||||||
|     'app/assets/javascripts/environments/components/environment_flux_resource_selector.vue', |  | ||||||
|     'app/assets/javascripts/environments/components/environment_form.vue', |  | ||||||
|     'app/assets/javascripts/environments/environment_details/components/deployment_actions.vue', |  | ||||||
|     'app/assets/javascripts/environments/environment_details/components/deployment_history.vue', |  | ||||||
|     'app/assets/javascripts/environments/environment_details/components/kubernetes/kubernetes_overview.vue', |  | ||||||
|     'app/assets/javascripts/error_tracking/components/error_details.vue', |     'app/assets/javascripts/error_tracking/components/error_details.vue', | ||||||
|     'app/assets/javascripts/error_tracking/components/error_tracking_list.vue', |     'app/assets/javascripts/error_tracking/components/error_tracking_list.vue', | ||||||
|     'app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue', |     'app/assets/javascripts/error_tracking_settings/components/project_dropdown.vue', | ||||||
|  | @ -144,8 +131,6 @@ export default { | ||||||
|     'app/assets/javascripts/invite_members/components/user_limit_notification.vue', |     'app/assets/javascripts/invite_members/components/user_limit_notification.vue', | ||||||
|     'app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue', |     'app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue', | ||||||
|     'app/assets/javascripts/jira_import/components/jira_import_form.vue', |     'app/assets/javascripts/jira_import/components/jira_import_form.vue', | ||||||
|     'app/assets/javascripts/kubernetes_dashboard/components/workload_details.vue', |  | ||||||
|     'app/assets/javascripts/kubernetes_dashboard/components/workload_details_drawer.vue', |  | ||||||
|     'app/assets/javascripts/members/components/filter_sort/sort_dropdown.vue', |     'app/assets/javascripts/members/components/filter_sort/sort_dropdown.vue', | ||||||
|     'app/assets/javascripts/merge_request_dashboard/components/status_badge.vue', |     'app/assets/javascripts/merge_request_dashboard/components/status_badge.vue', | ||||||
|     'app/assets/javascripts/merge_requests/components/reviewers/reviewer_drawer.vue', |     'app/assets/javascripts/merge_requests/components/reviewers/reviewer_drawer.vue', | ||||||
|  | @ -448,9 +433,6 @@ export default { | ||||||
|     'ee/app/assets/javascripts/projects/components/move_personal_project_to_group_modal.vue', |     'ee/app/assets/javascripts/projects/components/move_personal_project_to_group_modal.vue', | ||||||
|     'ee/app/assets/javascripts/projects/merge_requests/blocking_mr_input_root.vue', |     'ee/app/assets/javascripts/projects/merge_requests/blocking_mr_input_root.vue', | ||||||
|     'ee/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue', |     'ee/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue', | ||||||
|     'ee/app/assets/javascripts/protected_environments/create_protected_environment.vue', |  | ||||||
|     'ee/app/assets/javascripts/protected_environments/edit_protected_environment_rules_card.vue', |  | ||||||
|     'ee/app/assets/javascripts/protected_environments/protected_environments.vue', |  | ||||||
|     'ee/app/assets/javascripts/related_items_tree/components/create_epic_form.vue', |     'ee/app/assets/javascripts/related_items_tree/components/create_epic_form.vue', | ||||||
|     'ee/app/assets/javascripts/related_items_tree/components/related_items_tree_app.vue', |     'ee/app/assets/javascripts/related_items_tree/components/related_items_tree_app.vue', | ||||||
|     'ee/app/assets/javascripts/related_items_tree/components/related_items_tree_header_actions.vue', |     'ee/app/assets/javascripts/related_items_tree/components/related_items_tree_header_actions.vue', | ||||||
|  |  | ||||||
|  | @ -162,3 +162,5 @@ config: | ||||||
|     ] |     ] | ||||||
| ignores: | ignores: | ||||||
|   - "doc/architecture" |   - "doc/architecture" | ||||||
|  | customRules: | ||||||
|  |   - "./doc/.markdownlint/rules/unnecessary_traversal.js" | ||||||
|  |  | ||||||
|  | @ -69,11 +69,6 @@ export default { | ||||||
|       required: true, |       required: true, | ||||||
|       type: Array, |       type: Array, | ||||||
|     }, |     }, | ||||||
|     defaultBranchName: { |  | ||||||
|       default: '.noBranch', |  | ||||||
|       required: false, |  | ||||||
|       type: String, |  | ||||||
|     }, |  | ||||||
|     maxAgents: { |     maxAgents: { | ||||||
|       default: null, |       default: null, | ||||||
|       required: false, |       required: false, | ||||||
|  | @ -423,7 +418,6 @@ export default { | ||||||
|               v-if="action.name === 'delete-agent'" |               v-if="action.name === 'delete-agent'" | ||||||
|               :key="action.name" |               :key="action.name" | ||||||
|               :agent="item" |               :agent="item" | ||||||
|               :default-branch-name="defaultBranchName" |  | ||||||
|             /> |             /> | ||||||
|             <gl-disclosure-dropdown-item |             <gl-disclosure-dropdown-item | ||||||
|               v-else |               v-else | ||||||
|  |  | ||||||
|  | @ -119,7 +119,6 @@ export default { | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|       folderList: {}, |  | ||||||
|       feedbackBannerDismissed: false, |       feedbackBannerDismissed: false, | ||||||
|       queryErrored: false, |       queryErrored: false, | ||||||
|       sharedAgentsQueryErrored: false, |       sharedAgentsQueryErrored: false, | ||||||
|  | @ -292,12 +291,7 @@ export default { | ||||||
|           {{ $options.i18n.error }} |           {{ $options.i18n.error }} | ||||||
|         </gl-alert> |         </gl-alert> | ||||||
| 
 | 
 | ||||||
|         <agent-table |         <agent-table v-else :agents="tab.agents" :max-agents="limit" /> | ||||||
|           v-else |  | ||||||
|           :agents="tab.agents" |  | ||||||
|           :default-branch-name="defaultBranchName" |  | ||||||
|           :max-agents="limit" |  | ||||||
|         /> |  | ||||||
|       </gl-tab> |       </gl-tab> | ||||||
| 
 | 
 | ||||||
|       <gl-tab v-if="availableConfigs.length" :title="$options.i18n.availableConfigs"> |       <gl-tab v-if="availableConfigs.length" :title="$options.i18n.availableConfigs"> | ||||||
|  |  | ||||||
|  | @ -36,11 +36,6 @@ export default { | ||||||
|       type: Object, |       type: Object, | ||||||
|       validator: (value) => ['id', 'name'].every((prop) => value[prop]), |       validator: (value) => ['id', 'name'].every((prop) => value[prop]), | ||||||
|     }, |     }, | ||||||
|     defaultBranchName: { |  | ||||||
|       default: '.noBranch', |  | ||||||
|       required: false, |  | ||||||
|       type: String, |  | ||||||
|     }, |  | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|  |  | ||||||
|  | @ -194,6 +194,8 @@ export default { | ||||||
|       } |       } | ||||||
|       this.registerAgent(); |       this.registerAgent(); | ||||||
|     }, |     }, | ||||||
|  |     // This method is triggered from outside of the component | ||||||
|  |     // eslint-disable-next-line vue/no-unused-properties | ||||||
|     showModalForAgent(name) { |     showModalForAgent(name) { | ||||||
|       this.agentName = name; |       this.agentName = name; | ||||||
|       this.$refs.modal?.show(); |       this.$refs.modal?.show(); | ||||||
|  |  | ||||||
|  | @ -55,9 +55,6 @@ export default { | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     iid() { |  | ||||||
|       return this.deployment.iid; |  | ||||||
|     }, |  | ||||||
|     status() { |     status() { | ||||||
|       return this.deployment.status?.toLowerCase() ?? ''; |       return this.deployment.status?.toLowerCase() ?? ''; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -48,7 +48,7 @@ export default { | ||||||
|     static: true, |     static: true, | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { error: '', dismissed: true }; |     return { error: '' }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     stableWeight() { |     stableWeight() { | ||||||
|  |  | ||||||
|  | @ -71,9 +71,6 @@ export default { | ||||||
|       const dateTime = new Date(this.deploymentTime); |       const dateTime = new Date(this.deploymentTime); | ||||||
|       return localeDateFormat.asDateTimeFull.format(dateTime); |       return localeDateFormat.asDateTimeFull.format(dateTime); | ||||||
|     }, |     }, | ||||||
|     createdAt() { |  | ||||||
|       return this.deployment?.createdAt; |  | ||||||
|     }, |  | ||||||
|     commit() { |     commit() { | ||||||
|       return this.deployment?.commit; |       return this.deployment?.commit; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -24,14 +24,6 @@ export default { | ||||||
|       default: false, |       default: false, | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   computed: { |  | ||||||
|     title() { |  | ||||||
|       return this.hasTerm ? this.$options.i18n.searchingTitle : this.$options.i18n.title; |  | ||||||
|     }, |  | ||||||
|     content() { |  | ||||||
|       return this.hasTerm ? this.$options.i18n.searchingContent : this.$options.i18n.content; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   i18n: { |   i18n: { | ||||||
|     title: s__('Environments|Get started with environments'), |     title: s__('Environments|Get started with environments'), | ||||||
|     content: s__( |     content: s__( | ||||||
|  |  | ||||||
|  | @ -47,11 +47,6 @@ export default { | ||||||
|     - if: $CI_PIPELINE_SOURCE == "merge_request_event"`; |     - if: $CI_PIPELINE_SOURCE == "merge_request_event"`; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   methods: { |  | ||||||
|     commaOrPeriod(index, length) { |  | ||||||
|       return index + 1 === length ? '.' : ','; |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   i18n, |   i18n, | ||||||
|   configuringReviewAppsPath: helpPagePath('ci/review_apps/_index.md', { |   configuringReviewAppsPath: helpPagePath('ci/review_apps/_index.md', { | ||||||
|     anchor: 'configure-review-apps', |     anchor: 'configure-review-apps', | ||||||
|  |  | ||||||
|  | @ -61,10 +61,7 @@ export default { | ||||||
|     fluxKustomizations: { |     fluxKustomizations: { | ||||||
|       query: fluxKustomizationsQuery, |       query: fluxKustomizationsQuery, | ||||||
|       variables() { |       variables() { | ||||||
|         return { |         return this.variables; | ||||||
|           configuration: this.configuration, |  | ||||||
|           namespace: this.namespace, |  | ||||||
|         }; |  | ||||||
|       }, |       }, | ||||||
|       skip() { |       skip() { | ||||||
|         return !this.namespace; |         return !this.namespace; | ||||||
|  | @ -85,10 +82,7 @@ export default { | ||||||
|     fluxHelmReleases: { |     fluxHelmReleases: { | ||||||
|       query: fluxHelmReleasesQuery, |       query: fluxHelmReleasesQuery, | ||||||
|       variables() { |       variables() { | ||||||
|         return { |         return this.variables; | ||||||
|           configuration: this.configuration, |  | ||||||
|           namespace: this.namespace, |  | ||||||
|         }; |  | ||||||
|       }, |       }, | ||||||
|       skip() { |       skip() { | ||||||
|         return !this.namespace; |         return !this.namespace; | ||||||
|  |  | ||||||
|  | @ -103,13 +103,9 @@ export default { | ||||||
|       selectedAgentId: this.environment.clusterAgentId, |       selectedAgentId: this.environment.clusterAgentId, | ||||||
|       agentSearchTerm: '', |       agentSearchTerm: '', | ||||||
|       selectedNamespace: this.environment.kubernetesNamespace, |       selectedNamespace: this.environment.kubernetesNamespace, | ||||||
|       kubernetesError: '', |  | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     loadingNamespacesList() { |  | ||||||
|       return this.$apollo.queries.k8sNamespaces.loading; |  | ||||||
|     }, |  | ||||||
|     isNameDisabled() { |     isNameDisabled() { | ||||||
|       return Boolean(this.environment.id); |       return Boolean(this.environment.id); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -80,9 +80,6 @@ export default { | ||||||
|     isActionsShown() { |     isActionsShown() { | ||||||
|       return this.actions.length > 0; |       return this.actions.length > 0; | ||||||
|     }, |     }, | ||||||
|     deploymentIid() { |  | ||||||
|       return this.approvalEnvironment.deploymentIid; |  | ||||||
|     }, |  | ||||||
|     environment() { |     environment() { | ||||||
|       return this.approvalEnvironment.environment; |       return this.approvalEnvironment.environment; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -104,9 +104,6 @@ export default { | ||||||
|     isPaginationDisabled() { |     isPaginationDisabled() { | ||||||
|       return this.isLoading || this.isPrefetchingPages; |       return this.isLoading || this.isPrefetchingPages; | ||||||
|     }, |     }, | ||||||
|     pollingInterval() { |  | ||||||
|       return this.graphqlEtagKey ? ENVIRONMENT_DETAILS_QUERY_POLLING_INTERVAL : null; |  | ||||||
|     }, |  | ||||||
|     isDirectionAscending() { |     isDirectionAscending() { | ||||||
|       return this.sortDirection === DIRECTION_ASCENDING; |       return this.sortDirection === DIRECTION_ASCENDING; | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  | @ -132,7 +132,6 @@ export default { | ||||||
|       podsLoading: false, |       podsLoading: false, | ||||||
|       activeTab: k8sResourceType.k8sPods, |       activeTab: k8sResourceType.k8sPods, | ||||||
|       fluxApiError: '', |       fluxApiError: '', | ||||||
|       focusedElement: null, |  | ||||||
|       podToDelete: {}, |       podToDelete: {}, | ||||||
|       fluxHelmRelease: {}, |       fluxHelmRelease: {}, | ||||||
|       fluxKustomization: {}, |       fluxKustomization: {}, | ||||||
|  |  | ||||||
|  | @ -179,13 +179,13 @@ export default { | ||||||
|         </gl-badge> |         </gl-badge> | ||||||
|       </div> |       </div> | ||||||
|     </workload-details-item> |     </workload-details-item> | ||||||
|     <workload-details-item v-if="item.status && !item.fullStatus" :label="$options.i18n.status"> |     <workload-details-item v-if="item.status && !hasFullStatus" :label="$options.i18n.status"> | ||||||
|       <gl-badge :variant="$options.WORKLOAD_STATUS_BADGE_VARIANTS[item.status]">{{ |       <gl-badge :variant="$options.WORKLOAD_STATUS_BADGE_VARIANTS[item.status]">{{ | ||||||
|         $options.STATUS_LABELS[item.status] |         $options.STATUS_LABELS[item.status] | ||||||
|       }}</gl-badge> |       }}</gl-badge> | ||||||
|     </workload-details-item> |     </workload-details-item> | ||||||
|     <workload-details-item |     <workload-details-item | ||||||
|       v-if="item.fullStatus" |       v-if="hasFullStatus" | ||||||
|       :label="$options.i18n.status" |       :label="$options.i18n.status" | ||||||
|       :is-expanded="expanded.status" |       :is-expanded="expanded.status" | ||||||
|       collapsible |       collapsible | ||||||
|  |  | ||||||
|  | @ -41,6 +41,8 @@ export default { | ||||||
|     onDeletePod(pod) { |     onDeletePod(pod) { | ||||||
|       this.$emit('delete-pod', pod); |       this.$emit('delete-pod', pod); | ||||||
|     }, |     }, | ||||||
|  |     // This method is triggered from outside of the component | ||||||
|  |     // eslint-disable-next-line vue/no-unused-properties | ||||||
|     toggle(item, section) { |     toggle(item, section) { | ||||||
|       if (!isEqual(item, this.selectedItem)) { |       if (!isEqual(item, this.selectedItem)) { | ||||||
|         this.open(item, section); |         this.open(item, section); | ||||||
|  |  | ||||||
|  | @ -15,11 +15,9 @@ import { | ||||||
| import Tracking from '~/tracking'; | import Tracking from '~/tracking'; | ||||||
| import { | import { | ||||||
|   TODO_TARGET_TYPE_ISSUE, |   TODO_TARGET_TYPE_ISSUE, | ||||||
|   TODO_TARGET_TYPE_WORK_ITEM, |  | ||||||
|   TODO_TARGET_TYPE_MERGE_REQUEST, |   TODO_TARGET_TYPE_MERGE_REQUEST, | ||||||
|   TODO_TARGET_TYPE_DESIGN, |   TODO_TARGET_TYPE_DESIGN, | ||||||
|   TODO_TARGET_TYPE_ALERT, |   TODO_TARGET_TYPE_ALERT, | ||||||
|   TODO_TARGET_TYPE_EPIC, |  | ||||||
|   TODO_TARGET_TYPE_SSH_KEY, |   TODO_TARGET_TYPE_SSH_KEY, | ||||||
|   TODO_TARGET_TYPE_WIKI_PAGE, |   TODO_TARGET_TYPE_WIKI_PAGE, | ||||||
|   TODO_ACTION_TYPE_ASSIGNED, |   TODO_ACTION_TYPE_ASSIGNED, | ||||||
|  | @ -65,12 +63,7 @@ export const TARGET_TYPES = [ | ||||||
|     // eslint-disable-next-line @gitlab/require-i18n-strings |     // eslint-disable-next-line @gitlab/require-i18n-strings | ||||||
|     id: 'Issue', |     id: 'Issue', | ||||||
|     value: TODO_TARGET_TYPE_ISSUE, |     value: TODO_TARGET_TYPE_ISSUE, | ||||||
|     title: s__('Todos|Issue'), |     title: s__('Todos|Issue or Epic'), | ||||||
|   }, |  | ||||||
|   { |  | ||||||
|     id: 'WorkItem', |  | ||||||
|     value: TODO_TARGET_TYPE_WORK_ITEM, |  | ||||||
|     title: s__('Todos|Work item'), |  | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     id: 'MergeRequest', |     id: 'MergeRequest', | ||||||
|  | @ -87,12 +80,6 @@ export const TARGET_TYPES = [ | ||||||
|     value: TODO_TARGET_TYPE_ALERT, |     value: TODO_TARGET_TYPE_ALERT, | ||||||
|     title: s__('Todos|Alert'), |     title: s__('Todos|Alert'), | ||||||
|   }, |   }, | ||||||
|   { |  | ||||||
|     // eslint-disable-next-line @gitlab/require-i18n-strings |  | ||||||
|     id: 'Epic', |  | ||||||
|     value: TODO_TARGET_TYPE_EPIC, |  | ||||||
|     title: s__('Todos|Epic'), |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     // eslint-disable-next-line @gitlab/require-i18n-strings |     // eslint-disable-next-line @gitlab/require-i18n-strings | ||||||
|     id: 'Key', |     id: 'Key', | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ export const TODO_STATE_DONE = 'done'; | ||||||
| export const TODO_STATE_PENDING = 'pending'; | export const TODO_STATE_PENDING = 'pending'; | ||||||
| 
 | 
 | ||||||
| export const TODO_TARGET_TYPE_ISSUE = 'ISSUE'; | export const TODO_TARGET_TYPE_ISSUE = 'ISSUE'; | ||||||
| export const TODO_TARGET_TYPE_WORK_ITEM = 'WORKITEM'; |  | ||||||
| export const TODO_TARGET_TYPE_MERGE_REQUEST = 'MERGEREQUEST'; | export const TODO_TARGET_TYPE_MERGE_REQUEST = 'MERGEREQUEST'; | ||||||
| export const TODO_TARGET_TYPE_DESIGN = 'DESIGN'; | export const TODO_TARGET_TYPE_DESIGN = 'DESIGN'; | ||||||
| export const TODO_TARGET_TYPE_ALERT = 'ALERT'; | export const TODO_TARGET_TYPE_ALERT = 'ALERT'; | ||||||
|  |  | ||||||
|  | @ -91,7 +91,7 @@ export default { | ||||||
|       v-if="isNewNote" |       v-if="isNewNote" | ||||||
|       name="image-comment-dark" |       name="image-comment-dark" | ||||||
|       :size="24" |       :size="24" | ||||||
|       class="gl-rounded-full gl-border-2 gl-border-solid gl-border-white gl-bg-white" |       class="gl-rounded-full gl-border-2 gl-border-solid gl-border-neutral-0 gl-bg-neutral-0 gl-text-neutral-950" | ||||||
|     /> |     /> | ||||||
|     <template v-else> |     <template v-else> | ||||||
|       {{ label }} |       {{ label }} | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| <script> | <script> | ||||||
| import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; | import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui'; | ||||||
| import { __ } from '~/locale'; |  | ||||||
| import { | import { | ||||||
|   GROUP_VISIBILITY_TYPE, |   GROUP_VISIBILITY_TYPE, | ||||||
|   PROJECT_VISIBILITY_TYPE, |   PROJECT_VISIBILITY_TYPE, | ||||||
|  | @ -32,17 +31,10 @@ export default { | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     isBannedProject() { |  | ||||||
|       return !this.isGroup && this.visibilityLevel === 'banned'; |  | ||||||
|     }, |  | ||||||
|     visibilityIcon() { |     visibilityIcon() { | ||||||
|       return this.isBannedProject ? 'spam' : VISIBILITY_TYPE_ICON[this.visibilityLevel]; |       return VISIBILITY_TYPE_ICON[this.visibilityLevel]; | ||||||
|     }, |     }, | ||||||
|     visibilityTooltip() { |     visibilityTooltip() { | ||||||
|       if (this.isBannedProject) { |  | ||||||
|         return __('This project is hidden because its creator has been banned'); |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       if (this.isGroup) { |       if (this.isGroup) { | ||||||
|         return GROUP_VISIBILITY_TYPE[this.visibilityLevel]; |         return GROUP_VISIBILITY_TYPE[this.visibilityLevel]; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|  | @ -28,8 +28,8 @@ | ||||||
| .rd-hunk-header { | .rd-hunk-header { | ||||||
|   // this is used when a hunk header doesn't have any text, only expand buttons |   // this is used when a hunk header doesn't have any text, only expand buttons | ||||||
|   min-height: calc(1em * $code-line-height); |   min-height: calc(1em * $code-line-height); | ||||||
|   border-top: 1px solid var(--rd-hunk-header-border-color, $gray-100); |   border-top: 1px solid var(--rd-hunk-header-border-color, var(--gl-border-color-default)); | ||||||
|   border-bottom: 1px solid var(--rd-hunk-header-border-color, $gray-100); |   border-bottom: 1px solid var(--rd-hunk-header-border-color, var(--gl-border-color-default)); | ||||||
|   background-color: var(--rd-hunk-header-background-color, $gray-50); |   background-color: var(--rd-hunk-header-background-color, $gray-50); | ||||||
|   color: var(--rd-hunk-header-color, $gray-400); |   color: var(--rd-hunk-header-color, $gray-400); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -125,8 +125,7 @@ html { | ||||||
|   padding-left: $gl-padding; |   padding-left: $gl-padding; | ||||||
|   padding-right: $gl-padding; |   padding-right: $gl-padding; | ||||||
|   box-shadow: none; |   box-shadow: none; | ||||||
|   background-color: $gray-10; |   @apply gl-bg-subtle gl-border-l; | ||||||
|   border-left: 1px solid $gray-100; |  | ||||||
| 
 | 
 | ||||||
|   @include media-breakpoint-down(sm) { |   @include media-breakpoint-down(sm) { | ||||||
|     min-width: unset; |     min-width: unset; | ||||||
|  |  | ||||||
|  | @ -244,7 +244,7 @@ | ||||||
|   @apply gl-transition-width; |   @apply gl-transition-width; | ||||||
|   height: $toggle-sidebar-height; |   height: $toggle-sidebar-height; | ||||||
|   padding: 0 $gl-padding; |   padding: 0 $gl-padding; | ||||||
|   background-color: $gray-10; |   @apply gl-bg-subtle; | ||||||
|   border: 0; |   border: 0; | ||||||
|   @apply gl-text-subtle; |   @apply gl-text-subtle; | ||||||
|   display: flex; |   display: flex; | ||||||
|  | @ -252,7 +252,7 @@ | ||||||
|   @apply gl-border-b; |   @apply gl-border-b; | ||||||
| 
 | 
 | ||||||
|   &:hover { |   &:hover { | ||||||
|     background-color: $gray-100; |     @apply gl-bg-strong; | ||||||
|     @apply gl-text-default; |     @apply gl-text-default; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -19,21 +19,6 @@ | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .gl-sortable { |  | ||||||
|   .header { |  | ||||||
|     user-select: none; |  | ||||||
| 
 |  | ||||||
|     &:hover { |  | ||||||
|       cursor: pointer; |  | ||||||
|       background-color: $gray-100; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     &:focus { |  | ||||||
|       outline: 1px solid $blue-300; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .is-dragging { | .is-dragging { | ||||||
|   // Important because plugin sets inline CSS |   // Important because plugin sets inline CSS | ||||||
|   opacity: 1 !important; |   opacity: 1 !important; | ||||||
|  |  | ||||||
|  | @ -783,9 +783,7 @@ pre { | ||||||
|   font-size: $gl-font-size; |   font-size: $gl-font-size; | ||||||
|   word-break: break-all; |   word-break: break-all; | ||||||
|   word-wrap: break-word; |   word-wrap: break-word; | ||||||
|   @apply gl-text-default; |   @apply gl-text-default gl-bg-subtle gl-border; | ||||||
|   background-color: $gray-10; |  | ||||||
|   border: 1px solid $gray-100; |  | ||||||
|   border-radius: $border-radius-small; |   border-radius: $border-radius-small; | ||||||
| 
 | 
 | ||||||
|   // Select only code elements that will have the copy code button |   // Select only code elements that will have the copy code button | ||||||
|  |  | ||||||
|  | @ -144,7 +144,7 @@ blockquote, | ||||||
|   color: $gl-text-color-subtle; |   color: $gl-text-color-subtle; | ||||||
|   padding: 0 0 0 15px; |   padding: 0 0 0 15px; | ||||||
|   margin: 0; |   margin: 0; | ||||||
|   border-left: 3px solid $gray-100; |   border-left: 3px solid $gl-border-color-default; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| span.highlight_word { | span.highlight_word { | ||||||
|  |  | ||||||
|  | @ -90,7 +90,7 @@ tr td { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| tr.border-top td { | tr.border-top td { | ||||||
|   border-top: 2px solid $gray-100; |   border-top: 2px solid $gl-border-color-default; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| tr.line td { | tr.line td { | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ | ||||||
|   .markdown-code-block pre.code { |   .markdown-code-block pre.code { | ||||||
|     padding: $gl-padding-8 $input-horizontal-padding; |     padding: $gl-padding-8 $input-horizontal-padding; | ||||||
|     margin: 0 0 $gl-padding-8; |     margin: 0 0 $gl-padding-8; | ||||||
|     border: 1px solid $gray-100; |     border: 1px solid $gl-border-color-default; | ||||||
|     border-radius: $border-radius-small; |     border-radius: $border-radius-small; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -103,7 +103,7 @@ | ||||||
| 
 | 
 | ||||||
|     .margin { |     .margin { | ||||||
|       background-color: $white; |       background-color: $white; | ||||||
|       border-right: 1px solid $gray-100; |       @apply gl-border-r; | ||||||
| 
 | 
 | ||||||
|       .line-insert { |       .line-insert { | ||||||
|         border-right: 1px solid $line-added-dark; |         border-right: 1px solid $line-added-dark; | ||||||
|  |  | ||||||
|  | @ -60,17 +60,17 @@ | ||||||
|       &, |       &, | ||||||
|       .badge.badge-pill { |       .badge.badge-pill { | ||||||
|         color: var(--ide-text-color, $black); |         color: var(--ide-text-color, $black); | ||||||
|         border-color: var(--ide-input-border, $gray-200); |         border-color: var(--ide-input-border, var(--gl-border-color-strong)); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .drag-handle:hover { |   .drag-handle:hover { | ||||||
|     background-color: var(--ide-dropdown-hover-background, $gray-50); |     background-color: var(--ide-dropdown-hover-background, var(--gl-background-color-strong)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .card-header { |   .card-header { | ||||||
|     background-color: var(--ide-background, $white); |     background-color: var(--ide-background, var(--gl-background-color-default)); | ||||||
| 
 | 
 | ||||||
|     .badge.badge-pill { |     .badge.badge-pill { | ||||||
|       background-color: var(--ide-dropdown-hover-background, $badge-bg); |       background-color: var(--ide-dropdown-hover-background, $badge-bg); | ||||||
|  | @ -97,12 +97,12 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   code { |   code { | ||||||
|     background-color: var(--ide-background, $gray-100); |     background-color: var(--ide-background, var(--gl-background-color-strong)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .ide-pipeline .top-bar, |   .ide-pipeline .top-bar, | ||||||
|   .ide-terminal .top-bar { |   .ide-terminal .top-bar { | ||||||
|     background-color: var(--ide-background, $gray-10); |     background-color: var(--ide-background, var(--gl-background-color-subtle)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .common-note-form .md-area { |   .common-note-form .md-area { | ||||||
|  | @ -110,13 +110,13 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .md table:not(.code) tr th { |   .md table:not(.code) tr th { | ||||||
|     background-color: var(--ide-highlight-background, $gray-100); |     background-color: var(--ide-highlight-background, var(--gl-background-color-strong)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &, |   &, | ||||||
|   .card, |   .card, | ||||||
|   .common-note-form .md-area { |   .common-note-form .md-area { | ||||||
|     background-color: var(--ide-highlight-background, $white); |     background-color: var(--ide-highlight-background, var(--gl-background-color-default)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .card, |   .card, | ||||||
|  | @ -142,7 +142,7 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   pre { |   pre { | ||||||
|     border-color: var(--ide-border-color-alt, $gray-100); |     border-color: var(--ide-border-color-alt, var(--gl-border-color-default)); | ||||||
| 
 | 
 | ||||||
|     code { |     code { | ||||||
|       background-color: var(--ide-empty-state-background, inherit); |       background-color: var(--ide-empty-state-background, inherit); | ||||||
|  | @ -179,7 +179,7 @@ | ||||||
|       &, |       &, | ||||||
|       .avatar { |       .avatar { | ||||||
|         color: var(--ide-text-color, var(--gl-text-color-default)); |         color: var(--ide-text-color, var(--gl-text-color-default)); | ||||||
|         background-color: var(--ide-highlight-background, $white); |         background-color: var(--ide-highlight-background, var(--gl-background-color-default)); | ||||||
|         border-color: var(--ide-highlight-background, var(--gl-avatar-border-color-default)); |         border-color: var(--ide-highlight-background, var(--gl-avatar-border-color-default)); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  | @ -189,7 +189,7 @@ | ||||||
|   input[type='search'], |   input[type='search'], | ||||||
|   .filtered-search-box { |   .filtered-search-box { | ||||||
|     border-color: var(--ide-input-border, var(--gl-border-color-default)); |     border-color: var(--ide-input-border, var(--gl-border-color-default)); | ||||||
|     background: var(--ide-input-background, $white) !important; |     background: var(--ide-input-background, var(--gl-background-color-default)) !important; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   input[type='text']:not([disabled]):not([readonly]):focus, |   input[type='text']:not([disabled]):not([readonly]):focus, | ||||||
|  | @ -212,11 +212,11 @@ | ||||||
| 
 | 
 | ||||||
|   .filtered-search-token .value-container, |   .filtered-search-token .value-container, | ||||||
|   .filtered-search-term .value-container { |   .filtered-search-term .value-container { | ||||||
|     background-color: var(--ide-dropdown-hover-background, $gray-50); |     background-color: var(--ide-dropdown-hover-background, var(--gl-background-color-strong)); | ||||||
|     color: var(--ide-text-color, var(--gl-text-color-default)); |     color: var(--ide-text-color, var(--gl-text-color-default)); | ||||||
| 
 | 
 | ||||||
|     &:hover { |     &:hover { | ||||||
|       background-color: var(--ide-input-border, $gray-100); |       background-color: var(--ide-input-border, var(--gl-border-color-default)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -245,25 +245,25 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .dropdown-menu-toggle { |   .dropdown-menu-toggle { | ||||||
|     border-color: var(--ide-btn-default-border, $gray-200); |     border-color: var(--ide-btn-default-border, var(--gl-border-color-strong)); | ||||||
|     background-color: var(--ide-input-background, transparent); |     background-color: var(--ide-input-background, transparent); | ||||||
| 
 | 
 | ||||||
|     &:hover, |     &:hover, | ||||||
|     &:focus { |     &:focus { | ||||||
|       background-color: var(--ide-dropdown-btn-hover-background, $gray-50) !important; |       background-color: var(--ide-dropdown-btn-hover-background, var(--gl-background-color-strong)) !important; | ||||||
|       border-color: var(--ide-dropdown-btn-hover-border, $gray-200) !important; |       border-color: var(--ide-dropdown-btn-hover-border, var(--gl-border-color-strong)) !important; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // todo: remove this block after all default buttons have been migrated to gl-button |   // todo: remove this block after all default buttons have been migrated to gl-button | ||||||
|   .btn-default:not(.gl-button) { |   .btn-default:not(.gl-button) { | ||||||
|     background-color: var(--ide-btn-default-background, $white) !important; |     background-color: var(--ide-btn-default-background, var(--gl-background-color-default)) !important; | ||||||
|     border-color: var(--ide-btn-default-border, var(--gl-border-color-default)); |     border-color: var(--ide-btn-default-border, var(--gl-border-color-default)); | ||||||
| 
 | 
 | ||||||
|     &:hover, |     &:hover, | ||||||
|     &:focus { |     &:focus { | ||||||
|       border-color: var(--ide-btn-default-hover-border, var(--gl-border-color-default)) !important; |       border-color: var(--ide-btn-default-hover-border, var(--gl-border-color-default)) !important; | ||||||
|       background-color: var(--ide-btn-default-background, $gray-50) !important; |       background-color: var(--ide-btn-default-background, var(--gl-background-color-strong)) !important; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     &:active, |     &:active, | ||||||
|  | @ -276,21 +276,21 @@ | ||||||
|   .dropdown-menu { |   .dropdown-menu { | ||||||
|     color: var(--ide-text-color, var(--gl-text-color-default)); |     color: var(--ide-text-color, var(--gl-text-color-default)); | ||||||
|     border-color: var(--ide-background, var(--gl-border-color-default)); |     border-color: var(--ide-background, var(--gl-border-color-default)); | ||||||
|     background-color: var(--ide-dropdown-background, $white); |     background-color: var(--ide-dropdown-background, var(--gl-background-color-default)); | ||||||
| 
 | 
 | ||||||
|     .nav-links { |     .nav-links { | ||||||
|       background-color: var(--ide-dropdown-hover-background, $white); |       background-color: var(--ide-dropdown-hover-background, var(--gl-background-color-default)); | ||||||
|       border-color: var(--ide-dropdown-hover-background, var(--gl-border-color-default)); |       border-color: var(--ide-dropdown-hover-background, var(--gl-border-color-default)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .gl-tabs-nav { |     .gl-tabs-nav { | ||||||
|       background-color: var(--ide-dropdown-hover-background, $white); |       background-color: var(--ide-dropdown-hover-background, var(--gl-background-color-default)); | ||||||
|       box-shadow: inset 0 -2px 0 0 var(--ide-dropdown-hover-background, var(--gl-border-color-default)); |       box-shadow: inset 0 -2px 0 0 var(--ide-dropdown-hover-background, var(--gl-border-color-default)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .divider { |     .divider { | ||||||
|       background-color: var(--ide-dropdown-hover-background, $gray-100); |       background-color: var(--ide-dropdown-hover-background, var(--gl-border-color-default)); | ||||||
|       border-color: var(--ide-dropdown-hover-background, $gray-100); |       border-color: var(--ide-dropdown-hover-background, var(--gl-border-color-default)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     li > a:not(.disable-hover):hover, |     li > a:not(.disable-hover):hover, | ||||||
|  | @ -298,37 +298,37 @@ | ||||||
|     li button:not(.disable-hover):hover, |     li button:not(.disable-hover):hover, | ||||||
|     li button:not(.disable-hover):focus, |     li button:not(.disable-hover):focus, | ||||||
|     li button.is-focused { |     li button.is-focused { | ||||||
|       background-color: var(--ide-dropdown-hover-background, $gray-50); |       background-color: var(--ide-dropdown-hover-background, var(--gl-background-color-strong)); | ||||||
|       color: var(--ide-text-color, var(--gl-text-color-default)); |       color: var(--ide-text-color, var(--gl-text-color-default)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .dropdown-title, |   .dropdown-title, | ||||||
|   .dropdown-input { |   .dropdown-input { | ||||||
|     border-color: var(--ide-dropdown-hover-background, $gray-100) !important; |     border-color: var(--ide-dropdown-hover-background, var(--gl-border-color-default)) !important; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // todo: remove this block after all disabled buttons have been migrated to gl-button |   // todo: remove this block after all disabled buttons have been migrated to gl-button | ||||||
|   .btn[disabled]:not(.gl-button) { |   .btn[disabled]:not(.gl-button) { | ||||||
|     background-color: var(--ide-btn-default-background, $gray-10) !important; |     background-color: var(--ide-btn-default-background, var(--gl-background-color-subtle)) !important; | ||||||
|     border: 1px solid var(--ide-btn-disabled-border, $gray-100) !important; |     border: 1px solid var(--ide-btn-disabled-border, var(--gl-border-color-default)) !important; | ||||||
|     color: var(--ide-btn-disabled-color, var(--gl-text-color-disabled)) !important; |     color: var(--ide-btn-disabled-color, var(--gl-text-color-disabled)) !important; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .md table:not(.code) tbody { |   .md table:not(.code) tbody { | ||||||
|     background-color: var(--ide-empty-state-background, $white); |     background-color: var(--ide-empty-state-background, var(--gl-background-color-default)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   .animation-container { |   .animation-container { | ||||||
|     [class^='skeleton-line-'] { |     [class^='skeleton-line-'] { | ||||||
|       background-color: var(--ide-animation-gradient-1, $gray-100); |       background-color: var(--ide-animation-gradient-1, var(--gl-border-color-default)); | ||||||
| 
 | 
 | ||||||
|       &::after { |       &::after { | ||||||
|         background-image: linear-gradient(to right, |         background-image: linear-gradient(to right, | ||||||
|         var(--ide-animation-gradient-1, $gray-100) 0%, |         var(--ide-animation-gradient-1, var(--gl-border-color-default)) 0%, | ||||||
|         var(--ide-animation-gradient-2, $gray-10) 20%, |         var(--ide-animation-gradient-2, var(--gl-background-color-subtle)) 20%, | ||||||
|         var(--ide-animation-gradient-1, $gray-100) 40%, |         var(--ide-animation-gradient-1, var(--gl-border-color-default)) 40%, | ||||||
|         var(--ide-animation-gradient-1, $gray-100) 100%); |         var(--ide-animation-gradient-1, var(--gl-border-color-default)) 100%); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -62,8 +62,8 @@ $design-pin-diameter-sm: 24px; | ||||||
|   height: $design-pin-diameter-sm; |   height: $design-pin-diameter-sm; | ||||||
|   width: $design-pin-diameter-sm; |   width: $design-pin-diameter-sm; | ||||||
|   box-sizing: content-box; |   box-sizing: content-box; | ||||||
|   @apply gl-bg-purple-500 dark:gl-bg-purple-700; |   @apply gl-text-neutral-0; | ||||||
|   color: var(--white, $white); |   background-color: var(--gl-status-brand-icon-color); | ||||||
|   font-weight: $gl-font-weight-bold; |   font-weight: $gl-font-weight-bold; | ||||||
|   border-radius: 50%; |   border-radius: 50%; | ||||||
|   z-index: 1; |   z-index: 1; | ||||||
|  | @ -71,16 +71,16 @@ $design-pin-diameter-sm: 24px; | ||||||
|   border: 0; |   border: 0; | ||||||
| 
 | 
 | ||||||
|   &.draft { |   &.draft { | ||||||
|     background-color: var(--orange-500, $orange-500); |     background-color: var(--gl-status-warning-icon-color); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &.resolved { |   &.resolved { | ||||||
|     background-color: var(--gray-500, $gray-500); |     @apply gl-bg-strong gl-text-strong; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &.on-image { |   &.on-image { | ||||||
|     box-shadow: 0 2px 4px var(--gl-color-alpha-dark-8), 0 0 1px var(--gl-color-alpha-dark-24); |     box-shadow: 0 2px 4px var(--gl-color-alpha-dark-8), 0 0 1px var(--gl-color-alpha-dark-24); | ||||||
|     border: var(--white, $white) 2px solid; |     @apply gl-border-2 gl-border-solid gl-border-neutral-0; | ||||||
|     will-change: transform, box-shadow, opacity; |     will-change: transform, box-shadow, opacity; | ||||||
|     // NOTE: verbose transition property required for Safari |     // NOTE: verbose transition property required for Safari | ||||||
|     transition: transform $general-hover-transition-duration linear, box-shadow $general-hover-transition-duration linear, opacity $general-hover-transition-duration linear; |     transition: transform $general-hover-transition-duration linear, box-shadow $general-hover-transition-duration linear, opacity $general-hover-transition-duration linear; | ||||||
|  | @ -106,7 +106,7 @@ $design-pin-diameter-sm: 24px; | ||||||
| 
 | 
 | ||||||
|   &.small { |   &.small { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     border: 1px solid var(--white, $white); |     @apply gl-border-1 gl-border-solid gl-border-neutral-0; | ||||||
|     height: $design-pin-diameter-sm; |     height: $design-pin-diameter-sm; | ||||||
|     width: $design-pin-diameter-sm; |     width: $design-pin-diameter-sm; | ||||||
|   } |   } | ||||||
|  | @ -161,7 +161,7 @@ $design-pin-diameter-sm: 24px; | ||||||
| 
 | 
 | ||||||
|     &::before { |     &::before { | ||||||
|       content: ''; |       content: ''; | ||||||
|       border-left: 1px solid var(--gray-100, $gray-100); |       @apply gl-border-l; | ||||||
|       position: absolute; |       position: absolute; | ||||||
|       left: 11px; |       left: 11px; | ||||||
|       top: -17px; |       top: -17px; | ||||||
|  |  | ||||||
|  | @ -1,36 +1,10 @@ | ||||||
| @import 'page_bundles/mixins_and_variables_and_functions'; | @import 'page_bundles/mixins_and_variables_and_functions'; | ||||||
| 
 | 
 | ||||||
| .x-axis path, |  | ||||||
| .y-axis path, |  | ||||||
| .label-x-axis-line, |  | ||||||
| .label-y-axis-line { |  | ||||||
|   fill: none; |  | ||||||
|   stroke-width: 1; |  | ||||||
|   shape-rendering: crispEdges; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .x-axis path, |  | ||||||
| .y-axis path { |  | ||||||
|   stroke: var(--gray-300, $gray-300); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .label-x-axis-line, |  | ||||||
| .label-y-axis-line { |  | ||||||
|   stroke: var(--gray-100, $gray-100); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| .y-axis { |  | ||||||
|   line { |  | ||||||
|     stroke: var(--gray-300, $gray-300); |  | ||||||
|     stroke-width: 1; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Deploy boards |  * Deploy boards | ||||||
| */ | */ | ||||||
| .deploy-board { | .deploy-board { | ||||||
|   background-color: var(--gray-50, $gray-50); |   @apply gl-bg-strong; | ||||||
|   min-height: 20px; |   min-height: 20px; | ||||||
| 
 | 
 | ||||||
|   > .loading-icon, |   > .loading-icon, | ||||||
|  |  | ||||||
|  | @ -171,7 +171,7 @@ $ide-commit-header-height: 48px; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &:not([disabled]):hover { |   &:not([disabled]):hover { | ||||||
|     background-color: var(--ide-input-border, $gray-100); |     background-color: var(--ide-input-border, var(--gl-border-color-default)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &:not([disabled]):focus { |   &:not([disabled]):focus { | ||||||
|  |  | ||||||
|  | @ -621,7 +621,7 @@ | ||||||
|       border: 1px solid var(--gl-border-color-default); |       border: 1px solid var(--gl-border-color-default); | ||||||
| 
 | 
 | ||||||
|       .circle-icon-container { |       .circle-icon-container { | ||||||
|         color: var(--gray-100, $gray-100); |         color: var(--gl-border-color-default); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | @ -857,8 +857,7 @@ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .memory-graph-container { | .memory-graph-container { | ||||||
|   background: var(--white, $white); |   @apply gl-bg-default gl-border; | ||||||
|   border: 1px solid var(--gray-100, $gray-100); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .review-bar-component { | .review-bar-component { | ||||||
|  |  | ||||||
|  | @ -24,7 +24,6 @@ $item-height: 40px; | ||||||
| $details-cell-width: 180px; | $details-cell-width: 180px; | ||||||
| $timeline-cell-height: 32px; | $timeline-cell-height: 32px; | ||||||
| $timeline-cell-width: 180px; | $timeline-cell-width: 180px; | ||||||
| $border-style: 1px solid var(--gray-100, $gray-100); |  | ||||||
| $gradient-dark-gray: rgba(0, 0, 0, 0.15); | $gradient-dark-gray: rgba(0, 0, 0, 0.15); | ||||||
| $gradient-gray: rgba(255, 255, 255, 0.001); | $gradient-gray: rgba(255, 255, 255, 0.001); | ||||||
| $scroll-top-gradient: linear-gradient(to bottom, $gradient-dark-gray 0%, $gradient-gray 100%); | $scroll-top-gradient: linear-gradient(to bottom, $gradient-dark-gray 0%, $gradient-gray 100%); | ||||||
|  |  | ||||||
|  | @ -21,7 +21,8 @@ | ||||||
| 
 | 
 | ||||||
| // Pipeline mini graph | // Pipeline mini graph | ||||||
| .pipeline-mini-graph-stage-container { | .pipeline-mini-graph-stage-container { | ||||||
|   // Temoorarily sets a max height for the dropdown |   position: relative; | ||||||
|  |   // Temporarily sets a max height for the dropdown | ||||||
|   // until GlDisclosureDropdown supports adding |   // until GlDisclosureDropdown supports adding | ||||||
|   // a max-height value |   // a max-height value | ||||||
|   // see https://gitlab.com/gitlab-org/gitlab-ui/-/issues/3010 |   // see https://gitlab.com/gitlab-org/gitlab-ui/-/issues/3010 | ||||||
|  |  | ||||||
|  | @ -159,7 +159,7 @@ $disclosure-hierarchy-chevron-dimension: 1.2rem; | ||||||
|   display: flex; |   display: flex; | ||||||
|   position: relative; |   position: relative; | ||||||
|   font-size: $gl-font-size-sm; |   font-size: $gl-font-size-sm; | ||||||
|   border: 1px solid var(--gray-100, $gray-100); |   @apply gl-border; | ||||||
|   border-right-style: none; |   border-right-style: none; | ||||||
|   border-left-style: none; |   border-left-style: none; | ||||||
|   line-height: $gl-line-height-16; |   line-height: $gl-line-height-16; | ||||||
|  | @ -177,7 +177,7 @@ $disclosure-hierarchy-chevron-dimension: 1.2rem; | ||||||
|     width: $disclosure-hierarchy-chevron-dimension; |     width: $disclosure-hierarchy-chevron-dimension; | ||||||
|     height: $disclosure-hierarchy-chevron-dimension; |     height: $disclosure-hierarchy-chevron-dimension; | ||||||
|     transform: rotate(45deg) skew(14deg, 14deg); |     transform: rotate(45deg) skew(14deg, 14deg); | ||||||
|     border: 1px solid var(--gray-100, $gray-100); |     @apply gl-border; | ||||||
|     border-color: inherit; |     border-color: inherit; | ||||||
|     border-bottom-color: transparent; |     border-bottom-color: transparent; | ||||||
|     border-left-color: transparent; |     border-left-color: transparent; | ||||||
|  | @ -197,7 +197,7 @@ $disclosure-hierarchy-chevron-dimension: 1.2rem; | ||||||
| 
 | 
 | ||||||
|   .disclosure-hierarchy-item:first-child & { |   .disclosure-hierarchy-item:first-child & { | ||||||
|     padding-left: $gl-spacing-scale-3; |     padding-left: $gl-spacing-scale-3; | ||||||
|     border-left: 1px solid var(--gray-100, $gray-100); |     @apply gl-border-l; | ||||||
|     border-top-left-radius: $gl-border-radius-base; |     border-top-left-radius: $gl-border-radius-base; | ||||||
|     border-bottom-left-radius: $gl-border-radius-base; |     border-bottom-left-radius: $gl-border-radius-base; | ||||||
| 
 | 
 | ||||||
|  | @ -215,7 +215,7 @@ $disclosure-hierarchy-chevron-dimension: 1.2rem; | ||||||
| 
 | 
 | ||||||
|   .disclosure-hierarchy-item:last-child & { |   .disclosure-hierarchy-item:last-child & { | ||||||
|     padding-right: $gl-spacing-scale-4; |     padding-right: $gl-spacing-scale-4; | ||||||
|     border-right: 1px solid var(--gray-100, $gray-100); |     @apply gl-border-r; | ||||||
|     border-top-right-radius: $gl-border-radius-base; |     border-top-right-radius: $gl-border-radius-base; | ||||||
|     border-bottom-right-radius: $gl-border-radius-base; |     border-bottom-right-radius: $gl-border-radius-base; | ||||||
| 
 | 
 | ||||||
|  | @ -349,7 +349,7 @@ $disclosure-hierarchy-chevron-dimension: 1.2rem; | ||||||
|     .work-item-design-grid { |     .work-item-design-grid { | ||||||
|       @apply gl-grid-cols-3; |       @apply gl-grid-cols-3; | ||||||
|     } |     } | ||||||
|      | 
 | ||||||
|     .work-item-design-show-sm { |     .work-item-design-show-sm { | ||||||
|       @apply gl-block; |       @apply gl-block; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     &:first-child { |     &:first-child { | ||||||
|       border-top: 1px solid $gray-100; |       @apply gl-border-t; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ | ||||||
|         content: ' '; |         content: ' '; | ||||||
|         height: 100%; |         height: 100%; | ||||||
|         width: 4px; |         width: 4px; | ||||||
|         background-color: $gray-100; |         background-color: $gl-border-color-default; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       position: relative; |       position: relative; | ||||||
|  |  | ||||||
|  | @ -59,10 +59,6 @@ class ProjectsFinder < UnionFinder | ||||||
|         init_collection |         init_collection | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|     if Feature.enabled?(:hide_projects_of_banned_users) |  | ||||||
|       collection = without_created_and_owned_by_banned_user(collection) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     use_cte = params.delete(:use_cte) |     use_cte = params.delete(:use_cte) | ||||||
|     collection = Project.wrap_with_cte(collection) if use_cte |     collection = Project.wrap_with_cte(collection) if use_cte | ||||||
|     collection = filter_projects(collection) |     collection = filter_projects(collection) | ||||||
|  | @ -300,12 +296,6 @@ class ProjectsFinder < UnionFinder | ||||||
|     { min_access_level: params[:min_access_level] } |     { min_access_level: params[:min_access_level] } | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def without_created_and_owned_by_banned_user(projects) |  | ||||||
|     return projects if current_user&.can?(:admin_all_resources) |  | ||||||
| 
 |  | ||||||
|     projects.without_created_and_owned_by_banned_user |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   # Returns the available organizations to filter topics |   # Returns the available organizations to filter topics | ||||||
|   def topic_organization_ids |   def topic_organization_ids | ||||||
|     @topic_organization_ids ||= begin |     @topic_organization_ids ||= begin | ||||||
|  |  | ||||||
|  | @ -508,8 +508,6 @@ module ApplicationHelper | ||||||
|       title = format(issuable_title, issuable: _('issue')) |       title = format(issuable_title, issuable: _('issue')) | ||||||
|     when MergeRequest |     when MergeRequest | ||||||
|       title = format(issuable_title, issuable: _('merge request')) |       title = format(issuable_title, issuable: _('merge request')) | ||||||
|     when Project |  | ||||||
|       title = _('This project is hidden because its creator has been banned') |  | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     return unless title |     return unless title | ||||||
|  |  | ||||||
|  | @ -5,8 +5,6 @@ module ProjectsHelper | ||||||
|   include CompareHelper |   include CompareHelper | ||||||
|   include Gitlab::Allowable |   include Gitlab::Allowable | ||||||
| 
 | 
 | ||||||
|   BANNED = 'banned' |  | ||||||
| 
 |  | ||||||
|   def project_incident_management_setting |   def project_incident_management_setting | ||||||
|     @project_incident_management_setting ||= @project.incident_management_setting || |     @project_incident_management_setting ||= @project.incident_management_setting || | ||||||
|       @project.build_incident_management_setting |       @project.build_incident_management_setting | ||||||
|  | @ -573,7 +571,7 @@ module ProjectsHelper | ||||||
|       project_avatar: project.avatar_url, |       project_avatar: project.avatar_url, | ||||||
|       project_name: project.name, |       project_name: project.name, | ||||||
|       project_id: project.id, |       project_id: project.id, | ||||||
|       project_visibility_level: visibility_level_name(project) |       project_visibility_level: Gitlab::VisibilityLevel.string_level(project.visibility_level) | ||||||
|     }.merge( |     }.merge( | ||||||
|       dropdown_attributes, |       dropdown_attributes, | ||||||
|       fork_button_attributes, |       fork_button_attributes, | ||||||
|  | @ -672,10 +670,6 @@ module ProjectsHelper | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def visibility_level_content(project, css_class: nil, icon_css_class: nil, icon_variant: nil) |   def visibility_level_content(project, css_class: nil, icon_css_class: nil, icon_variant: nil) | ||||||
|     if project.created_and_owned_by_banned_user? && Feature.enabled?(:hide_projects_of_banned_users) |  | ||||||
|       return hidden_resource_icon(project, css_class: css_class, variant: icon_variant) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     title = visibility_icon_description(project) |     title = visibility_icon_description(project) | ||||||
|     container_class = [ |     container_class = [ | ||||||
|       'has-tooltip gl-border-0 gl-bg-transparent gl-p-0 gl-leading-0 gl-text-inherit', |       'has-tooltip gl-border-0 gl-bg-transparent gl-p-0 gl-leading-0 gl-text-inherit', | ||||||
|  | @ -791,14 +785,6 @@ module ProjectsHelper | ||||||
|     } |     } | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def visibility_level_name(project) |  | ||||||
|     if project.created_and_owned_by_banned_user? && Feature.enabled?(:hide_projects_of_banned_users) |  | ||||||
|       BANNED |  | ||||||
|     else |  | ||||||
|       Gitlab::VisibilityLevel.string_level(project.visibility_level) |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def can_admin_project_clusters?(project) |   def can_admin_project_clusters?(project) | ||||||
|     project.clusters.any? && can?(current_user, :admin_cluster, project) |     project.clusters.any? && can?(current_user, :admin_cluster, project) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -1072,26 +1072,6 @@ class Project < ApplicationRecord | ||||||
|   scope :for_group_and_its_ancestor_groups, ->(group) { where(namespace_id: group.self_and_ancestors.select(:id)) } |   scope :for_group_and_its_ancestor_groups, ->(group) { where(namespace_id: group.self_and_ancestors.select(:id)) } | ||||||
|   scope :is_importing, -> { with_import_state.where(import_state: { status: %w[started scheduled] }) } |   scope :is_importing, -> { with_import_state.where(import_state: { status: %w[started scheduled] }) } | ||||||
| 
 | 
 | ||||||
|   scope :without_created_and_owned_by_banned_user, -> do |  | ||||||
|     where_not_exists( |  | ||||||
|       Users::BannedUser.joins( |  | ||||||
|         'INNER JOIN project_authorizations ON project_authorizations.user_id = banned_users.user_id' |  | ||||||
|       ).where('projects.creator_id = banned_users.user_id') |  | ||||||
|         .where('project_authorizations.project_id = projects.id') |  | ||||||
|         .where(project_authorizations: { access_level: Gitlab::Access::OWNER }) |  | ||||||
|     ) |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   scope :with_created_and_owned_by_banned_user, -> do |  | ||||||
|     where_exists( |  | ||||||
|       Users::BannedUser.joins( |  | ||||||
|         'INNER JOIN project_authorizations ON project_authorizations.user_id = banned_users.user_id' |  | ||||||
|       ).where('projects.creator_id = banned_users.user_id') |  | ||||||
|         .where('project_authorizations.project_id = projects.id') |  | ||||||
|         .where(project_authorizations: { access_level: Gitlab::Access::OWNER }) |  | ||||||
|     ) |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   class << self |   class << self | ||||||
|     # Searches for a list of projects based on the query given in `query`. |     # Searches for a list of projects based on the query given in `query`. | ||||||
|     # |     # | ||||||
|  | @ -3367,12 +3347,6 @@ class Project < ApplicationRecord | ||||||
|     pending_delete? || hidden? |     pending_delete? || hidden? | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def created_and_owned_by_banned_user? |  | ||||||
|     return false unless creator |  | ||||||
| 
 |  | ||||||
|     creator.banned? && team.max_member_access(creator.id) == Gitlab::Access::OWNER |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def work_items_feature_flag_enabled? |   def work_items_feature_flag_enabled? | ||||||
|     group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self) |     group&.work_items_feature_flag_enabled? || Feature.enabled?(:work_items, self) | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -309,10 +309,6 @@ class ProjectPolicy < BasePolicy | ||||||
| 
 | 
 | ||||||
|   condition(:namespace_catalog_available) { namespace_catalog_available? } |   condition(:namespace_catalog_available) { namespace_catalog_available? } | ||||||
| 
 | 
 | ||||||
|   condition(:created_and_owned_by_banned_user, scope: :subject) do |  | ||||||
|     Feature.enabled?(:hide_projects_of_banned_users) && @subject.created_and_owned_by_banned_user? |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   desc "User has either planner or reporter access" |   desc "User has either planner or reporter access" | ||||||
|   condition(:planner_or_reporter_access) do |   condition(:planner_or_reporter_access) do | ||||||
|     can?(:reporter_access) || can?(:planner_access) |     can?(:reporter_access) || can?(:planner_access) | ||||||
|  | @ -1100,10 +1096,6 @@ class ProjectPolicy < BasePolicy | ||||||
|     enable :write_model_experiments |     enable :write_model_experiments | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   rule { ~admin & ~organization_owner & created_and_owned_by_banned_user }.policy do |  | ||||||
|     prevent :read_project |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   rule { ~private_project & guest & external_user }.enable :read_container_image |   rule { ~private_project & guest & external_user }.enable :read_container_image | ||||||
| 
 | 
 | ||||||
|   rule { can?(:create_pipeline_schedule) }.policy do |   rule { can?(:create_pipeline_schedule) }.policy do | ||||||
|  |  | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Groups # rubocop:disable Gitlab/BoundedContexts -- existing top-level module | ||||||
|  |   class RestoreService < Groups::BaseService | ||||||
|  |     def execute | ||||||
|  |       return error(_('You are not authorized to perform this action')) unless can?(current_user, :remove_group, group) | ||||||
|  |       return error(_('Group has not been marked for deletion')) unless group.marked_for_deletion? | ||||||
|  |       return error(_('Group deletion is in progress')) if group.deleted? | ||||||
|  | 
 | ||||||
|  |       result = remove_deletion_schedule | ||||||
|  | 
 | ||||||
|  |       group.reset | ||||||
|  | 
 | ||||||
|  |       log_event if result[:status] == :success | ||||||
|  | 
 | ||||||
|  |       result | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     private | ||||||
|  | 
 | ||||||
|  |     def remove_deletion_schedule | ||||||
|  |       deletion_schedule = group.deletion_schedule | ||||||
|  | 
 | ||||||
|  |       if deletion_schedule.destroy | ||||||
|  |         success | ||||||
|  |       else | ||||||
|  |         error(_('Could not restore the group')) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def log_event | ||||||
|  |       log_info("User #{current_user.id} restored group #{group.full_path}") | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | Groups::RestoreService.prepend_mod | ||||||
|  | @ -9,7 +9,7 @@ module Projects | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def self.query(project_ids) |     def self.query(project_ids) | ||||||
|       MergeRequest.opened.of_projects(project_ids) |       MergeRequest.opened.without_hidden.of_projects(project_ids) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,61 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Projects | ||||||
|  |   class RestoreService < BaseService | ||||||
|  |     include Gitlab::Utils::StrongMemoize | ||||||
|  | 
 | ||||||
|  |     DELETED_SUFFIX_REGEX = /-deleted-[a-zA-Z0-9]+\z/ | ||||||
|  | 
 | ||||||
|  |     def execute | ||||||
|  |       return error(_('Project already deleted')) if project.pending_delete? | ||||||
|  | 
 | ||||||
|  |       result = ::Projects::UpdateService.new( | ||||||
|  |         project, | ||||||
|  |         current_user, | ||||||
|  |         { archived: false, | ||||||
|  |           hidden: false, | ||||||
|  |           marked_for_deletion_at: nil, | ||||||
|  |           deleting_user: nil, | ||||||
|  |           name: updated_value(project.name), | ||||||
|  |           path: updated_value(project.path) } | ||||||
|  |       ).execute | ||||||
|  | 
 | ||||||
|  |       if result[:status] == :success | ||||||
|  |         log_event | ||||||
|  | 
 | ||||||
|  |         ## Trigger root namespace statistics refresh, to add project_statistics of | ||||||
|  |         ## projects restored from deletion | ||||||
|  |         Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       result | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     private | ||||||
|  | 
 | ||||||
|  |     def log_event | ||||||
|  |       log_info("User #{current_user.id} restored project #{project.full_path}") | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def suffix | ||||||
|  |       original_path_taken?(project) ? "-#{SecureRandom.alphanumeric(5)}" : "" | ||||||
|  |     end | ||||||
|  |     strong_memoize_attr :suffix | ||||||
|  | 
 | ||||||
|  |     def original_path_taken?(project) | ||||||
|  |       existing_project = ::Project.find_by_full_path(original_value(project.full_path)) | ||||||
|  | 
 | ||||||
|  |       existing_project.present? && existing_project.id != project.id | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def original_value(value) | ||||||
|  |       value.sub(DELETED_SUFFIX_REGEX, '') | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def updated_value(value) | ||||||
|  |       "#{original_value(value)}#{suffix}" | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | Projects::RestoreService.prepend_mod | ||||||
|  | @ -129,10 +129,7 @@ | ||||||
|               = _('access:') |               = _('access:') | ||||||
|             %strong |             %strong | ||||||
|               = visibility_level_content(@project, css_class: visibility_level_color(@project.visibility_level)) |               = visibility_level_content(@project, css_class: visibility_level_color(@project.visibility_level)) | ||||||
|               - if @project.created_and_owned_by_banned_user? && Feature.enabled?(:hide_projects_of_banned_users) |               = visibility_level_label(@project.visibility_level) | ||||||
|                 = _('This project is hidden because its creator has been banned') |  | ||||||
|               - else |  | ||||||
|                 = visibility_level_label(@project.visibility_level) |  | ||||||
| 
 | 
 | ||||||
|     = render 'shared/custom_attributes', custom_attributes: @project.custom_attributes |     = render 'shared/custom_attributes', custom_attributes: @project.custom_attributes | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| --- |  | ||||||
| name: hide_projects_of_banned_users |  | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121488 |  | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/412621 |  | ||||||
| milestone: '16.2' |  | ||||||
| type: development |  | ||||||
| group: group::anti-abuse |  | ||||||
| default_enabled: false |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| - title: "GitLab.com certificate-based integration with Kubernetes" | - title: "GitLab.com certificate-based integration with Kubernetes" | ||||||
|   announcement_milestone: "14.5" |   announcement_milestone: "14.5" | ||||||
|   removal_milestone: "15.9" |   removal_milestone: "18.0" | ||||||
|  |   window: 3 | ||||||
|   breaking_change: true |   breaking_change: true | ||||||
|   body: | |   body: | | ||||||
|     The certificate-based integration with Kubernetes will be [deprecated and removed](https://about.gitlab.com/blog/2021/11/15/deprecating-the-cert-based-kubernetes-integration/). As a GitLab.com user, on new namespaces, you will no longer be able to integrate GitLab and your cluster using the certificate-based approach as of GitLab 15.0. The integration for current users will be enabled per namespace. |     The certificate-based integration with Kubernetes will be [deprecated and removed](https://about.gitlab.com/blog/2021/11/15/deprecating-the-cert-based-kubernetes-integration/). As a GitLab.com user, on new namespaces, you will no longer be able to integrate GitLab and your cluster using the certificate-based approach as of GitLab 15.0. The integration for current users will be enabled per namespace. | ||||||
|  | @ -8,9 +9,6 @@ | ||||||
|     For a more robust, secure, forthcoming, and reliable integration with Kubernetes, we recommend you use the |     For a more robust, secure, forthcoming, and reliable integration with Kubernetes, we recommend you use the | ||||||
|     [agent for Kubernetes](https://docs.gitlab.com/user/clusters/agent/) to connect Kubernetes clusters with GitLab. [How do I migrate?](https://docs.gitlab.com/user/infrastructure/clusters/migrate_to_gitlab_agent/) |     [agent for Kubernetes](https://docs.gitlab.com/user/clusters/agent/) to connect Kubernetes clusters with GitLab. [How do I migrate?](https://docs.gitlab.com/user/infrastructure/clusters/migrate_to_gitlab_agent/) | ||||||
| 
 | 
 | ||||||
|     Although an explicit removal date is set, we don't plan to remove this feature until the new solution has feature parity. |  | ||||||
|     For more information about the blockers to removal, see [this issue](https://gitlab.com/gitlab-org/configure/general/-/issues/199). |  | ||||||
| 
 |  | ||||||
|     For updates and details about this deprecation, follow [this epic](https://gitlab.com/groups/gitlab-org/configure/-/epics/8). |     For updates and details about this deprecation, follow [this epic](https://gitlab.com/groups/gitlab-org/configure/-/epics/8). | ||||||
| 
 | 
 | ||||||
|     GitLab Self-Managed customers can still use the feature [with a feature flag](https://docs.gitlab.com/update/deprecations/#self-managed-certificate-based-integration-with-kubernetes). |     GitLab Self-Managed customers can still use the feature [with a feature flag](https://docs.gitlab.com/update/deprecations/#self-managed-certificate-based-integration-with-kubernetes). | ||||||
|  |  | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | --- | ||||||
|  | migration_job_name: BackfillAmazonInstanceAuditEventDestinations | ||||||
|  | description: Backfill the audit_events_instance_amazon_s3_configurations table to audit_events_instance_external_streaming_destinations | ||||||
|  | feature_category: audit_events | ||||||
|  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/184039 | ||||||
|  | milestone: '17.11' | ||||||
|  | queued_migration_version: 20250310151216 | ||||||
|  | finalized_by: # version of the migration that finalized this BBM | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class QueueBackfillAmazonInstanceAuditEventDestinations < Gitlab::Database::Migration[2.2] | ||||||
|  |   milestone '17.11' | ||||||
|  | 
 | ||||||
|  |   restrict_gitlab_migration gitlab_schema: :gitlab_main | ||||||
|  | 
 | ||||||
|  |   MIGRATION = "BackfillAmazonInstanceAuditEventDestinations" | ||||||
|  |   DELAY_INTERVAL = 2.minutes | ||||||
|  |   BATCH_SIZE = 1000 | ||||||
|  |   SUB_BATCH_SIZE = 100 | ||||||
|  | 
 | ||||||
|  |   def up | ||||||
|  |     queue_batched_background_migration( | ||||||
|  |       MIGRATION, | ||||||
|  |       :audit_events_instance_amazon_s3_configurations, | ||||||
|  |       :id, | ||||||
|  |       job_interval: DELAY_INTERVAL, | ||||||
|  |       batch_size: BATCH_SIZE, | ||||||
|  |       sub_batch_size: SUB_BATCH_SIZE | ||||||
|  |     ) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def down | ||||||
|  |     delete_batched_background_migration(MIGRATION, :audit_events_instance_amazon_s3_configurations, :id, []) | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | 3d5a619453f9db8266fc1a8534a64286f16037a686b9e6a54623061a20ba6e64 | ||||||
|  | @ -0,0 +1,53 @@ | ||||||
|  | const path = require('path'); | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   names: ['Custom rule/unnecessary-traversal'], | ||||||
|  |   description: 'Links should not traverse out and back into the same directory', | ||||||
|  |   tags: ['gitlab-docs', 'links'], | ||||||
|  |   function: (params, onError) => { | ||||||
|  |     // Get the current file directory name
 | ||||||
|  |     const { name: filePath = '', lines = [] } = params; | ||||||
|  |     const dirName = path.basename(path.dirname(filePath)); | ||||||
|  | 
 | ||||||
|  |     if (!filePath) return; | ||||||
|  |     // Process each line
 | ||||||
|  |     lines.forEach((line, i) => { | ||||||
|  |       // Skip lines that don't contain markdown links with relative paths
 | ||||||
|  |       if (!line.includes('](../')) return; | ||||||
|  | 
 | ||||||
|  |       // Regular expression to find markdown links with potential traversal issues
 | ||||||
|  |       const linkRegex = /\[([^\]]+)\]\((\.\.\/([^/]+)\/)(.*?)(?:\s+"[^"]*")?\)/g; | ||||||
|  | 
 | ||||||
|  |       let match; | ||||||
|  |       while ((match = linkRegex.exec(line)) !== null) { | ||||||
|  |         /*  | ||||||
|  |           Destructure regex match into: | ||||||
|  |           - fullMatch: the entire link | ||||||
|  |           - linkText: the link text | ||||||
|  |           - traversalPart: the '../dir/' part | ||||||
|  |           - traversalDir: just the 'dir' part | ||||||
|  |           - targetPath: the rest of the path | ||||||
|  |         */ | ||||||
|  |         const [fullMatch, linkText, traversalPart, traversalDir, targetPath] = match; | ||||||
|  | 
 | ||||||
|  |         // Check if traversal directory matches current directory
 | ||||||
|  |         if (traversalDir === dirName) { | ||||||
|  |           // Calculate positions for precise highlighting
 | ||||||
|  |           const linkStart = match.index; | ||||||
|  |           const traversalStart = fullMatch.indexOf(traversalPart); | ||||||
|  | 
 | ||||||
|  |           onError({ | ||||||
|  |             lineNumber: i + 1, | ||||||
|  |             range: [linkStart + traversalStart, traversalPart.length], | ||||||
|  |             detail: `Link path does not need: '../${traversalDir}/'. Shorten link path to '[${linkText}](${targetPath})'`, | ||||||
|  |             fixInfo: { | ||||||
|  |               editColumn: linkStart + 1, | ||||||
|  |               deleteCount: fullMatch.length, | ||||||
|  |               insertText: `[${linkText}](${targetPath})`, | ||||||
|  |             }, | ||||||
|  |           }); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | @ -1,14 +1,14 @@ | ||||||
| --- | --- | ||||||
| # Error: gitlab_docs.InternalLinkFormat | # Error: gitlab_docs.InternalLinkFormat | ||||||
| # | # | ||||||
| # Checks that internal link paths don't start with '/' or './'. | # Checks that internal link paths don't use `//`, or start with '/' or './'. | ||||||
| # | # | ||||||
| # For a list of all options, see https://vale.sh/docs/topics/styles/ | # For a list of all options, see https://vale.sh/docs/topics/styles/ | ||||||
| extends: existence | extends: existence | ||||||
| message: "Edit the link so it does not start with '/' or './'." | message: "Edit the link so it does not use `//`, or start with '/' or './'." | ||||||
| link: https://docs.gitlab.com/development/documentation/styleguide/#links | link: https://docs.gitlab.com/development/documentation/styleguide/#links | ||||||
| vocab: false | vocab: false | ||||||
| level: error | level: error | ||||||
| scope: raw | scope: raw | ||||||
| raw: | raw: | ||||||
|   - '\[[^\]]+\]\(\.?\/(?!uploads|documentation).*?\)' |   - '\[[^\]]+\]\((\.?\/(?!uploads|documentation)|[^:)]*\/\/)[^)]*\)' | ||||||
|  |  | ||||||
|  | @ -1,14 +0,0 @@ | ||||||
| --- |  | ||||||
| # Error: gitlab_docs.RelativeLinksDoubleSlashes |  | ||||||
| # |  | ||||||
| # Checks for the presence of double slashes in relative URLs. |  | ||||||
| # |  | ||||||
| # For a list of all options, see https://vale.sh/docs/topics/styles/ |  | ||||||
| extends: existence |  | ||||||
| message: "Do not use double slashes '//' or '../doc' in the link path" |  | ||||||
| link: https://docs.gitlab.com/development/documentation/styleguide/#links |  | ||||||
| vocab: false |  | ||||||
| level: error |  | ||||||
| scope: raw |  | ||||||
| raw: |  | ||||||
|   - '(\.//)|(\.\.\/doc\/)' |  | ||||||
|  | @ -531,7 +531,7 @@ To set this limit to `2000` on your instance, run the following command in the G | ||||||
| Plan.default.actual_limits.update!(pipeline_hierarchy_size: 2000) | Plan.default.actual_limits.update!(pipeline_hierarchy_size: 2000) | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| You can also set this limit by using the GitLab UI in the [Admin area](../administration/settings/continuous_integration.md#set-cicd-limits). | You can also set this limit by using the GitLab UI in the [Admin area](settings/continuous_integration.md#set-cicd-limits). | ||||||
| 
 | 
 | ||||||
| This limit is enabled on GitLab.com and cannot be changed. | This limit is enabled on GitLab.com and cannot be changed. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -47,7 +47,7 @@ Verify the presence of report on the base commit by obtaining the `base_sha` usi | ||||||
| 
 | 
 | ||||||
| ## No Code Quality symbol in the changes view | ## No Code Quality symbol in the changes view | ||||||
| 
 | 
 | ||||||
| If no symbol is displayed in the [changes view](../testing/code_quality.md#merge-request-changes-view), ensure that the `location.path` in the code quality report: | If no symbol is displayed in the [changes view](code_quality.md#merge-request-changes-view), ensure that the `location.path` in the code quality report: | ||||||
| 
 | 
 | ||||||
| - Is using a relative path to the file containing the code quality violation. | - Is using a relative path to the file containing the code quality violation. | ||||||
| - Is not prefixed with `./`. For example, the `path` should be `somedir/file1.rb` instead of `./somedir/file1.rb`. | - Is not prefixed with `./`. For example, the `path` should be `somedir/file1.rb` instead of `./somedir/file1.rb`. | ||||||
|  |  | ||||||
|  | @ -213,7 +213,7 @@ writes, but should be set to a low value, such as 10 seconds. | ||||||
| 
 | 
 | ||||||
| `Rails.cache` uses Redis as the store. | `Rails.cache` uses Redis as the store. | ||||||
| GitLab instances, like GitLab.com, can configure Redis for [key eviction](https://redis.io/docs/latest/develop/reference/eviction/). | GitLab instances, like GitLab.com, can configure Redis for [key eviction](https://redis.io/docs/latest/develop/reference/eviction/). | ||||||
| See the [Redis development guide](../development/redis.md#caching). | See the [Redis development guide](redis.md#caching). | ||||||
| 
 | 
 | ||||||
| ### When to use HTTP caching | ### When to use HTTP caching | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ enhancement. They are responsible for: | ||||||
| 
 | 
 | ||||||
| The first merge request where a feature can be tested should include the | The first merge request where a feature can be tested should include the | ||||||
| documentation, even if the feature is behind a feature flag. | documentation, even if the feature is behind a feature flag. | ||||||
| For more information, see the [guidelines](../documentation/feature_flags.md). | For more information, see the [guidelines](feature_flags.md). | ||||||
| 
 | 
 | ||||||
| The author of this MR, either a frontend or backend developer, should write the documentation. | The author of this MR, either a frontend or backend developer, should write the documentation. | ||||||
| 
 | 
 | ||||||
|  | @ -101,7 +101,7 @@ otherwise agreed with the product manager and technical writer: | ||||||
|   If the new or changed documentation requires extensive collaboration or |   If the new or changed documentation requires extensive collaboration or | ||||||
|   conversation, a separate, linked issue can be used for the planning process. |   conversation, a separate, linked issue can be used for the planning process. | ||||||
| 
 | 
 | ||||||
| - Use the [Documentation guidelines](../documentation/_index.md), | - Use the [Documentation guidelines](_index.md), | ||||||
|   and other resources linked from there, including: |   and other resources linked from there, including: | ||||||
|   - [Documentation folder structure](site_architecture/folder_structure.md). |   - [Documentation folder structure](site_architecture/folder_structure.md). | ||||||
|   - [Documentation Style Guide](styleguide/_index.md). |   - [Documentation Style Guide](styleguide/_index.md). | ||||||
|  | @ -114,7 +114,7 @@ otherwise agreed with the product manager and technical writer: | ||||||
|   - Want to request any other help. |   - Want to request any other help. | ||||||
| - If you are working on documentation in a separate merge request, ensure the | - If you are working on documentation in a separate merge request, ensure the | ||||||
|   documentation is merged as close as possible to the code merge. |   documentation is merged as close as possible to the code merge. | ||||||
| - If the feature has a feature flag, [follow the policy for documenting feature-flagged issues](../documentation/feature_flags.md). | - If the feature has a feature flag, [follow the policy for documenting feature-flagged issues](feature_flags.md). | ||||||
| 
 | 
 | ||||||
| #### Review | #### Review | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -118,7 +118,7 @@ Refer to [Track and Propose Sessions for Python Learning Group](https://gitlab.c | ||||||
| 
 | 
 | ||||||
| ### Communication | ### Communication | ||||||
| 
 | 
 | ||||||
| - Stay updated by following the [learning group issue](<https://gitlab.com/gitlab-org/gitlab/-/issues/517449>) | - Stay updated by following the [learning group issue](https://gitlab.com/gitlab-org/gitlab/-/issues/517449) | ||||||
| - Join the discussion on Slack: **#python_getting_started** | - Join the discussion on Slack: **#python_getting_started** | ||||||
| 
 | 
 | ||||||
| --- | --- | ||||||
|  |  | ||||||
|  | @ -72,6 +72,7 @@ This window takes place on May 5 - 7, 2025 from 09:00 UTC to 22:00 UTC. | ||||||
| 
 | 
 | ||||||
| | Deprecation | Impact | Stage | Scope | | | Deprecation | Impact | Stage | Scope | | ||||||
| |-------------|--------|-------|-------| | |-------------|--------|-------|-------| | ||||||
|  | | [GitLab.com certificate-based integration with Kubernetes](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) |  | Configure |  | | ||||||
| | [Runner `active` GraphQL fields replaced by `paused`](https://gitlab.com/gitlab-org/gitlab/-/issues/351109) | Low | Verify | Instance, group, project | | | [Runner `active` GraphQL fields replaced by `paused`](https://gitlab.com/gitlab-org/gitlab/-/issues/351109) | Low | Verify | Instance, group, project | | ||||||
| | [ZenTao integration](https://gitlab.com/gitlab-org/gitlab/-/issues/377825) | Low | Foundations | Instance | | | [ZenTao integration](https://gitlab.com/gitlab-org/gitlab/-/issues/377825) | Low | Foundations | Instance | | ||||||
| | [GraphQL deprecation of `dependencyProxyTotalSizeInBytes` field](https://gitlab.com/gitlab-org/gitlab/-/issues/414236) | Low | Package | Group | | | [GraphQL deprecation of `dependencyProxyTotalSizeInBytes` field](https://gitlab.com/gitlab-org/gitlab/-/issues/414236) | Low | Package | Group | | ||||||
|  |  | ||||||
|  | @ -1329,6 +1329,29 @@ You can read more about it in the [charts release page](https://docs.gitlab.com/ | ||||||
| 
 | 
 | ||||||
| </div> | </div> | ||||||
| 
 | 
 | ||||||
|  | <div class="deprecation breaking-change" data-milestone="18.0"> | ||||||
|  | 
 | ||||||
|  | ### GitLab.com certificate-based integration with Kubernetes | ||||||
|  | 
 | ||||||
|  | <div class="deprecation-notes"> | ||||||
|  | 
 | ||||||
|  | - Announced in GitLab <span class="milestone">14.5</span> | ||||||
|  | - Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/update/terminology/#breaking-change)) | ||||||
|  | - To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/groups/gitlab-org/configure/-/epics/8). | ||||||
|  | 
 | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
|  | The certificate-based integration with Kubernetes will be [deprecated and removed](https://about.gitlab.com/blog/2021/11/15/deprecating-the-cert-based-kubernetes-integration/). As a GitLab.com user, on new namespaces, you will no longer be able to integrate GitLab and your cluster using the certificate-based approach as of GitLab 15.0. The integration for current users will be enabled per namespace. | ||||||
|  | 
 | ||||||
|  | For a more robust, secure, forthcoming, and reliable integration with Kubernetes, we recommend you use the | ||||||
|  | [agent for Kubernetes](https://docs.gitlab.com/user/clusters/agent/) to connect Kubernetes clusters with GitLab. [How do I migrate?](https://docs.gitlab.com/user/infrastructure/clusters/migrate_to_gitlab_agent/) | ||||||
|  | 
 | ||||||
|  | For updates and details about this deprecation, follow [this epic](https://gitlab.com/groups/gitlab-org/configure/-/epics/8). | ||||||
|  | 
 | ||||||
|  | GitLab Self-Managed customers can still use the feature [with a feature flag](https://docs.gitlab.com/update/deprecations/#self-managed-certificate-based-integration-with-kubernetes). | ||||||
|  | 
 | ||||||
|  | </div> | ||||||
|  | 
 | ||||||
| <div class="deprecation " data-milestone="18.0"> | <div class="deprecation " data-milestone="18.0"> | ||||||
| 
 | 
 | ||||||
| ### Gitaly rate limiting | ### Gitaly rate limiting | ||||||
|  | @ -6331,32 +6354,6 @@ We are deprecating support for **uploading backups to remote storage** using Ope | ||||||
| 
 | 
 | ||||||
| <div class="deprecation breaking-change" data-milestone="15.9"> | <div class="deprecation breaking-change" data-milestone="15.9"> | ||||||
| 
 | 
 | ||||||
| ### GitLab.com certificate-based integration with Kubernetes |  | ||||||
| 
 |  | ||||||
| <div class="deprecation-notes"> |  | ||||||
| 
 |  | ||||||
| - Announced in GitLab <span class="milestone">14.5</span> |  | ||||||
| - Removal in GitLab <span class="milestone">15.9</span> ([breaking change](https://docs.gitlab.com/update/terminology/#breaking-change)) |  | ||||||
| - To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/groups/gitlab-org/configure/-/epics/8). |  | ||||||
| 
 |  | ||||||
| </div> |  | ||||||
| 
 |  | ||||||
| The certificate-based integration with Kubernetes will be [deprecated and removed](https://about.gitlab.com/blog/2021/11/15/deprecating-the-cert-based-kubernetes-integration/). As a GitLab.com user, on new namespaces, you will no longer be able to integrate GitLab and your cluster using the certificate-based approach as of GitLab 15.0. The integration for current users will be enabled per namespace. |  | ||||||
| 
 |  | ||||||
| For a more robust, secure, forthcoming, and reliable integration with Kubernetes, we recommend you use the |  | ||||||
| [agent for Kubernetes](https://docs.gitlab.com/user/clusters/agent/) to connect Kubernetes clusters with GitLab. [How do I migrate?](https://docs.gitlab.com/user/infrastructure/clusters/migrate_to_gitlab_agent/) |  | ||||||
| 
 |  | ||||||
| Although an explicit removal date is set, we don't plan to remove this feature until the new solution has feature parity. |  | ||||||
| For more information about the blockers to removal, see [this issue](https://gitlab.com/gitlab-org/configure/general/-/issues/199). |  | ||||||
| 
 |  | ||||||
| For updates and details about this deprecation, follow [this epic](https://gitlab.com/groups/gitlab-org/configure/-/epics/8). |  | ||||||
| 
 |  | ||||||
| GitLab Self-Managed customers can still use the feature [with a feature flag](https://docs.gitlab.com/update/deprecations/#self-managed-certificate-based-integration-with-kubernetes). |  | ||||||
| 
 |  | ||||||
| </div> |  | ||||||
| 
 |  | ||||||
| <div class="deprecation breaking-change" data-milestone="15.9"> |  | ||||||
| 
 |  | ||||||
| ### Live Preview no longer available in the Web IDE | ### Live Preview no longer available in the Web IDE | ||||||
| 
 | 
 | ||||||
| <div class="deprecation-notes"> | <div class="deprecation-notes"> | ||||||
|  |  | ||||||
|  | @ -266,7 +266,7 @@ Prerequisites: | ||||||
|   [enable the extension marketplace](../../administration/settings/vscode_extension_marketplace.md). |   [enable the extension marketplace](../../administration/settings/vscode_extension_marketplace.md). | ||||||
| 
 | 
 | ||||||
| If you have the Owner role for a top-level group, you can enable the | If you have the Owner role for a top-level group, you can enable the | ||||||
| [extension marketplace](../enterprise_user/_index.md#enable-the-extension-marketplace-for-the-web-ide-and-workspaces) for enterprise users. | [extension marketplace](_index.md#enable-the-extension-marketplace-for-the-web-ide-and-workspaces) for enterprise users. | ||||||
| 
 | 
 | ||||||
| To enable the extension marketplace for the | To enable the extension marketplace for the | ||||||
| [Web IDE](../project/web_ide/_index.md) and [workspaces](../workspace/_index.md): | [Web IDE](../project/web_ide/_index.md) and [workspaces](../workspace/_index.md): | ||||||
|  |  | ||||||
|  | @ -152,7 +152,7 @@ Alternatively, you can remove a linked item using the `/unlink` [quick action](. | ||||||
| 
 | 
 | ||||||
| ## Related topics | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [Work items](../work_items/_index.md) | - [Work items](_index.md) | ||||||
| - [Issues](../project/issues/_index.md) | - [Issues](../project/issues/_index.md) | ||||||
| - [Epics](../group/epics/_index.md) | - [Epics](../group/epics/_index.md) | ||||||
| - [Tasks](../tasks.md) | - [Tasks](../tasks.md) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,15 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | module Gitlab | ||||||
|  |   module BackgroundMigration | ||||||
|  |     class BackfillAmazonInstanceAuditEventDestinations < BatchedMigrationJob | ||||||
|  |       feature_category :audit_events | ||||||
|  | 
 | ||||||
|  |       def perform | ||||||
|  |         # CE implementation is a no-op | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | 
 | ||||||
|  | Gitlab::BackgroundMigration::BackfillAmazonInstanceAuditEventDestinations.prepend_mod | ||||||
|  | @ -60792,9 +60792,6 @@ msgstr "" | ||||||
| msgid "This project is archived and read-only. To resume pull mirroring, unarchive the project." | msgid "This project is archived and read-only. To resume pull mirroring, unarchive the project." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "This project is hidden because its creator has been banned" |  | ||||||
| msgstr "" |  | ||||||
| 
 |  | ||||||
| msgid "This project is licensed under the %{strong_start}%{license_name}%{strong_end}." | msgid "This project is licensed under the %{strong_start}%{license_name}%{strong_end}." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -61643,9 +61640,6 @@ msgstr "" | ||||||
| msgid "Todos|Due today" | msgid "Todos|Due today" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Todos|Epic" |  | ||||||
| msgstr "" |  | ||||||
| 
 |  | ||||||
| msgid "Todos|Failed adding todo. Try again later." | msgid "Todos|Failed adding todo. Try again later." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -61679,7 +61673,7 @@ msgstr "" | ||||||
| msgid "Todos|Isn't an empty To-Do List beautiful?" | msgid "Todos|Isn't an empty To-Do List beautiful?" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Todos|Issue" | msgid "Todos|Issue or Epic" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Todos|It's how you always know what to work on next." | msgid "Todos|It's how you always know what to work on next." | ||||||
|  | @ -61885,9 +61879,6 @@ msgstr "" | ||||||
| msgid "Todos|Wiki page" | msgid "Todos|Wiki page" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
| msgid "Todos|Work item" |  | ||||||
| msgstr "" |  | ||||||
| 
 |  | ||||||
| msgid "Todos|You" | msgid "Todos|You" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -64,7 +64,7 @@ | ||||||
|     "@gitlab/favicon-overlay": "2.0.0", |     "@gitlab/favicon-overlay": "2.0.0", | ||||||
|     "@gitlab/fonts": "^1.3.0", |     "@gitlab/fonts": "^1.3.0", | ||||||
|     "@gitlab/query-language-rust": "0.5.2", |     "@gitlab/query-language-rust": "0.5.2", | ||||||
|     "@gitlab/svgs": "3.123.0", |     "@gitlab/svgs": "3.126.0", | ||||||
|     "@gitlab/ui": "111.9.1", |     "@gitlab/ui": "111.9.1", | ||||||
|     "@gitlab/vue-router-vue3": "npm:vue-router@4.5.0", |     "@gitlab/vue-router-vue3": "npm:vue-router@4.5.0", | ||||||
|     "@gitlab/vuex-vue3": "npm:vuex@4.1.0", |     "@gitlab/vuex-vue3": "npm:vuex@4.1.0", | ||||||
|  |  | ||||||
|  | @ -25,12 +25,6 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do | ||||||
|       create(:project, :private, name: 'D', path: 'D') |       create(:project, :private, name: 'D', path: 'D') | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     let_it_be(:banned_user_project) do |  | ||||||
|       create(:project, :public, name: 'Project created by a banned user', creator: create(:user, :banned)).tap do |p| |  | ||||||
|         create(:project_authorization, :owner, user: p.creator, project: p) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     let(:params) { {} } |     let(:params) { {} } | ||||||
|     let(:current_user) { user } |     let(:current_user) { user } | ||||||
|     let(:project_ids_relation) { nil } |     let(:project_ids_relation) { nil } | ||||||
|  | @ -554,22 +548,13 @@ RSpec.describe ProjectsFinder, feature_category: :groups_and_projects do | ||||||
|               public_project, |               public_project, | ||||||
|               internal_project, |               internal_project, | ||||||
|               private_project, |               private_project, | ||||||
|               shared_project, |               shared_project | ||||||
|               banned_user_project |  | ||||||
|             ]) |             ]) | ||||||
|           end |           end | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         context 'with admin mode disabled' do |         context 'with admin mode disabled' do | ||||||
|           it { is_expected.to match_array([public_project, internal_project]) } |           it { is_expected.to match_array([public_project, internal_project]) } | ||||||
| 
 |  | ||||||
|           context 'when hide_projects_of_banned_users FF is disabled' do |  | ||||||
|             before do |  | ||||||
|               stub_feature_flags(hide_projects_of_banned_users: false) |  | ||||||
|             end |  | ||||||
| 
 |  | ||||||
|             it { is_expected.to match_array([public_project, internal_project, banned_user_project]) } |  | ||||||
|           end |  | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -29,9 +29,6 @@ Vue.use(VueApollo); | ||||||
| describe('Agents', () => { | describe('Agents', () => { | ||||||
|   let wrapper; |   let wrapper; | ||||||
| 
 | 
 | ||||||
|   const defaultProps = { |  | ||||||
|     defaultBranchName: 'default', |  | ||||||
|   }; |  | ||||||
|   const provideData = { |   const provideData = { | ||||||
|     fullPath: 'path/to/project/group', |     fullPath: 'path/to/project/group', | ||||||
|   }; |   }; | ||||||
|  | @ -94,7 +91,6 @@ describe('Agents', () => { | ||||||
|     wrapper = shallowMount(Agents, { |     wrapper = shallowMount(Agents, { | ||||||
|       apolloProvider, |       apolloProvider, | ||||||
|       propsData: { |       propsData: { | ||||||
|         ...defaultProps, |  | ||||||
|         ...props, |         ...props, | ||||||
|       }, |       }, | ||||||
|       provide: { |       provide: { | ||||||
|  |  | ||||||
|  | @ -17,7 +17,6 @@ import { clusterAgentsResponse } from './mock_data'; | ||||||
| Vue.use(VueApollo); | Vue.use(VueApollo); | ||||||
| 
 | 
 | ||||||
| const fullPath = 'path/to/project'; | const fullPath = 'path/to/project'; | ||||||
| const defaultBranchName = 'default'; |  | ||||||
| const agent = { | const agent = { | ||||||
|   id: 'agent-id', |   id: 'agent-id', | ||||||
|   name: 'agent-name', |   name: 'agent-name', | ||||||
|  | @ -53,7 +52,6 @@ describe('DeleteAgentButton', () => { | ||||||
|       query: getAgentsQuery, |       query: getAgentsQuery, | ||||||
|       variables: { |       variables: { | ||||||
|         fullPath, |         fullPath, | ||||||
|         defaultBranchName, |  | ||||||
|         isGroup: false, |         isGroup: false, | ||||||
|       }, |       }, | ||||||
|       data: clusterAgentsResponse.data, |       data: clusterAgentsResponse.data, | ||||||
|  | @ -71,7 +69,6 @@ describe('DeleteAgentButton', () => { | ||||||
|       isGroup: false, |       isGroup: false, | ||||||
|     }; |     }; | ||||||
|     const propsData = { |     const propsData = { | ||||||
|       defaultBranchName, |  | ||||||
|       agent, |       agent, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ exports[`Design note pin component should match the snapshot of note without ind | ||||||
|   type="button" |   type="button" | ||||||
| > | > | ||||||
|   <gl-icon-stub |   <gl-icon-stub | ||||||
|     class="gl-bg-white gl-border-2 gl-border-solid gl-border-white gl-rounded-full" |     class="gl-bg-neutral-0 gl-border-2 gl-border-neutral-0 gl-border-solid gl-rounded-full gl-text-neutral-950" | ||||||
|     name="image-comment-dark" |     name="image-comment-dark" | ||||||
|     size="24" |     size="24" | ||||||
|     variant="current" |     variant="current" | ||||||
|  | @ -35,7 +35,7 @@ exports[`Design note pin component should match the snapshot when pin is resolve | ||||||
|   type="button" |   type="button" | ||||||
| > | > | ||||||
|   <gl-icon-stub |   <gl-icon-stub | ||||||
|     class="gl-bg-white gl-border-2 gl-border-solid gl-border-white gl-rounded-full" |     class="gl-bg-neutral-0 gl-border-2 gl-border-neutral-0 gl-border-solid gl-rounded-full gl-text-neutral-950" | ||||||
|     name="image-comment-dark" |     name="image-comment-dark" | ||||||
|     size="24" |     size="24" | ||||||
|     variant="current" |     variant="current" | ||||||
|  | @ -50,7 +50,7 @@ exports[`Design note pin component should match the snapshot when position is ab | ||||||
|   type="button" |   type="button" | ||||||
| > | > | ||||||
|   <gl-icon-stub |   <gl-icon-stub | ||||||
|     class="gl-bg-white gl-border-2 gl-border-solid gl-border-white gl-rounded-full" |     class="gl-bg-neutral-0 gl-border-2 gl-border-neutral-0 gl-border-solid gl-rounded-full gl-text-neutral-950" | ||||||
|     name="image-comment-dark" |     name="image-comment-dark" | ||||||
|     size="24" |     size="24" | ||||||
|     variant="current" |     variant="current" | ||||||
|  |  | ||||||
|  | @ -68,11 +68,10 @@ describe('Visibility icon button', () => { | ||||||
| 
 | 
 | ||||||
|     describe('if item represents project', () => { |     describe('if item represents project', () => { | ||||||
|       it.each` |       it.each` | ||||||
|         visibilityLevel                     | visibilityTooltip                                               | visibilityIcon                                            | tooltipPlacement |         visibilityLevel                     | visibilityTooltip                                            | visibilityIcon                                            | tooltipPlacement | ||||||
|         ${VISIBILITY_LEVEL_PUBLIC_STRING}   | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_PUBLIC_STRING]}      | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_PUBLIC_STRING]}   | ${'top'} |         ${VISIBILITY_LEVEL_PUBLIC_STRING}   | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_PUBLIC_STRING]}   | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_PUBLIC_STRING]}   | ${'top'} | ||||||
|         ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_INTERNAL_STRING]}    | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_INTERNAL_STRING]} | ${'bottom'} |         ${VISIBILITY_LEVEL_INTERNAL_STRING} | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_INTERNAL_STRING]} | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_INTERNAL_STRING]} | ${'bottom'} | ||||||
|         ${VISIBILITY_LEVEL_PRIVATE_STRING}  | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_PRIVATE_STRING]}     | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_PRIVATE_STRING]}  | ${'left'} |         ${VISIBILITY_LEVEL_PRIVATE_STRING}  | ${PROJECT_VISIBILITY_TYPE[VISIBILITY_LEVEL_PRIVATE_STRING]}  | ${VISIBILITY_TYPE_ICON[VISIBILITY_LEVEL_PRIVATE_STRING]}  | ${'left'} | ||||||
|         ${'banned'}                         | ${'This project is hidden because its creator has been banned'} | ${'spam'}                                                 | ${'right'} |  | ||||||
|       `(
 |       `(
 | ||||||
|         'should return corresponding text when visibility level is $visibilityLevel', |         'should return corresponding text when visibility level is $visibilityLevel', | ||||||
|         ({ visibilityLevel, visibilityTooltip, visibilityIcon, tooltipPlacement }) => { |         ({ visibilityLevel, visibilityTooltip, visibilityIcon, tooltipPlacement }) => { | ||||||
|  |  | ||||||
|  | @ -938,13 +938,6 @@ RSpec.describe ApplicationHelper do | ||||||
|       it_behaves_like 'returns icon with tooltip' |       it_behaves_like 'returns icon with tooltip' | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     context 'when resource is a project' do |  | ||||||
|       let_it_be(:resource) { build(:project) } |  | ||||||
|       let(:expected_title) { 'This project is hidden because its creator has been banned' } |  | ||||||
| 
 |  | ||||||
|       it_behaves_like 'returns icon with tooltip' |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when css_class is provided' do |     context 'when css_class is provided' do | ||||||
|       let_it_be(:resource) { build(:issue) } |       let_it_be(:resource) { build(:issue) } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1294,28 +1294,6 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#visibility_level_name' do |  | ||||||
|     using RSpec::Parameterized::TableSyntax |  | ||||||
| 
 |  | ||||||
|     where(:banned_user, :feature_flag_enabled, :expected) do |  | ||||||
|       true  | true  | 'banned' |  | ||||||
|       false | false | 'private' |  | ||||||
|       true  | false | 'private' |  | ||||||
|       false | true  | 'private' |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     with_them do |  | ||||||
|       before do |  | ||||||
|         stub_feature_flags(hide_projects_of_banned_users: feature_flag_enabled) |  | ||||||
|         allow(project).to receive(:created_and_owned_by_banned_user?).and_return(banned_user) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       subject { visibility_level_name(project) } |  | ||||||
| 
 |  | ||||||
|       it { is_expected.to eq(expected) } |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   shared_examples 'configure import method modal' do |   shared_examples 'configure import method modal' do | ||||||
|     context 'as a user' do |     context 'as a user' do | ||||||
|       it 'returns a link to contact an administrator' do |       it 'returns a link to contact an administrator' do | ||||||
|  | @ -1835,35 +1813,6 @@ RSpec.describe ProjectsHelper, feature_category: :source_code_management do | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     it_behaves_like 'returns visibility level content_tag' |     it_behaves_like 'returns visibility level content_tag' | ||||||
| 
 |  | ||||||
|     context 'when project creator is banned' do |  | ||||||
|       let(:hidden_resource_icon) { '<svg>fake hidden resource icon</svg>' } |  | ||||||
| 
 |  | ||||||
|       before do |  | ||||||
|         allow(project).to receive(:created_and_owned_by_banned_user?).and_return(true) |  | ||||||
|         allow(helper).to receive(:hidden_resource_icon).and_return(hidden_resource_icon) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it 'returns hidden resource icon' do |  | ||||||
|         expect(helper.visibility_level_content(project)).to eq hidden_resource_icon |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'with hide_projects_of_banned_users feature flag disabled' do |  | ||||||
|       before do |  | ||||||
|         stub_feature_flags(hide_projects_of_banned_users: false) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it_behaves_like 'returns visibility level content_tag' |  | ||||||
| 
 |  | ||||||
|       context 'when project creator is banned' do |  | ||||||
|         before do |  | ||||||
|           allow(project).to receive(:created_and_owned_by_banned_user?).and_return(true) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         it_behaves_like 'returns visibility level content_tag' |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '#hidden_issue_icon' do |   describe '#hidden_issue_icon' do | ||||||
|  |  | ||||||
|  | @ -0,0 +1,38 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | require_migration! | ||||||
|  | 
 | ||||||
|  | RSpec.describe QueueBackfillAmazonInstanceAuditEventDestinations, | ||||||
|  |   migration: :gitlab_main, | ||||||
|  |   feature_category: :audit_events do | ||||||
|  |   let(:batched_migration) { described_class::MIGRATION } | ||||||
|  | 
 | ||||||
|  |   it 'schedules a new batched migration' do | ||||||
|  |     reversible_migration do |migration| | ||||||
|  |       migration.before -> { | ||||||
|  |         expect(batched_migration).not_to have_scheduled_batched_migration | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       migration.after -> { | ||||||
|  |         expect(batched_migration).to have_scheduled_batched_migration( | ||||||
|  |           table_name: :audit_events_instance_amazon_s3_configurations, | ||||||
|  |           column_name: :id, | ||||||
|  |           interval: described_class::DELAY_INTERVAL, | ||||||
|  |           batch_size: described_class::BATCH_SIZE, | ||||||
|  |           sub_batch_size: described_class::SUB_BATCH_SIZE, | ||||||
|  |           gitlab_schema: :gitlab_main | ||||||
|  |         ) | ||||||
|  |       } | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   it 'removes scheduled migration when rolling back' do | ||||||
|  |     disable_migrations_output do | ||||||
|  |       migrate! | ||||||
|  |       schema_migrate_down! | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     expect(batched_migration).not_to have_scheduled_batched_migration | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -9529,109 +9529,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '.without_created_and_owned_by_banned_user' do |  | ||||||
|     let_it_be(:other_project) { create(:project) } |  | ||||||
| 
 |  | ||||||
|     subject(:results) { described_class.without_created_and_owned_by_banned_user } |  | ||||||
| 
 |  | ||||||
|     context 'when project creator is not banned' do |  | ||||||
|       let_it_be(:project_of_active_user) { create(:project, creator: create(:user)) } |  | ||||||
| 
 |  | ||||||
|       it 'includes the project' do |  | ||||||
|         expect(results).to match_array([other_project, project_of_active_user]) |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when project creator is banned' do |  | ||||||
|       let_it_be(:banned_user) { create(:user, :banned) } |  | ||||||
|       let_it_be(:project_of_banned_user) { create(:project, creator: banned_user) } |  | ||||||
| 
 |  | ||||||
|       context 'when project creator is also an owner' do |  | ||||||
|         let_it_be(:project_auth) do |  | ||||||
|           project = project_of_banned_user |  | ||||||
|           create(:project_authorization, :owner, user: project.creator, project: project) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         it 'excludes the project' do |  | ||||||
|           expect(results).to match_array([other_project]) |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       context 'when project creator is not an owner' do |  | ||||||
|         it 'includes the project' do |  | ||||||
|           expect(results).to match_array([other_project, project_of_banned_user]) |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   describe '.with_created_and_owned_by_banned_user' do |  | ||||||
|     let_it_be(:other_project) { create(:project) } |  | ||||||
| 
 |  | ||||||
|     subject(:results) { described_class.with_created_and_owned_by_banned_user } |  | ||||||
| 
 |  | ||||||
|     context 'when project creator is not banned' do |  | ||||||
|       let_it_be(:project_of_active_user) { create(:project, creator: create(:user)) } |  | ||||||
| 
 |  | ||||||
|       it 'does not include the project' do |  | ||||||
|         expect(results).to be_empty |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when project creator is banned' do |  | ||||||
|       let_it_be(:banned_user) { create(:user, :banned) } |  | ||||||
|       let_it_be(:project_of_banned_user) { create(:project, creator: banned_user) } |  | ||||||
| 
 |  | ||||||
|       context 'when project creator is also an owner' do |  | ||||||
|         let_it_be(:project_auth) do |  | ||||||
|           project = project_of_banned_user |  | ||||||
|           create(:project_authorization, :owner, user: project.creator, project: project) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         it 'includes the banned user project' do |  | ||||||
|           expect(results).to match_array([project_of_banned_user]) |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       context 'when project creator is not an owner' do |  | ||||||
|         it 'does not include the project' do |  | ||||||
|           expect(results).not_to be_present |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   describe '#created_and_owned_by_banned_user?' do |  | ||||||
|     subject { project.created_and_owned_by_banned_user? } |  | ||||||
| 
 |  | ||||||
|     context 'when creator is banned' do |  | ||||||
|       let_it_be(:creator) { create(:user, :banned) } |  | ||||||
|       let_it_be(:project) { create(:project, creator: creator) } |  | ||||||
| 
 |  | ||||||
|       it { is_expected.to eq false } |  | ||||||
| 
 |  | ||||||
|       context 'when creator is an owner' do |  | ||||||
|         let_it_be(:project_auth) do |  | ||||||
|           create(:project_authorization, :owner, user: project.creator, project: project) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         it { is_expected.to eq true } |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when creator is not banned' do |  | ||||||
|       let_it_be(:project) { create(:project) } |  | ||||||
| 
 |  | ||||||
|       it { is_expected.to eq false } |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when there is no creator' do |  | ||||||
|       let_it_be(:project) { build_stubbed(:project, creator: nil) } |  | ||||||
| 
 |  | ||||||
|       it { is_expected.to eq false } |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   it_behaves_like 'something that has web-hooks' do |   it_behaves_like 'something that has web-hooks' do | ||||||
|     let_it_be_with_reload(:object) { create(:project) } |     let_it_be_with_reload(:object) { create(:project) } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -124,31 +124,5 @@ RSpec.describe Namespaces::ProjectNamespacePolicy, feature_category: :groups_and | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 |  | ||||||
|     describe 'when project is created and owned by a banned user' do |  | ||||||
|       let_it_be(:project) { create(:project, :public) } |  | ||||||
| 
 |  | ||||||
|       let(:current_user) { developer } |  | ||||||
| 
 |  | ||||||
|       before do |  | ||||||
|         allow(project).to receive(:created_and_owned_by_banned_user?).and_return(true) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it { expect_disallowed(:read_project, :read_namespace) } |  | ||||||
| 
 |  | ||||||
|       context 'when current user is an admin', :enable_admin_mode do |  | ||||||
|         let(:current_user) { admin } |  | ||||||
| 
 |  | ||||||
|         it { expect_allowed(:read_project, :read_namespace) } |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       context 'when hide_projects_of_banned_users FF is disabled' do |  | ||||||
|         before do |  | ||||||
|           stub_feature_flags(hide_projects_of_banned_users: false) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         it { expect_allowed(:read_project, :read_namespace) } |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -3978,32 +3978,6 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe 'when project is created and owned by a banned user' do |  | ||||||
|     let_it_be(:project) { create(:project, :public) } |  | ||||||
| 
 |  | ||||||
|     let(:current_user) { guest } |  | ||||||
| 
 |  | ||||||
|     before do |  | ||||||
|       allow(project).to receive(:created_and_owned_by_banned_user?).and_return(true) |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     it { expect_disallowed(:read_project) } |  | ||||||
| 
 |  | ||||||
|     context 'when current user is an admin', :enable_admin_mode do |  | ||||||
|       let(:current_user) { admin } |  | ||||||
| 
 |  | ||||||
|       it { expect_allowed(:read_project) } |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     context 'when hide_projects_of_banned_users FF is disabled' do |  | ||||||
|       before do |  | ||||||
|         stub_feature_flags(hide_projects_of_banned_users: false) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it { expect_allowed(:read_project) } |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   describe 'webhooks' do |   describe 'webhooks' do | ||||||
|     context 'when the current_user is a maintainer' do |     context 'when the current_user is a maintainer' do | ||||||
|       let(:current_user) { maintainer } |       let(:current_user) { maintainer } | ||||||
|  |  | ||||||
|  | @ -218,11 +218,10 @@ RSpec.describe 'getting project information', feature_category: :groups_and_proj | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     context 'for N+1 queries with isCatalogResource' do |     context 'for N+1 queries with isCatalogResource' do | ||||||
|       let_it_be(:project1) { create(:project, group: group) } |       let_it_be(:project1) { create(:project, group: group, owners: current_user) } | ||||||
|       let_it_be(:project2) { create(:project, group: group) } |       let_it_be(:project2) { create(:project, group: group, owners: current_user) } | ||||||
| 
 | 
 | ||||||
|       it 'avoids N+1 database queries' do |       it 'avoids N+1 database queries', :request_store do | ||||||
|         pending('See: https://gitlab.com/gitlab-org/gitlab/-/issues/403634') |  | ||||||
|         ctx = { current_user: current_user } |         ctx = { current_user: current_user } | ||||||
| 
 | 
 | ||||||
|         baseline_query = graphql_query_for(:project, { full_path: project1.full_path }, 'isCatalogResource') |         baseline_query = graphql_query_for(:project, { full_path: project1.full_path }, 'isCatalogResource') | ||||||
|  |  | ||||||
|  | @ -0,0 +1,94 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe Groups::RestoreService, feature_category: :groups_and_projects do | ||||||
|  |   let(:user) { create(:user) } | ||||||
|  |   let(:group) do | ||||||
|  |     create(:group_with_deletion_schedule, | ||||||
|  |       marked_for_deletion_on: 1.day.ago, | ||||||
|  |       deleting_user: user) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   subject(:execute) { described_class.new(group, user, {}).execute } | ||||||
|  | 
 | ||||||
|  |   context 'when restoring the group' do | ||||||
|  |     context 'with a user that can admin the group' do | ||||||
|  |       before do | ||||||
|  |         group.add_owner(user) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'for a group that has been marked for deletion' do | ||||||
|  |         it 'removes the mark for deletion' do | ||||||
|  |           execute | ||||||
|  | 
 | ||||||
|  |           expect(group.marked_for_deletion_on).to be_nil | ||||||
|  |           expect(group.deleting_user).to be_nil | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         it 'returns success' do | ||||||
|  |           result = execute | ||||||
|  | 
 | ||||||
|  |           expect(result).to eq({ status: :success }) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         context 'when restoring fails' do | ||||||
|  |           it 'returns error' do | ||||||
|  |             allow(group.deletion_schedule).to receive(:destroy).and_return(false) | ||||||
|  | 
 | ||||||
|  |             result = execute | ||||||
|  | 
 | ||||||
|  |             expect(result).to eq({ status: :error, message: 'Could not restore the group' }) | ||||||
|  |           end | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'for a group that has not been marked for deletion' do | ||||||
|  |         let(:group) { create(:group) } | ||||||
|  | 
 | ||||||
|  |         it 'does not change the attributes associated with delayed deletion' do | ||||||
|  |           execute | ||||||
|  | 
 | ||||||
|  |           expect(group.marked_for_deletion_on).to be_nil | ||||||
|  |           expect(group.deleting_user).to be_nil | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         it 'returns error' do | ||||||
|  |           result = execute | ||||||
|  | 
 | ||||||
|  |           expect(result).to eq({ status: :error, message: 'Group has not been marked for deletion' }) | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'logs the restore' do | ||||||
|  |         allow(Gitlab::AppLogger).to receive(:info) | ||||||
|  | 
 | ||||||
|  |         expect(::Gitlab::AppLogger).to receive(:info).with("User #{user.id} restored group #{group.full_path}") | ||||||
|  | 
 | ||||||
|  |         execute | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'when the group is deletion is in progress' do | ||||||
|  |         before do | ||||||
|  |           group.namespace_details.update!(deleted_at: Time.current) | ||||||
|  |         end | ||||||
|  | 
 | ||||||
|  |         it { is_expected.to eq({ status: :error, message: 'Group deletion is in progress' }) } | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'with a user that cannot admin the group' do | ||||||
|  |       it 'does not restore the group' do | ||||||
|  |         execute | ||||||
|  | 
 | ||||||
|  |         expect(group.marked_for_deletion?).to be_truthy | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'returns error' do | ||||||
|  |         result = execute | ||||||
|  | 
 | ||||||
|  |         expect(result).to eq({ status: :error, message: 'You are not authorized to perform this action' }) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -15,5 +15,19 @@ RSpec.describe Projects::OpenMergeRequestsCountService, :use_clean_rails_memory_ | ||||||
| 
 | 
 | ||||||
|       expect(subject.count).to eq(1) |       expect(subject.count).to eq(1) | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     context 'when there are hidden merge requests' do | ||||||
|  |       let(:banned_user) { create(:user, :banned) } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         create(:merge_request, :opened, source_project: project, target_project: project, source_branch: 'user-branch') | ||||||
|  |         create(:merge_request, :opened, source_project: project, target_project: project, | ||||||
|  |           source_branch: 'banned-user-branch', author: banned_user) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'does not include hidden merge requests in the count' do | ||||||
|  |         expect(described_class.new(project).count).to eq(1) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,102 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe Projects::RestoreService, feature_category: :groups_and_projects do | ||||||
|  |   let(:user) { create(:user, :with_namespace) } | ||||||
|  |   let(:pending_delete) { nil } | ||||||
|  |   let(:project) do | ||||||
|  |     create(:project, | ||||||
|  |       :repository, | ||||||
|  |       path: 'project-1-deleted-177483', | ||||||
|  |       name: 'Project1 Name-deleted-177483', | ||||||
|  |       namespace: user.namespace, | ||||||
|  |       marked_for_deletion_at: 1.day.ago, | ||||||
|  |       deleting_user: user, | ||||||
|  |       archived: true, | ||||||
|  |       hidden: true, | ||||||
|  |       pending_delete: pending_delete) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'when restoring project' do | ||||||
|  |     subject(:execute) { described_class.new(project, user).execute } | ||||||
|  | 
 | ||||||
|  |     it 'marks project as not hidden, unarchived and not marked for deletion' do | ||||||
|  |       expect(Namespaces::ScheduleAggregationWorker).to receive(:perform_async) | ||||||
|  |         .with(project.namespace.id).and_call_original | ||||||
|  | 
 | ||||||
|  |       execute | ||||||
|  | 
 | ||||||
|  |       expect(Project.unscoped.all).to include(project) | ||||||
|  |       expect(project.archived).to be(false) | ||||||
|  |       expect(project.hidden).to be(false) | ||||||
|  |       expect(project.marked_for_deletion_at).to be_nil | ||||||
|  |       expect(project.deleting_user).to be_nil | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the original project path is not taken' do | ||||||
|  |       it 'renames the project back to its original path' do | ||||||
|  |         expect { execute }.to change { project.path }.from("project-1-deleted-177483").to("project-1") | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'renames the project back to its original name' do | ||||||
|  |         expect { execute }.to change { project.name }.from("Project1 Name-deleted-177483").to("Project1 Name") | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the original project name has been taken' do | ||||||
|  |       before do | ||||||
|  |         create(:project, path: 'project-1', name: 'Project1 Name', namespace: user.namespace, deleting_user: user) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'renames the project back to its original path with a suffix' do | ||||||
|  |         expect { execute }.to change { project.path }.from("project-1-deleted-177483").to(/project-1-[a-zA-Z0-9]{5}/) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'renames the project back to its original name with a suffix' do | ||||||
|  |         expect { execute }.to change { project.name }.from("Project1 Name-deleted-177483") | ||||||
|  |           .to(/Project1 Name-[a-zA-Z0-9]{5}/) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'uses the same suffix for both the path and name' do | ||||||
|  |         execute | ||||||
|  | 
 | ||||||
|  |         path_suffix = project.path.split('-')[-1] | ||||||
|  |         name_suffix = project.name.split('-')[-1] | ||||||
|  | 
 | ||||||
|  |         expect(path_suffix).to eq(name_suffix) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the original project path does not contain the -deleted- suffix' do | ||||||
|  |       let(:project) do | ||||||
|  |         create( | ||||||
|  |           :project, | ||||||
|  |           :repository, | ||||||
|  |           namespace: user.namespace, | ||||||
|  |           marked_for_deletion_at: 1.day.ago, | ||||||
|  |           deleting_user: user, | ||||||
|  |           archived: true, | ||||||
|  |           pending_delete: pending_delete | ||||||
|  |         ) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'renames the project back to its original path' do | ||||||
|  |         expect { execute }.not_to change { project.path } | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'renames the project back to its original name' do | ||||||
|  |         expect { execute }.not_to change { project.name } | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'when restoring project already in process of removal' do | ||||||
|  |     let(:deletion_date) { 2.days.ago } | ||||||
|  |     let(:pending_delete) { true } | ||||||
|  | 
 | ||||||
|  |     it 'does not allow to restore' do | ||||||
|  |       expect(described_class.new(project, user).execute).to include(status: :error) | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -1436,10 +1436,10 @@ | ||||||
|     stylelint-declaration-strict-value "1.10.4" |     stylelint-declaration-strict-value "1.10.4" | ||||||
|     stylelint-scss "6.0.0" |     stylelint-scss "6.0.0" | ||||||
| 
 | 
 | ||||||
| "@gitlab/svgs@3.123.0": | "@gitlab/svgs@3.126.0": | ||||||
|   version "3.123.0" |   version "3.126.0" | ||||||
|   resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.123.0.tgz#1fa3b1a709755ff7c8ef67e18c0442101655ebf0" |   resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.126.0.tgz#1c0bb95c11de808b78afd05dc95aca258c3b39f0" | ||||||
|   integrity sha512-yjVn+utOTIKk8d9JlvGo6EgJ4TQ+CKpe3RddflAqtsQqQuL/2MlVdtaUePybxYzWIaumFuh5LouQ6BrWyw1niQ== |   integrity sha512-7X8uzitNn7NDcVy+FVCw8npMNEUpLGHTO5Z+BJZqVILj/FD+0WveYdPxAEVa9hXYQn5qXWM0ZAknzB9LM6Id8w== | ||||||
| 
 | 
 | ||||||
| "@gitlab/ui@111.9.1": | "@gitlab/ui@111.9.1": | ||||||
|   version "111.9.1" |   version "111.9.1" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue