Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-24 15:06:49 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-24 15:06:49 +0300
commitaf5193aa3cc159fd7d08be988bd00f1921077117 (patch)
tree664d9936ab99208c49b8721877be87b7e420d5bd /app
parent4dcdd5bebb55bd5522ec180070d4d265e00943b5 (diff)
Add latest changes from gitlab-org/gitlab@masterHEADmaster
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/constants.js2
-rw-r--r--app/assets/javascripts/gl_form.js16
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js17
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue17
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue57
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue72
-rw-r--r--app/controllers/projects/gcp/artifact_registry/base_controller.rb43
-rw-r--r--app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb135
-rw-r--r--app/controllers/projects/gcp/artifact_registry/setup_controller.rb11
-rw-r--r--app/graphql/resolvers/namespace_projects_resolver.rb6
-rw-r--r--app/helpers/dashboard_helper.rb4
-rw-r--r--app/helpers/todos_helper.rb4
-rw-r--r--app/mailers/previews/notify_preview.rb2
-rw-r--r--app/models/namespace.rb6
-rw-r--r--app/models/namespaces/traversal/cached.rb54
-rw-r--r--app/models/namespaces/traversal/linear.rb4
-rw-r--r--app/models/namespaces/traversal/recursive.rb6
-rw-r--r--app/models/user_detail.rb1
-rw-r--r--app/services/groups/update_service.rb3
-rw-r--r--app/validators/json_schemas/user_detail_onboarding_status.json17
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml5
-rw-r--r--app/views/layouts/devise.html.haml6
-rw-r--r--app/views/projects/gcp/artifact_registry/docker_images/_docker_image.html.haml33
-rw-r--r--app/views/projects/gcp/artifact_registry/docker_images/_docker_image_tag.html.haml1
-rw-r--r--app/views/projects/gcp/artifact_registry/docker_images/_pagination.html.haml13
-rw-r--r--app/views/projects/gcp/artifact_registry/docker_images/index.html.haml23
-rw-r--r--app/views/projects/gcp/artifact_registry/setup/new.html.haml31
-rw-r--r--app/views/shared/wikis/edit.html.haml3
31 files changed, 191 insertions, 412 deletions
diff --git a/app/assets/javascripts/constants.js b/app/assets/javascripts/constants.js
index 631968ff531..f43a2d5d8ff 100644
--- a/app/assets/javascripts/constants.js
+++ b/app/assets/javascripts/constants.js
@@ -3,5 +3,3 @@ export const getModifierKey = (removeSuffix = false) => {
const winKey = `Ctrl${removeSuffix ? '' : '+'}`;
return window.gl?.client?.isMac ? '⌘' : winKey;
};
-
-export const PRELOAD_THROTTLE_TIMEOUT_MS = 4000;
diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js
index 776f27a8583..f4008fe3cc9 100644
--- a/app/assets/javascripts/gl_form.js
+++ b/app/assets/javascripts/gl_form.js
@@ -5,7 +5,6 @@ import GfmAutoComplete, { defaultAutocompleteConfig } from 'ee_else_ce/gfm_auto_
import { disableButtonIfEmptyField } from '~/lib/utils/common_utils';
import dropzoneInput from './dropzone_input';
import { addMarkdownListeners, removeMarkdownListeners } from './lib/utils/text_markdown';
-import { PRELOAD_THROTTLE_TIMEOUT_MS } from './constants';
export default class GLForm {
/**
@@ -69,21 +68,6 @@ export default class GLForm {
);
this.autoComplete = new GfmAutoComplete(dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), this.enableGFM);
-
- if (this.preloadMembers && dataSources?.members) {
- // for now the preload is only implemented for the members
- // timeout helping to trottle the preloads in the case content_editor
- // is set as main comment editor and support for rspec tests
- // https://gitlab.com/gitlab-org/gitlab/-/issues/427437
-
- requestIdleCallback(() =>
- setTimeout(
- () => this.autoComplete?.fetchData($('.js-gfm-input'), '@'),
- PRELOAD_THROTTLE_TIMEOUT_MS,
- ),
- );
- }
-
this.formDropzone = dropzoneInput(this.form, { parallelUploads: 1 });
if (this.form.is(':not(.js-no-autosize)')) {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js
index 24bc7017e06..88efcfa46e7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/constants.js
@@ -1,3 +1,5 @@
+import { __ } from '~/locale';
+
export const COMPONENTS = {
conflict: () => import('./conflicts.vue'),
discussions_not_resolved: () => import('./unresolved_discussions.vue'),
@@ -5,3 +7,18 @@ export const COMPONENTS = {
need_rebase: () => import('./rebase.vue'),
default: () => import('./message.vue'),
};
+
+export const FAILURE_REASONS = {
+ broken_status: __('Cannot merge the source into the target branch, due to a conflict.'),
+ ci_must_pass: __('Pipeline must succeed.'),
+ conflict: __('Merge conflicts must be resolved.'),
+ discussions_not_resolved: __('Unresolved discussions must be resolved.'),
+ draft_status: __('Merge request must not be draft.'),
+ not_open: __('Merge request must be open.'),
+ need_rebase: __('Merge request must be rebased, because a fast-forward merge is not possible.'),
+ not_approved: __('All required approvals must be given.'),
+ policies_denied: __('Denied licenses must be removed or approved.'),
+ merge_request_blocked: __('Merge request is blocked by another merge request.'),
+ status_checks_must_pass: __('Status checks must pass.'),
+ jira_association_missing: __('Either the title or description must reference a Jira issue.'),
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue b/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue
index 7f21445559a..da3cb1397dd 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/checks/message.vue
@@ -1,6 +1,6 @@
<script>
-import { __ } from '~/locale';
import StatusIcon from '../widget/status_icon.vue';
+import { FAILURE_REASONS } from './constants';
const ICON_NAMES = {
failed: 'failed',
@@ -8,21 +8,6 @@ const ICON_NAMES = {
success: 'success',
};
-export const FAILURE_REASONS = {
- broken_status: __('Cannot merge the source into the target branch, due to a conflict.'),
- ci_must_pass: __('Pipeline must succeed.'),
- conflict: __('Merge conflicts must be resolved.'),
- discussions_not_resolved: __('Unresolved discussions must be resolved.'),
- draft_status: __('Merge request must not be draft.'),
- not_open: __('Merge request must be open.'),
- need_rebase: __('Merge request must be rebased, because a fast-forward merge is not possible.'),
- not_approved: __('All required approvals must be given.'),
- policies_denied: __('Denied licenses must be removed or approved.'),
- merge_request_blocked: __('Merge request is blocked by another merge request.'),
- status_checks_must_pass: __('Status checks must pass.'),
- jira_association_missing: __('Either the title or description must reference a Jira issue.'),
-};
-
export default {
name: 'MergeChecksMessage',
components: {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue b/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue
index 9afed170097..016278db4ca 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/merge_checks.vue
@@ -3,7 +3,10 @@ import { GlSkeletonLoader } from '@gitlab/ui';
import { __, n__, sprintf } from '~/locale';
import { TYPENAME_MERGE_REQUEST } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { COMPONENTS } from '~/vue_merge_request_widget/components/checks/constants';
+import {
+ COMPONENTS,
+ FAILURE_REASONS,
+} from '~/vue_merge_request_widget/components/checks/constants';
import mergeRequestQueryVariablesMixin from '../mixins/merge_request_query_variables';
import mergeChecksQuery from '../queries/merge_checks.query.graphql';
import mergeChecksSubscription from '../queries/merge_checks.subscription.graphql';
@@ -102,7 +105,7 @@ export default {
const order = ['FAILED', 'SUCCESS'];
return [...this.checks]
- .filter((s) => s.status !== 'INACTIVE')
+ .filter((s) => s.status !== 'INACTIVE' && FAILURE_REASONS[s.identifier.toLowerCase()])
.sort((a, b) => order.indexOf(a.status) - order.indexOf(b.status));
},
failedChecks() {
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
index 5d72ac34e73..8ea97ad73b4 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/base_token.vue
@@ -78,16 +78,12 @@ export default {
required: false,
default: undefined,
},
- multiSelectValues: {
- type: Array,
- required: false,
- default: () => [],
- },
},
data() {
return {
hasFetched: false, // use this to avoid flash of `No suggestions found` before fetching
searchKey: '',
+ selectedTokens: [],
recentSuggestions: this.config.recentSuggestionsStorageKey
? getRecentlyUsedSuggestions(this.config.recentSuggestionsStorageKey) ?? []
: [],
@@ -197,6 +193,30 @@ export default {
}
},
},
+ value: {
+ deep: true,
+ immediate: true,
+ handler(newValue) {
+ const { data } = newValue;
+
+ if (!this.multiSelectEnabled) {
+ return;
+ }
+
+ // don't add empty values to selectedUsernames
+ if (!data) {
+ return;
+ }
+
+ if (Array.isArray(data)) {
+ this.selectedTokens = data;
+ // !active so we don't add strings while searching, e.g. r, ro, roo
+ // !includes so we don't add the same usernames (if @input is emitted twice)
+ } else if (!this.active && !this.selectedTokens.includes(data)) {
+ this.selectedTokens = this.selectedTokens.concat(data);
+ }
+ },
+ },
},
methods: {
handleInput: debounce(function debouncedSearch({ data, operator }) {
@@ -222,7 +242,15 @@ export default {
}, DEBOUNCE_DELAY),
handleTokenValueSelected(selectedValue) {
if (this.multiSelectEnabled) {
- this.$emit('token-selected', selectedValue);
+ const index = this.selectedTokens.indexOf(selectedValue);
+ if (index > -1) {
+ this.selectedTokens.splice(index, 1);
+ } else {
+ this.selectedTokens.push(selectedValue);
+ }
+
+ // need to clear search
+ this.$emit('input', { ...this.value, data: '' });
}
const activeTokenValue = this.getActiveTokenValue(this.suggestions, selectedValue);
@@ -253,7 +281,7 @@ export default {
:config="validatedConfig"
:value="value"
:active="active"
- :multi-select-values="multiSelectValues"
+ :multi-select-values="selectedTokens"
v-bind="$attrs"
v-on="$listeners"
@input="handleInput"
@@ -265,6 +293,7 @@ export default {
:view-token-props="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
...viewTokenProps,
activeTokenValue,
+ selectedTokens,
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
></slot>
</template>
@@ -274,6 +303,7 @@ export default {
:view-token-props="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ {
...viewTokenProps,
activeTokenValue,
+ selectedTokens,
} /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */"
></slot>
</template>
@@ -290,17 +320,26 @@ export default {
</template>
<template v-if="showRecentSuggestions">
<gl-dropdown-section-header>{{ __('Recently used') }}</gl-dropdown-section-header>
- <slot name="suggestions-list" :suggestions="recentSuggestions"></slot>
+ <slot
+ name="suggestions-list"
+ :suggestions="recentSuggestions"
+ :selections="selectedTokens"
+ ></slot>
<gl-dropdown-divider />
</template>
<slot
v-if="showPreloadedSuggestions"
name="suggestions-list"
:suggestions="preloadedSuggestions"
+ :selections="selectedTokens"
></slot>
<gl-loading-icon v-if="suggestionsLoading" size="sm" />
<template v-else-if="showAvailableSuggestions">
- <slot name="suggestions-list" :suggestions="availableSuggestions"></slot>
+ <slot
+ name="suggestions-list"
+ :suggestions="availableSuggestions"
+ :selections="selectedTokens"
+ ></slot>
</template>
<gl-dropdown-text v-else-if="showNoMatchesText">
{{ __('No matches found') }}
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
index 87e295d00dd..8cf4759d419 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/user_token.vue
@@ -6,8 +6,7 @@ import { __ } from '~/locale';
import { WORKSPACE_GROUP, WORKSPACE_PROJECT } from '~/issues/constants';
import usersAutocompleteQuery from '~/graphql_shared/queries/users_autocomplete.query.graphql';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import { OPERATORS_TO_GROUP, OPTIONS_NONE_ANY } from '../constants';
+import { OPTIONS_NONE_ANY } from '../constants';
import BaseToken from './base_token.vue';
@@ -19,7 +18,6 @@ export default {
GlIntersperse,
GlFilteredSearchSuggestion,
},
- mixins: [glFeatureFlagMixin()],
props: {
config: {
type: Object,
@@ -40,7 +38,6 @@ export default {
users: this.config.initialUsers || [],
allUsers: this.config.initialUsers || [],
loading: false,
- selectedUsernames: [],
};
},
computed: {
@@ -56,39 +53,6 @@ export default {
fetchUsersQuery() {
return this.config.fetchUsers ? this.config.fetchUsers : this.fetchUsersBySearchTerm;
},
- multiSelectEnabled() {
- return (
- this.config.multiSelect &&
- this.glFeatures.groupMultiSelectTokens &&
- OPERATORS_TO_GROUP.includes(this.value.operator)
- );
- },
- },
- watch: {
- value: {
- deep: true,
- immediate: true,
- handler(newValue) {
- const { data } = newValue;
-
- if (!this.multiSelectEnabled) {
- return;
- }
-
- // don't add empty values to selectedUsernames
- if (!data) {
- return;
- }
-
- if (Array.isArray(data)) {
- this.selectedUsernames = data;
- // !active so we don't add strings while searching, e.g. r, ro, roo
- // !includes so we don't add the same usernames (if @input is emitted twice)
- } else if (!this.active && !this.selectedUsernames.includes(data)) {
- this.selectedUsernames = this.selectedUsernames.concat(data);
- }
- },
- },
},
methods: {
getActiveUser(users, data) {
@@ -104,26 +68,6 @@ export default {
const user = this.getActiveUser(this.allUsers, username);
return this.getAvatarUrl(user);
},
- addCheckIcon(username) {
- return this.multiSelectEnabled && this.selectedUsernames.includes(username);
- },
- addPadding(username) {
- return this.multiSelectEnabled && !this.selectedUsernames.includes(username);
- },
- handleSelected(username) {
- if (!this.multiSelectEnabled) {
- return;
- }
-
- const index = this.selectedUsernames.indexOf(username);
- if (index > -1) {
- this.selectedUsernames.splice(index, 1);
- } else {
- this.selectedUsernames.push(username);
- }
-
- this.$emit('input', { ...this.value, data: '' });
- },
fetchUsersBySearchTerm(search) {
return this.$apollo
.query({
@@ -171,16 +115,14 @@ export default {
:get-active-token-value="getActiveUser"
:default-suggestions="defaultUsers"
:preloaded-suggestions="preloadedUsers"
- :multi-select-values="selectedUsernames"
v-bind="$attrs"
@fetch-suggestions="fetchUsers"
- @token-selected="handleSelected"
v-on="$listeners"
>
- <template #view="{ viewTokenProps: { inputValue, activeTokenValue } }">
- <gl-intersperse v-if="multiSelectEnabled" separator=",">
+ <template #view="{ viewTokenProps: { inputValue, activeTokenValue, selectedTokens } }">
+ <gl-intersperse v-if="selectedTokens.length > 0" separator=",">
<span
- v-for="(username, index) in selectedUsernames"
+ v-for="(username, index) in selectedTokens"
:key="username"
:class="{ 'gl-ml-2': index > 0 }"
><gl-avatar :size="16" :src="avatarFor(username)" class="gl-mr-1" />{{
@@ -198,7 +140,7 @@ export default {
{{ activeTokenValue ? activeTokenValue.name : inputValue }}
</template>
</template>
- <template #suggestions-list="{ suggestions }">
+ <template #suggestions-list="{ suggestions, selections = [] }">
<gl-filtered-search-suggestion
v-for="user in suggestions"
:key="user.username"
@@ -206,10 +148,10 @@ export default {
>
<div
class="gl-display-flex gl-align-items-center"
- :class="{ 'gl-pl-6': addPadding(user.username) }"
+ :class="{ 'gl-pl-6': !selections.includes(user.username) }"
>
<gl-icon
- v-if="addCheckIcon(user.username)"
+ v-if="selections.includes(user.username)"
name="check"
class="gl-mr-3 gl-text-secondary gl-flex-shrink-0"
/>
diff --git a/app/controllers/projects/gcp/artifact_registry/base_controller.rb b/app/controllers/projects/gcp/artifact_registry/base_controller.rb
deleted file mode 100644
index 4084427f3e5..00000000000
--- a/app/controllers/projects/gcp/artifact_registry/base_controller.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module Gcp
- module ArtifactRegistry
- class BaseController < ::Projects::ApplicationController
- before_action :ensure_feature_flag
- before_action :ensure_saas
- before_action :authorize_read_container_image!
- before_action :ensure_private_project
-
- feature_category :container_registry
- urgency :low
-
- private
-
- def ensure_feature_flag
- return if Feature.enabled?(:gcp_technical_demo, project)
-
- @error = 'Feature flag disabled'
-
- render
- end
-
- def ensure_saas
- return if Gitlab.com_except_jh? # rubocop: disable Gitlab/AvoidGitlabInstanceChecks -- demo requirement
-
- @error = "Can't run here"
-
- render
- end
-
- def ensure_private_project
- return if project.private?
-
- @error = 'Can only run on private projects'
-
- render
- end
- end
- end
- end
-end
diff --git a/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb b/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb
deleted file mode 100644
index 60adbbe6e5d..00000000000
--- a/app/controllers/projects/gcp/artifact_registry/docker_images_controller.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module Gcp
- module ArtifactRegistry
- class DockerImagesController < Projects::Gcp::ArtifactRegistry::BaseController
- before_action :require_gcp_params
- before_action :handle_pagination
-
- REPO_NAME_REGEX = %r{/repositories/(.*)/dockerImages/}
-
- def index
- result = service.execute(page_token: params[:page_token])
-
- if result.success?
- @docker_images = process_docker_images(result.payload[:images] || [])
- @next_page_token = result.payload[:next_page_token]
- @artifact_repository_name = artifact_repository_name
- @error = @docker_images.blank? ? 'No docker images' : false
- else
- @error = result.message
- end
- end
-
- private
-
- def service
- ::GoogleCloudPlatform::ArtifactRegistry::ListDockerImagesService.new(
- project: @project,
- current_user: current_user,
- params: {
- gcp_project_id: gcp_project_id,
- gcp_location: gcp_location,
- gcp_repository: gcp_ar_repository,
- gcp_wlif: gcp_wlif_url
- }
- )
- end
-
- def process_docker_images(raw_images)
- raw_images.map { |r| process_docker_image(r) }
- end
-
- def process_docker_image(raw_image)
- DockerImage.new(
- name: raw_image[:name],
- uri: raw_image[:uri],
- tags: raw_image[:tags],
- image_size_bytes: raw_image[:size_bytes],
- media_type: raw_image[:media_type],
- upload_time: raw_image[:uploaded_at],
- build_time: raw_image[:built_at],
- update_time: raw_image[:updated_at]
- )
- end
-
- def artifact_repository_name
- return unless @docker_images.present?
-
- (@docker_images.first.name || '')[REPO_NAME_REGEX, 1]
- end
-
- def handle_pagination
- @page = Integer(params[:page] || 1)
- @page_tokens = {}
- @previous_page_token = nil
-
- if params[:page_tokens]
- @page_tokens = ::Gitlab::Json.parse(Base64.decode64(params[:page_tokens]))
- @previous_page_token = @page_tokens[(@page - 1).to_s]
- end
-
- @page_tokens[@page.to_s] = params[:page_token]
- @page_tokens = Base64.encode64(::Gitlab::Json.dump(@page_tokens.compact))
- end
-
- def require_gcp_params
- return unless gcp_project_id.blank? || gcp_location.blank? || gcp_ar_repository.blank? || gcp_wlif_url.blank?
-
- redirect_to new_namespace_project_gcp_artifact_registry_setup_path
- end
-
- def gcp_project_id
- params[:gcp_project_id]
- end
-
- def gcp_location
- params[:gcp_location]
- end
-
- def gcp_ar_repository
- params[:gcp_ar_repository]
- end
-
- def gcp_wlif_url
- params[:gcp_wlif_url]
- end
-
- class DockerImage
- include ActiveModel::API
-
- attr_accessor :name, :uri, :tags, :image_size_bytes, :upload_time, :media_type, :build_time, :update_time
-
- SHORT_NAME_REGEX = %r{dockerImages/(.*)$}
-
- def short_name
- (name || '')[SHORT_NAME_REGEX, 1]
- end
-
- def updated_at
- return unless update_time
-
- Time.zone.parse(update_time)
- end
-
- def built_at
- return unless build_time
-
- Time.zone.parse(build_time)
- end
-
- def uploaded_at
- return unless upload_time
-
- Time.zone.parse(upload_time)
- end
-
- def details_url
- "https://#{uri}"
- end
- end
- end
- end
- end
-end
diff --git a/app/controllers/projects/gcp/artifact_registry/setup_controller.rb b/app/controllers/projects/gcp/artifact_registry/setup_controller.rb
deleted file mode 100644
index e90304ce593..00000000000
--- a/app/controllers/projects/gcp/artifact_registry/setup_controller.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module Gcp
- module ArtifactRegistry
- class SetupController < ::Projects::Gcp::ArtifactRegistry::BaseController
- def new; end
- end
- end
- end
-end
diff --git a/app/graphql/resolvers/namespace_projects_resolver.rb b/app/graphql/resolvers/namespace_projects_resolver.rb
index f0781058bea..1e8a7365fc0 100644
--- a/app/graphql/resolvers/namespace_projects_resolver.rb
+++ b/app/graphql/resolvers/namespace_projects_resolver.rb
@@ -7,6 +7,11 @@ module Resolvers
default_value: false,
description: 'Include also subgroup projects.'
+ argument :include_archived, GraphQL::Types::Boolean,
+ required: false,
+ default_value: true,
+ description: 'Include also archived projects.'
+
argument :not_aimed_for_deletion, GraphQL::Types::Boolean,
required: false,
default_value: false,
@@ -65,6 +70,7 @@ module Resolvers
def finder_params(args)
{
include_subgroups: args.dig(:include_subgroups),
+ include_archived: args.dig(:include_archived),
not_aimed_for_deletion: args.dig(:not_aimed_for_deletion),
sort: args.dig(:sort),
search: args.dig(:search),
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 3756584e3b3..89f6d61ef44 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -33,6 +33,10 @@ module DashboardHelper
end
end
end
+
+ def user_groups_requiring_reauth
+ []
+ end
end
DashboardHelper.prepend_mod_with('DashboardHelper')
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index fc4d69dcdbc..7d29cd7a877 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -254,6 +254,10 @@ module TodosHelper
!todo.build_failed? && !todo.unmergeable?
end
+ def todo_groups_requiring_saml_reauth(_todos)
+ []
+ end
+
private
def todos_design_path(todo, path_options)
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index c7d6f2843de..1b083c70bba 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -381,7 +381,7 @@ class NotifyPreview < ActionMailer::Preview
def custom_email_credential
@custom_email_credential ||= project.service_desk_custom_email_credential || ServiceDesk::CustomEmailCredential.create!(
project: project,
- smtp_address: 'smtp.gmail.com', # Use gmail, because Gitlab::UrlBlocker resolves DNS
+ smtp_address: 'smtp.gmail.com', # Use gmail, because Gitlab::HTTP_V2::UrlBlocker resolves DNS
smtp_port: 587,
smtp_username: 'user@gmail.com',
smtp_password: 'supersecret'
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 238556f0cf0..9020f90fd3c 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -472,12 +472,8 @@ class Namespace < ApplicationRecord
false
end
- def all_project_ids
- all_projects.pluck(:id)
- end
-
def all_project_ids_except(ids)
- all_projects.where.not(id: ids).pluck(:id)
+ all_project_ids.where.not(id: ids)
end
# Deprecated, use #licensed_feature_available? instead. Remove once Namespace#feature_available? isn't used anymore.
diff --git a/app/models/namespaces/traversal/cached.rb b/app/models/namespaces/traversal/cached.rb
index 55eaaa4667e..b962038d039 100644
--- a/app/models/namespaces/traversal/cached.rb
+++ b/app/models/namespaces/traversal/cached.rb
@@ -10,8 +10,62 @@ module Namespaces
after_destroy :invalidate_descendants_cache
end
+ override :self_and_descendant_ids
+ def self_and_descendant_ids
+ return super unless attempt_to_use_cached_data?
+
+ scope_with_cached_ids(
+ super,
+ self.class,
+ Namespaces::Descendants.arel_table[:self_and_descendant_group_ids]
+ )
+ end
+
+ override :all_project_ids
+ def all_project_ids
+ return super unless attempt_to_use_cached_data?
+
+ scope_with_cached_ids(
+ all_projects.select(:id),
+ Project,
+ Namespaces::Descendants.arel_table[:all_project_ids]
+ )
+ end
+
private
+ # This method implements an OR based cache lookup using COALESCE, similar what you would do in Ruby:
+ # return cheap_cached_data || expensive_uncached_data
+ def scope_with_cached_ids(consistent_ids_scope, model, cached_ids_column)
+ # Look up the cached ids and unnest them into rows if the cache is up to date.
+ cache_lookup_query = Namespaces::Descendants
+ .where(outdated_at: nil, namespace_id: id)
+ .select(cached_ids_column.as('ids'))
+
+ # Invoke the consistent lookup query and collect the ids as a single array value
+ consistent_descendant_ids_scope = model
+ .from(consistent_ids_scope.arel.as(model.table_name))
+ .reselect(Arel::Nodes::NamedFunction.new('ARRAY_AGG', [model.arel_table[:id]]).as('ids'))
+ .unscope(where: :type)
+
+ from = <<~SQL
+ UNNEST(
+ COALESCE(
+ (SELECT ids FROM (#{cache_lookup_query.to_sql}) cached_query),
+ (SELECT ids FROM (#{consistent_descendant_ids_scope.to_sql}) consistent_query))
+ ) AS #{model.table_name}(id)
+ SQL
+
+ model
+ .from(from)
+ .unscope(where: :type)
+ .select(:id)
+ end
+
+ def attempt_to_use_cached_data?
+ Feature.enabled?(:group_hierarchy_optimization, self, type: :beta)
+ end
+
override :sync_traversal_ids
def sync_traversal_ids
super
diff --git a/app/models/namespaces/traversal/linear.rb b/app/models/namespaces/traversal/linear.rb
index c3348c49ea1..5779b777fd7 100644
--- a/app/models/namespaces/traversal/linear.rb
+++ b/app/models/namespaces/traversal/linear.rb
@@ -106,6 +106,10 @@ module Namespaces
end
end
+ def all_project_ids
+ all_projects.select(:id)
+ end
+
def self_and_descendants
return super unless use_traversal_ids?
diff --git a/app/models/namespaces/traversal/recursive.rb b/app/models/namespaces/traversal/recursive.rb
index 1c5d395cb3c..3d551243cfb 100644
--- a/app/models/namespaces/traversal/recursive.rb
+++ b/app/models/namespaces/traversal/recursive.rb
@@ -19,6 +19,12 @@ module Namespaces
end
alias_method :recursive_root_ancestor, :root_ancestor
+ def all_project_ids
+ namespace = user_namespace? ? self : recursive_self_and_descendant_ids
+ Project.where(namespace: namespace).select(:id)
+ end
+ alias_method :recursive_all_project_ids, :all_project_ids
+
# Returns all ancestors, self, and descendants of the current namespace.
def self_and_hierarchy
object_hierarchy(self.class.where(id: id))
diff --git a/app/models/user_detail.rb b/app/models/user_detail.rb
index bbb08ed5774..e6dc99d114b 100644
--- a/app/models/user_detail.rb
+++ b/app/models/user_detail.rb
@@ -39,6 +39,7 @@ class UserDetail < MainClusterwide::ApplicationRecord
validates :skype, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validates :twitter, length: { maximum: DEFAULT_FIELD_LENGTH }, allow_blank: true
validates :website_url, length: { maximum: DEFAULT_FIELD_LENGTH }, url: true, allow_blank: true, if: :website_url_changed?
+ validates :onboarding_status, json_schema: { filename: 'user_detail_onboarding_status' }
before_validation :sanitize_attrs
before_save :prevent_nil_fields
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index a6ef8c8743b..bdf943091e9 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -61,7 +61,8 @@ module Groups
params[:namespace_descendants_attributes] = {
traversal_ids: group.traversal_ids,
all_project_ids: [],
- self_and_descendant_group_ids: []
+ self_and_descendant_group_ids: [],
+ outdated_at: Time.current
}
else
return unless group.namespace_descendants
diff --git a/app/validators/json_schemas/user_detail_onboarding_status.json b/app/validators/json_schemas/user_detail_onboarding_status.json
new file mode 100644
index 00000000000..548e81f1955
--- /dev/null
+++ b/app/validators/json_schemas/user_detail_onboarding_status.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "title": "Onboarding Status",
+ "description": "Onboarding Status items recorded during onboarding/registration",
+ "type": "object",
+ "properties": {
+ "step_url": {
+ "description": "Onboarding step the user is currently on or last step before finishing",
+ "type": "string"
+ },
+ "email_opt_in": {
+ "description": "Setting to guide marketing email opt-ins outside of the product. See https://gitlab.com/gitlab-org/gitlab/-/issues/435741",
+ "type": "boolean"
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 78c3270114e..f7b2ba59549 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -6,6 +6,8 @@
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
= render_dashboard_ultimate_trial(current_user)
+= render_if_exists 'shared/dashboard/saml_reauth_notice',
+ groups_requiring_saml_reauth: user_groups_requiring_reauth
.page-title-holder.gl-display-flex.gl-align-items-center
%h1.page-title.gl-font-size-h-display= _('Issues')
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 91cec50226b..d29cb56db07 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -11,6 +11,8 @@
add_page_specific_style 'page_bundles/issuable_list'
= render_dashboard_ultimate_trial(current_user)
+= render_if_exists 'shared/dashboard/saml_reauth_notice',
+ groups_requiring_saml_reauth: user_groups_requiring_reauth
.page-title-holder.d-flex.align-items-start.flex-column.flex-sm-row.align-items-sm-center
%h1.page-title.gl-font-size-h-display= title
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 1b0bd10db77..0587ba61db4 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,7 +2,10 @@
= render_two_factor_auth_recovery_settings_check
= render_dashboard_ultimate_trial(current_user)
-= render_if_exists 'dashboard/todos/saml_reauth_notice'
+
+= render_if_exists 'shared/dashboard/saml_reauth_notice',
+ groups_requiring_saml_reauth: todo_groups_requiring_saml_reauth(@todos)
+
- add_page_specific_style 'page_bundles/todos'
- add_page_specific_style 'page_bundles/issuable'
- filter_by_done = params[:state] == 'done'
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 0ae2e5337f5..a7e078b074a 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -2,7 +2,7 @@
- custom_text = custom_sign_in_description
!!! 5
%html.html-devise-layout{ class: user_application_theme, lang: I18n.locale }
- = render "layouts/head", { startup_filename: 'signin' }
+ = render "layouts/head"
%body.gl-h-full.login-page.navless{ class: "#{system_message_class} #{client_class_list}", data: { page: body_data_page, testid: 'login-page' } }
= header_message
= render "layouts/init_client_detection_flags"
@@ -15,12 +15,12 @@
.row.gl-mt-5.gl-row-gap-5
.col-md.order-12.sm-bg-gray
.col-sm-12
- %h1.mb-3.gl-font-size-h2
+ %h1.gl-mb-5.gl-font-size-h2
= brand_title
= custom_text
.col-md.order-md-12
.col-sm-12.bar
- .gl-text-center
+ .gl-text-center.gl-mb-5
= brand_image
= yield
- else
diff --git a/app/views/projects/gcp/artifact_registry/docker_images/_docker_image.html.haml b/app/views/projects/gcp/artifact_registry/docker_images/_docker_image.html.haml
deleted file mode 100644
index 750dea9896f..00000000000
--- a/app/views/projects/gcp/artifact_registry/docker_images/_docker_image.html.haml
+++ /dev/null
@@ -1,33 +0,0 @@
-.gl-display-flex.gl-flex-direction-column
- .gl-display-flex.gl-flex-direction-column.gl-border-b-solid.gl-border-t-solid.gl-border-t-1.gl-border-b-1.gl-border-t-transparent.gl-border-b-gray-100
- .gl-display-flex.gl-align-items-center.gl-py-3
- .gl-display-flex.gl-flex-direction-column.gl-sm-flex-direction-row.gl-justify-content-space-between.gl-align-items-stretch.gl-flex-grow-1
- .gl-display-flex.gl-flex-direction-column.gl-mb-3.gl-sm-mb-0.gl-min-w-0.gl-flex-grow-1
- .gl-display-flex.gl-align-items-center.gl-text-body.gl-font-weight-bold.gl-font-size-h2
- %span.gl-text-body.gl-font-weight-bold= docker_image.short_name
- .gl-bg-gray-50.gl-inset-border-1-gray-100.gl-rounded-base.gl-pt-6
- .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
- = sprite_icon('information-o', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
- Full name: #{docker_image.name}
- .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
- = sprite_icon('earth', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
- %a{ href: docker_image.details_url, target: 'blank', rel: 'noopener noreferrer' }
- Artifact Registry details page
- .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
- = sprite_icon('doc-code', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
- Media Type: #{docker_image.media_type}
- .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
- = sprite_icon('archive', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
- Size: #{number_to_human_size(docker_image.image_size_bytes)}
- .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
- = sprite_icon('calendar', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
- Built at: #{docker_image.built_at&.to_fs}
- .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
- = sprite_icon('calendar', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
- Uploaded at: #{docker_image.uploaded_at&.to_fs}
- .gl-display-flex.gl-align-items-top.gl-font-monospace.gl-font-sm.gl-word-break-all.gl-p-4.gl-border-b-solid.gl-border-gray-100.gl-border-b-1
- = sprite_icon('calendar', css_class: 'gl-text-gray-500 gl-mr-3 gl-icon s16')
- Updated at: #{docker_image.updated_at&.to_fs}
- - if docker_image.tags.present?
- .gl-display-flex.gl-align-items-center.gl-text-gray-500.gl-min-h-6.gl-min-w-0.gl-flex-grow-1.gl-pt-4
- = render partial: 'docker_image_tag', collection: docker_image.tags
diff --git a/app/views/projects/gcp/artifact_registry/docker_images/_docker_image_tag.html.haml b/app/views/projects/gcp/artifact_registry/docker_images/_docker_image_tag.html.haml
deleted file mode 100644
index a030cd7d634..00000000000
--- a/app/views/projects/gcp/artifact_registry/docker_images/_docker_image_tag.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-%a.gl-button.btn.btn-md.btn-default.gl-mr-3!= docker_image_tag
diff --git a/app/views/projects/gcp/artifact_registry/docker_images/_pagination.html.haml b/app/views/projects/gcp/artifact_registry/docker_images/_pagination.html.haml
deleted file mode 100644
index df98ba8d68e..00000000000
--- a/app/views/projects/gcp/artifact_registry/docker_images/_pagination.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-.gl-display-flex.gl-justify-content-center
- %nav.gl-pagination.gl-mt-3
- .gl-keyset-pagination.btn-group
- - if @page > 1
- = link_to 'Prev', namespace_project_gcp_artifact_registry_docker_images_path(params[:namespace_id], params[:project_id], page_token: @previous_page_token, page_tokens: @page_tokens, page: @page - 1, gcp_project_id: params[:gcp_project_id], gcp_location: params[:gcp_location], gcp_ar_repository: params[:gcp_ar_repository], gcp_wlif_url: params[:gcp_wlif_url]), class: 'btn btn-default btn-md gl-button'
- - else
- %span.btn.btn-default.btn-md.gl-button.disabled= 'Prev'
- - if @next_page_token.present?
- = link_to 'Next', namespace_project_gcp_artifact_registry_docker_images_path(params[:namespace_id], params[:project_id], page_token: @next_page_token, page_tokens: @page_tokens, page: @page + 1, gcp_project_id: params[:gcp_project_id], gcp_location: params[:gcp_location], gcp_ar_repository: params[:gcp_ar_repository], gcp_wlif_url: params[:gcp_wlif_url]), class: 'btn btn-default btn-md gl-button'
- - else
- %span.btn.btn-default.btn-md.gl-button.disabled= 'Next'
-
-
diff --git a/app/views/projects/gcp/artifact_registry/docker_images/index.html.haml b/app/views/projects/gcp/artifact_registry/docker_images/index.html.haml
deleted file mode 100644
index b487a175691..00000000000
--- a/app/views/projects/gcp/artifact_registry/docker_images/index.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-- page_title 'Artifact Registry Docker Images'
-
-- unless @error
- .gl-display-flex.gl-flex-direction-column
- .gl-display-flex.gl-justify-content-space-between.gl-py-3
- .gl-flex-direction-column.gl-flex-grow-1
- .gl-display-flex
- .gl-display-flex.gl-flex-direction-column
- %h2.gl-font-size-h1.gl-mt-3.gl-mb-0 Docker Images of #{@artifact_repository_name}
- = render partial: 'pagination'
- = render partial: 'docker_image', collection: @docker_images
- = render partial: 'pagination'
-- else
- .flash-container.flash-container-page.sticky
- .gl-alert.flash-notice.gl-alert-info
- .gl-alert-icon-container
- = sprite_icon('information-o', css_class: 's16 gl-alert-icon gl-alert-icon-no-title')
- .gl-alert-content
- .gl-alert-body
- - if @error
- = @error
- - else
- Nothing to show here.
diff --git a/app/views/projects/gcp/artifact_registry/setup/new.html.haml b/app/views/projects/gcp/artifact_registry/setup/new.html.haml
deleted file mode 100644
index 39ce0093372..00000000000
--- a/app/views/projects/gcp/artifact_registry/setup/new.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-- page_title 'Artifact Registry Setup'
-
-- if @error.present?
- .flash-container.flash-container-page.sticky
- .gl-alert.flash-notice.gl-alert-info
- .gl-alert-icon-container
- = sprite_icon('information-o', css_class: 's16 gl-alert-icon gl-alert-icon-no-title')
- .gl-alert-content
- .gl-alert-body= @error
-- else
- %p
-
- = form_tag namespace_project_gcp_artifact_registry_docker_images_path , method: :get do
- .form-group.row
- = label_tag :gcp_project_id, 'Google Project ID', class: 'col-form-label col-md-2'
- .col-md-4
- = text_field_tag :gcp_project_id, nil, class: 'form-control gl-form-input gl-mr-3'
- .form-group.row
- = label_tag :gcp_location, 'Google Project Location', class: 'col-form-label col-md-2'
- .col-md-4
- = text_field_tag :gcp_location, nil, class: 'form-control gl-form-input gl-mr-3'
- .form-group.row
- = label_tag :gcp_ar_repository, 'Artifact Registry Repository Name', class: 'col-form-label col-md-2'
- .col-md-4
- = text_field_tag :gcp_ar_repository, nil, class: 'form-control gl-form-input gl-mr-3'
- .form-group.row
- = label_tag :gcp_wlif_url, 'Worflow Identity Federation url', class: 'col-form-label col-md-2'
- .col-md-4
- = text_field_tag :gcp_wlif_url, nil, class: 'form-control gl-form-input gl-mr-3'
- .form-actions
- = submit_tag 'Setup', class: 'gl-button btn btn-confirm'
diff --git a/app/views/shared/wikis/edit.html.haml b/app/views/shared/wikis/edit.html.haml
index ce8c7782c7f..ffe479329b4 100644
--- a/app/views/shared/wikis/edit.html.haml
+++ b/app/views/shared/wikis/edit.html.haml
@@ -1,3 +1,4 @@
+- breadcrumb_title(s_("Wiki|New Page")) unless @page.persisted?
- wiki_page_title @page, @page.persisted? ? _('Edit') : _('New')
- add_page_specific_style 'page_bundles/wiki'
- @gfm_form = true
@@ -16,7 +17,7 @@
&middot;
= s_("Wiki|Edit Page")
- else
- = s_("Wiki|Create New Page")
+ = s_("Wiki|New Page")
.nav-controls.pb-md-3.pb-lg-0
- if @page.persisted?