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
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/test-on-gdk/main.gitlab-ci.yml45
-rw-r--r--.rubocop_todo/rspec/context_wording.yml2
-rw-r--r--.rubocop_todo/rspec/missing_feature_category.yml1
-rw-r--r--.rubocop_todo/style/class_and_module_children.yml1
-rw-r--r--.rubocop_todo/style/guard_clause.yml1
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_create_form.vue33
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_details.vue4
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_managers_detail.vue24
-rw-r--r--app/assets/javascripts/ci/runner/components/runner_managers_table.vue11
-rw-r--r--app/assets/javascripts/ci/runner/constants.js3
-rw-r--r--app/assets/javascripts/ci/runner/graphql/show/runner_managers.query.graphql6
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue31
-rw-r--r--app/graphql/mutations/achievements/delete_user_achievement.rb33
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/policies/group_policy.rb4
-rw-r--r--app/services/achievements/destroy_user_achievement_service.rb33
-rw-r--r--app/services/database/mark_migration_service.rb58
-rw-r--r--app/services/personal_access_tokens/last_used_service.rb9
-rw-r--r--config/feature_flags/development/revert_daily_hll_events_to_weekly_aggregation.yml8
-rw-r--r--config/feature_flags/development/update_personal_access_token_usage_information_every_10_minutes.yml8
-rw-r--r--data/deprecations/15-6-deprecate-post-api-v4-runner.yml2
-rw-r--r--data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml2
-rw-r--r--db/post_migrate/20230328111013_re_migrate_redis_slot_keys.rb6
-rw-r--r--db/post_migrate/20230608081257_delete_index_members_on_source_id_and_source_type.rb15
-rw-r--r--db/post_migrate/20230608081924_delete_index_unique_project_authorizations_on_project_id_user_id.rb15
-rw-r--r--db/schema_migrations/202306080812571
-rw-r--r--db/schema_migrations/202306080819241
-rw-r--r--db/structure.sql4
-rw-r--r--doc/api/error_tracking.md6
-rw-r--r--doc/api/graphql/reference/index.md23
-rw-r--r--doc/architecture/blueprints/pods/index.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-admin-area.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-agent-for-kubernetes.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-backups.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-ci-runners.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-container-registry.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-contributions-forks.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-dashboard.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-data-migration.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-database-sequences.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-git-access.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-gitlab-pages.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-global-search.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-graphql.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-organizations.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-personal-namespaces.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-router-endpoints-classification.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-schema-changes.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-secrets.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-snippets.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-template.md11
-rw-r--r--doc/architecture/blueprints/pods/pods-feature-uploads.md11
-rw-r--r--doc/architecture/blueprints/pods/proposal-stateless-router-with-buffering-requests.md11
-rw-r--r--doc/architecture/blueprints/pods/proposal-stateless-router-with-routes-learning.md11
-rw-r--r--doc/architecture/blueprints/remote_development/index.md2
-rw-r--r--doc/ci/runners/saas/macos_saas_runner.md14
-rw-r--r--doc/development/documentation/styleguide/index.md22
-rw-r--r--doc/development/search/advanced_search_migration_styleguide.md20
-rw-r--r--doc/development/workspace/index.md11
-rw-r--r--doc/gitlab-basics/add-file.md2
-rw-r--r--doc/install/azure/index.md3
-rw-r--r--doc/raketasks/backup_restore.md6
-rw-r--r--doc/security/hardening_application_recommendations.md39
-rw-r--r--doc/security/password_length_limits.md6
-rw-r--r--doc/security/reset_user_password.md5
-rw-r--r--doc/security/ssh_keys_restrictions.md5
-rw-r--r--doc/security/two_factor_authentication.md9
-rw-r--r--doc/security/unlock_user.md3
-rw-r--r--doc/security/user_email_confirmation.md7
-rw-r--r--doc/security/user_file_uploads.md4
-rw-r--r--doc/security/webhooks.md12
-rw-r--r--doc/update/deprecations.md4
-rw-r--r--doc/user/analytics/analytics_dashboards.md174
-rw-r--r--doc/user/analytics/index.md2
-rw-r--r--doc/user/packages/infrastructure_registry/index.md11
-rw-r--r--doc/user/product_analytics/index.md139
-rw-r--r--doc/user/profile/achievements.md30
-rw-r--r--doc/user/profile/personal_access_tokens.md7
-rw-r--r--doc/user/project/badges.md21
-rw-r--r--doc/user/project/description_templates.md20
-rw-r--r--lib/api/admin/migrations.rb62
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/entities/error_tracking.rb2
-rw-r--r--lib/api/error_tracking/collector.rb156
-rw-r--r--lib/generators/gitlab/analytics/internal_events_generator.rb1
-rw-r--r--lib/generators/gitlab/usage_metric/templates/instrumentation_class_spec.rb.template2
-rw-r--r--lib/gitlab/middleware/compressed_json.rb4
-rw-r--r--lib/gitlab/usage_data_counters/hll_redis_counter.rb80
-rw-r--r--lib/tasks/gitlab/packages/events.rake5
-rw-r--r--lib/tasks/gitlab/usage_data.rake9
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb2
-rw-r--r--spec/frontend/ci/runner/components/runner_create_form_spec.js33
-rw-r--r--spec/frontend/ci/runner/components/runner_managers_detail_spec.js27
-rw-r--r--spec/frontend/ci/runner/components/runner_managers_table_spec.js32
-rw-r--r--spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js6
-rw-r--r--spec/graphql/mutations/achievements/delete_user_achievement_spec.rb55
-rw-r--r--spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/compressed_json_spec.rb24
-rw-r--r--spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb158
-rw-r--r--spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb8
-rw-r--r--spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb19
-rw-r--r--spec/policies/group_policy_spec.rb8
-rw-r--r--spec/requests/api/admin/migrations_spec.rb89
-rw-r--r--spec/requests/api/error_tracking/collector_spec.rb233
-rw-r--r--spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb85
-rw-r--r--spec/services/achievements/destroy_user_achievement_service_spec.rb40
-rw-r--r--spec/services/database/mark_migration_service_spec.rb71
-rw-r--r--spec/services/personal_access_tokens/last_used_service_spec.rb52
-rw-r--r--spec/support/rspec_order_todo.yml1
-rw-r--r--workhorse/go.mod2
-rw-r--r--workhorse/go.sum4
112 files changed, 1272 insertions, 1238 deletions
diff --git a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
index 3f177f87a30..c2447519268 100644
--- a/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
+++ b/.gitlab/ci/test-on-gdk/main.gitlab-ci.yml
@@ -51,9 +51,9 @@ variables:
- echo "$(docker exec gdk bash -c "getent hosts \$HOSTNAME" | awk '{print $1}') gdk.test" >> /etc/hosts
- source scripts/utils.sh
- cd qa && bundle install
+ script:
- retry_exponential test_url ${GDK_URL}/users/sign_in
- echo -e "\e[0Ksection_end:`date +%s`:launch_gdk\r\e[0K"
- script:
- echo -e "\e[0Ksection_start:`date +%s`:run_tests\r\e[0KRun E2E tests"
- QA_COMMAND="bundle exec bin/qa Test::Instance::All ${GDK_URL} -- ${RSPEC_TAGS} ${RSPEC_REPORT_OPTS}"
- echo "Running - '${QA_COMMAND}'"
@@ -87,6 +87,21 @@ cache-gems:
script:
- cd qa && bundle install
+# Take the existing GDK docker image and reconfigure it with Postgres load
+# balancing. Adding 5s lag to 1 of the replicas to validate robustness of
+# the load balancer.
+.gdk-with-load-balancer-setup:
+ before_script:
+ - !reference [".gdk-qa-base", "before_script"]
+ - |
+ docker exec gdk bash -c "
+ gdk config set postgresql.replica.enabled true &&\
+ gdk config set postgresql.replica_2.enabled true &&\
+ gdk config set load_balancing.enabled true &&\
+ gdk reconfigure &&\
+ echo 'recovery_min_apply_delay = 5s' >> postgresql-replica-2/data/postgresql.conf &&\
+ gdk restart"
+
gdk-qa-smoke:
extends:
- .gdk-qa-base
@@ -97,6 +112,19 @@ gdk-qa-smoke:
rules:
- when: always
+gdk-qa-smoke-with-load-balancer:
+ extends:
+ - .gdk-qa-base
+ - .gdk-with-load-balancer-setup
+ variables:
+ TEST_GDK_TAGS: "--tag smoke"
+ QA_RUN_TYPE: gdk-qa-smoke
+ RSPEC_TAGS: --tag smoke
+ rules:
+ - changes:
+ - ".gitlab/ci/test-on-gdk/**"
+ - "lib/gitlab/database/load_balancing/**/*"
+
# TODO: set non manual once smoke tests prove to be stable
gdk-qa-reliable:
extends:
@@ -110,6 +138,21 @@ gdk-qa-reliable:
rules:
- when: manual
+gdk-qa-reliable-with-load-balancer:
+ extends:
+ - .gdk-qa-base
+ - .gdk-with-load-balancer-setup
+ parallel: 5
+ variables:
+ QA_RUN_TYPE: gdk-qa-blocking
+ QA_KNAPSACK_REPORT_NAME: ee-instance-parallel
+ RSPEC_TAGS: --tag smoke --tag reliable
+ allow_failure: true
+ rules:
+ - changes:
+ - ".gitlab/ci/test-on-gdk/**"
+ - "lib/gitlab/database/load_balancing/**/*"
+
gdk-qa-non-blocking:
extends:
- .gdk-qa-base
diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index a0df088b088..aaef30b1e37 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -1957,7 +1957,6 @@ RSpec/ContextWording:
- 'spec/lib/gitlab/metrics/subscribers/rack_attack_spec.rb'
- 'spec/lib/gitlab/metrics_spec.rb'
- 'spec/lib/gitlab/middleware/basic_health_check_spec.rb'
- - 'spec/lib/gitlab/middleware/compressed_json_spec.rb'
- 'spec/lib/gitlab/middleware/go_spec.rb'
- 'spec/lib/gitlab/middleware/multipart_spec.rb'
- 'spec/lib/gitlab/middleware/request_context_spec.rb'
@@ -2390,7 +2389,6 @@ RSpec/ContextWording:
- 'spec/requests/api/deploy_tokens_spec.rb'
- 'spec/requests/api/deployments_spec.rb'
- 'spec/requests/api/environments_spec.rb'
- - 'spec/requests/api/error_tracking/collector_spec.rb'
- 'spec/requests/api/files_spec.rb'
- 'spec/requests/api/generic_packages_spec.rb'
- 'spec/requests/api/graphql/ci/runner_spec.rb'
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index 0c54d92764c..527d7913311 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -4043,7 +4043,6 @@ RSpec/MissingFeatureCategory:
- 'spec/lib/gitlab/metrics/web_transaction_spec.rb'
- 'spec/lib/gitlab/metrics_spec.rb'
- 'spec/lib/gitlab/middleware/basic_health_check_spec.rb'
- - 'spec/lib/gitlab/middleware/compressed_json_spec.rb'
- 'spec/lib/gitlab/middleware/handle_ip_spoof_attack_error_spec.rb'
- 'spec/lib/gitlab/middleware/handle_malformed_strings_spec.rb'
- 'spec/lib/gitlab/middleware/memory_report_spec.rb'
diff --git a/.rubocop_todo/style/class_and_module_children.yml b/.rubocop_todo/style/class_and_module_children.yml
index 81b154f906b..55df73d3633 100644
--- a/.rubocop_todo/style/class_and_module_children.yml
+++ b/.rubocop_todo/style/class_and_module_children.yml
@@ -501,7 +501,6 @@ Style/ClassAndModuleChildren:
- 'ee/lib/ee/gitlab/throttle.rb'
- 'ee/lib/gitlab/path_locks_finder.rb'
- 'lib/api/error_tracking/client_keys.rb'
- - 'lib/api/error_tracking/collector.rb'
- 'lib/api/error_tracking/project_settings.rb'
- 'lib/gitlab/background_migration/remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings.rb'
- 'lib/gitlab/ci/badge/base.rb'
diff --git a/.rubocop_todo/style/guard_clause.yml b/.rubocop_todo/style/guard_clause.yml
index 4fcefa6ef85..8613e240d9c 100644
--- a/.rubocop_todo/style/guard_clause.yml
+++ b/.rubocop_todo/style/guard_clause.yml
@@ -428,7 +428,6 @@ Style/GuardClause:
- 'ee/spec/support/ci/minutes_helpers.rb'
- 'haml_lint/linter/documentation_links.rb'
- 'lib/api/commits.rb'
- - 'lib/api/error_tracking/collector.rb'
- 'lib/api/feature_flags.rb'
- 'lib/api/helpers.rb'
- 'lib/api/helpers/packages/conan/api_helpers.rb'
diff --git a/app/assets/javascripts/ci/runner/components/runner_create_form.vue b/app/assets/javascripts/ci/runner/components/runner_create_form.vue
index 040e42fa938..1b363174d28 100644
--- a/app/assets/javascripts/ci/runner/components/runner_create_form.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_create_form.vue
@@ -4,7 +4,13 @@ import RunnerFormFields from '~/ci/runner/components/runner_form_fields.vue';
import runnerCreateMutation from '~/ci/runner/graphql/new/runner_create.mutation.graphql';
import { modelToUpdateMutationVariables } from 'ee_else_ce/ci/runner/runner_update_form_utils';
import { captureException } from '../sentry_utils';
-import { RUNNER_TYPES, DEFAULT_ACCESS_LEVEL, PROJECT_TYPE, GROUP_TYPE } from '../constants';
+import {
+ RUNNER_TYPES,
+ DEFAULT_ACCESS_LEVEL,
+ PROJECT_TYPE,
+ GROUP_TYPE,
+ I18N_CREATE_ERROR,
+} from '../constants';
export default {
name: 'RunnerCreateForm',
@@ -82,16 +88,29 @@ export default {
});
if (errors?.length) {
- this.$emit('error', new Error(errors.join(' ')));
- this.saving = false;
- } else {
- this.onSuccess(runner);
+ this.onError(new Error(errors.join(' ')), true);
+ return;
}
+
+ if (!runner?.ephemeralRegisterUrl) {
+ // runner is missing information, report issue and
+ // fail naviation to register page.
+ this.onError(new Error(I18N_CREATE_ERROR));
+ return;
+ }
+
+ this.onSuccess(runner);
} catch (error) {
+ this.onError(error);
+ }
+ },
+ onError(error, isValidationError = false) {
+ if (!isValidationError) {
captureException({ error, component: this.$options.name });
- this.$emit('error', error);
- this.saving = false;
}
+
+ this.$emit('error', error);
+ this.saving = false;
},
onSuccess(runner) {
this.$emit('saved', runner);
diff --git a/app/assets/javascripts/ci/runner/components/runner_details.vue b/app/assets/javascripts/ci/runner/components/runner_details.vue
index 51c752f0dee..8c1280cffb9 100644
--- a/app/assets/javascripts/ci/runner/components/runner_details.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_details.vue
@@ -10,6 +10,7 @@ import {
GROUP_TYPE,
PROJECT_TYPE,
RUNNER_MANAGERS_HELP_URL,
+ I18N_STATUS_NEVER_CONTACTED,
} from '../constants';
import RunnerDetail from './runner_detail.vue';
import RunnerGroups from './runner_groups.vue';
@@ -85,6 +86,7 @@ export default {
},
ACCESS_LEVEL_REF_PROTECTED,
RUNNER_MANAGERS_HELP_URL,
+ I18N_STATUS_NEVER_CONTACTED,
};
</script>
@@ -99,7 +101,7 @@ export default {
<runner-detail :label="s__('Runners|Description')" :value="runner.description" />
<runner-detail
:label="s__('Runners|Last contact')"
- :empty-value="s__('Runners|Never contacted')"
+ :empty-value="$options.I18N_STATUS_NEVER_CONTACTED"
>
<template v-if="runner.contactedAt" #value>
<time-ago :time="runner.contactedAt" />
diff --git a/app/assets/javascripts/ci/runner/components/runner_managers_detail.vue b/app/assets/javascripts/ci/runner/components/runner_managers_detail.vue
index a9df7f12955..5cc1bbef481 100644
--- a/app/assets/javascripts/ci/runner/components/runner_managers_detail.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_managers_detail.vue
@@ -1,13 +1,12 @@
<script>
-import { GlCollapse, GlButton, GlIcon, GlSkeletonLoader, GlTableLite } from '@gitlab/ui';
-import HelpPopover from '~/vue_shared/components/help_popover.vue';
+import { GlCollapse, GlButton, GlIcon, GlSkeletonLoader } from '@gitlab/ui';
import { __, s__, formatNumber } from '~/locale';
-import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { createAlert } from '~/alert';
import runnerManagersQuery from '../graphql/show/runner_managers.query.graphql';
import { I18N_FETCH_ERROR } from '../constants';
import { captureException } from '../sentry_utils';
import { tableField } from '../utils';
+import RunnerManagersTable from './runner_managers_table.vue';
export default {
name: 'RunnerManagersDetail',
@@ -16,9 +15,7 @@ export default {
GlButton,
GlIcon,
GlSkeletonLoader,
- GlTableLite,
- TimeAgo,
- HelpPopover,
+ RunnerManagersTable,
},
props: {
runner: {
@@ -108,20 +105,7 @@ export default {
<gl-collapse :visible="expanded" class="gl-mt-5">
<gl-skeleton-loader v-if="loading" />
- <gl-table-lite v-else-if="managers.length" :fields="$options.fields" :items="managers">
- <template #head(systemId)="{ label }">
- {{ label }}
- <help-popover>
- {{ s__('Runners|The unique ID for each runner that uses this configuration.') }}
- </help-popover>
- </template>
- <template #cell(contactedAt)="{ item = {} }">
- <template v-if="item.contactedAt">
- <time-ago :time="item.contactedAt" />
- </template>
- <template v-else>{{ s__('Runners|Never contacted') }}</template>
- </template>
- </gl-table-lite>
+ <runner-managers-table v-else-if="managers.length" :items="managers" />
</gl-collapse>
</div>
</template>
diff --git a/app/assets/javascripts/ci/runner/components/runner_managers_table.vue b/app/assets/javascripts/ci/runner/components/runner_managers_table.vue
index 71cf76aed46..2039d76f8f7 100644
--- a/app/assets/javascripts/ci/runner/components/runner_managers_table.vue
+++ b/app/assets/javascripts/ci/runner/components/runner_managers_table.vue
@@ -4,6 +4,7 @@ import HelpPopover from '~/vue_shared/components/help_popover.vue';
import { s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { tableField } from '../utils';
+import { I18N_STATUS_NEVER_CONTACTED } from '../constants';
export default {
name: 'RunnerManagersTable',
@@ -20,13 +21,6 @@ export default {
default: () => [],
},
},
- data() {
- return {
- skip: true,
- expanded: false,
- managers: [],
- };
- },
fields: [
tableField({ key: 'systemId', label: s__('Runners|System ID') }),
tableField({ key: 'version', label: s__('Runners|Version') }),
@@ -40,6 +34,7 @@ export default {
thClasses: ['gl-text-right'],
}),
],
+ I18N_STATUS_NEVER_CONTACTED,
};
</script>
@@ -65,7 +60,7 @@ export default {
<template v-if="item.contactedAt">
<time-ago :time="item.contactedAt" />
</template>
- <template v-else>{{ s__('Runners|Never contacted') }}</template>
+ <template v-else>{{ $options.I18N_STATUS_NEVER_CONTACTED }}</template>
</template>
</gl-table-lite>
</template>
diff --git a/app/assets/javascripts/ci/runner/constants.js b/app/assets/javascripts/ci/runner/constants.js
index 395d9ac0d8e..40841696ead 100644
--- a/app/assets/javascripts/ci/runner/constants.js
+++ b/app/assets/javascripts/ci/runner/constants.js
@@ -9,6 +9,9 @@ export const RUNNER_DETAILS_PROJECTS_PAGE_SIZE = 5;
export const RUNNER_DETAILS_JOBS_PAGE_SIZE = 30;
export const I18N_FETCH_ERROR = s__('Runners|Something went wrong while fetching runner data.');
+export const I18N_CREATE_ERROR = s__(
+ 'Runners|An error occurred while creating the runner. Please try again.',
+);
export const FILTER_CSS_CLASSES =
'gl-bg-gray-10 gl-p-5 gl-border-solid gl-border-gray-100 gl-border-0 gl-border-t-1 gl-border-b-1';
diff --git a/app/assets/javascripts/ci/runner/graphql/show/runner_managers.query.graphql b/app/assets/javascripts/ci/runner/graphql/show/runner_managers.query.graphql
index 65fb5f91b60..c4fb63f2b0d 100644
--- a/app/assets/javascripts/ci/runner/graphql/show/runner_managers.query.graphql
+++ b/app/assets/javascripts/ci/runner/graphql/show/runner_managers.query.graphql
@@ -6,6 +6,12 @@ query getRunnerManagers($runnerId: CiRunnerID!) {
nodes {
id
systemId
+ version
+ revision
+ executorName
+ architectureName
+ platformName
+ ipAddress
contactedAt
}
}
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue
index e45b88bc6d5..ecd1bfb8ebe 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue
@@ -1,5 +1,11 @@
<script>
-import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui';
+import {
+ GlLink,
+ GlTable,
+ GlDisclosureDropdownItem,
+ GlDisclosureDropdown,
+ GlButton,
+} from '@gitlab/ui';
import { last } from 'lodash';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
@@ -13,9 +19,8 @@ export default {
components: {
GlLink,
GlTable,
- GlIcon,
- GlDropdown,
- GlDropdownItem,
+ GlDisclosureDropdown,
+ GlDisclosureDropdownItem,
GlButton,
FileIcon,
TimeAgoTooltip,
@@ -136,14 +141,16 @@ export default {
</template>
<template #cell(actions)="{ item }">
- <gl-dropdown category="tertiary" right>
- <template #button-content>
- <gl-icon name="ellipsis_v" />
- </template>
- <gl-dropdown-item data-testid="delete-file" @click="$emit('delete-file', item)">
- {{ $options.i18n.deleteFile }}
- </gl-dropdown-item>
- </gl-dropdown>
+ <gl-disclosure-dropdown category="tertiary" right no-caret icon="ellipsis_v">
+ <gl-disclosure-dropdown-item
+ data-testid="delete-file"
+ @action="$emit('delete-file', item)"
+ >
+ <template #list-item>
+ {{ $options.i18n.deleteFile }}
+ </template>
+ </gl-disclosure-dropdown-item>
+ </gl-disclosure-dropdown>
</template>
<template #row-details="{ item }">
diff --git a/app/graphql/mutations/achievements/delete_user_achievement.rb b/app/graphql/mutations/achievements/delete_user_achievement.rb
new file mode 100644
index 00000000000..f1527c2981a
--- /dev/null
+++ b/app/graphql/mutations/achievements/delete_user_achievement.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Achievements
+ class DeleteUserAchievement < BaseMutation
+ graphql_name 'UserAchievementsDelete'
+
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ field :user_achievement,
+ ::Types::Achievements::UserAchievementType,
+ null: true,
+ description: 'Deleted user achievement.'
+
+ argument :user_achievement_id, ::Types::GlobalIDType[::Achievements::UserAchievement],
+ required: true,
+ description: 'Global ID of the user achievement being deleted.'
+
+ authorize :destroy_user_achievement
+
+ def resolve(args)
+ user_achievement = authorized_find!(id: args[:user_achievement_id])
+
+ result = ::Achievements::DestroyUserAchievementService.new(current_user, user_achievement).execute
+ { user_achievement: result.payload, errors: result.errors }
+ end
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id, expected_type: ::Achievements::UserAchievement)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index efc7bf89693..62ab1086ed3 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -9,6 +9,7 @@ module Types
mount_mutation Mutations::Achievements::Award, alpha: { milestone: '15.10' }
mount_mutation Mutations::Achievements::Create, alpha: { milestone: '15.8' }
mount_mutation Mutations::Achievements::Delete, alpha: { milestone: '15.11' }
+ mount_mutation Mutations::Achievements::DeleteUserAchievement, alpha: { milestone: '16.1' }
mount_mutation Mutations::Achievements::Revoke, alpha: { milestone: '15.10' }
mount_mutation Mutations::Achievements::Update, alpha: { milestone: '15.11' }
mount_mutation Mutations::Admin::SidekiqQueues::DeleteJobs
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 447251c6442..94a67f5b5c8 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -163,6 +163,10 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :award_achievement
end
+ rule { can?(:owner_access) & achievements_enabled }.policy do
+ enable :destroy_user_achievement
+ end
+
rule { ~public_group & ~has_access }.prevent :read_counts
rule { ~can_read_group_member }.policy do
diff --git a/app/services/achievements/destroy_user_achievement_service.rb b/app/services/achievements/destroy_user_achievement_service.rb
new file mode 100644
index 00000000000..3beaed646e3
--- /dev/null
+++ b/app/services/achievements/destroy_user_achievement_service.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Achievements
+ class DestroyUserAchievementService
+ attr_reader :current_user, :user_achievement
+
+ def initialize(current_user, user_achievement)
+ @current_user = current_user
+ @user_achievement = user_achievement
+ end
+
+ def execute
+ return error_no_permissions unless allowed?
+
+ user_achievement.delete
+ ServiceResponse.success(payload: user_achievement)
+ end
+
+ private
+
+ def allowed?
+ current_user&.can?(:destroy_user_achievement, user_achievement)
+ end
+
+ def error_no_permissions
+ error('You have insufficient permissions to delete this user achievement')
+ end
+
+ def error(message)
+ ServiceResponse.error(message: Array(message))
+ end
+ end
+end
diff --git a/app/services/database/mark_migration_service.rb b/app/services/database/mark_migration_service.rb
new file mode 100644
index 00000000000..aff10fa5f76
--- /dev/null
+++ b/app/services/database/mark_migration_service.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Database
+ class MarkMigrationService
+ def initialize(connection:, version:)
+ @connection = connection
+ @version = version
+ end
+
+ def execute
+ return error(reason: :not_found) unless migration.present?
+ return error(reason: :invalid) if all_versions.include?(migration.version)
+
+ if create_version(version)
+ ServiceResponse.success
+ else
+ error(reason: :invalid)
+ end
+ end
+
+ private
+
+ attr_reader :connection, :version
+
+ def migration
+ @migration ||= connection
+ .migration_context
+ .migrations
+ .find { |migration| migration.version == version }
+ end
+
+ def all_versions
+ all_executed_migrations.map(&:to_i)
+ end
+
+ def all_executed_migrations
+ sm = Arel::SelectManager.new(arel_table)
+ sm.project(arel_table[:version])
+ sm.order(arel_table[:version].asc) # rubocop: disable CodeReuse/ActiveRecord
+ connection.select_values(sm, "#{self.class} Load")
+ end
+
+ def create_version(version)
+ im = Arel::InsertManager.new
+ im.into(arel_table)
+ im.insert(arel_table[:version] => version)
+ connection.insert(im, "#{self.class} Create", :version, version)
+ end
+
+ def arel_table
+ @arel_table ||= Arel::Table.new(:schema_migrations)
+ end
+
+ def error(reason:)
+ ServiceResponse.error(message: 'error', reason: reason)
+ end
+ end
+end
diff --git a/app/services/personal_access_tokens/last_used_service.rb b/app/services/personal_access_tokens/last_used_service.rb
index 9066fd1acdf..6fc3110a70b 100644
--- a/app/services/personal_access_tokens/last_used_service.rb
+++ b/app/services/personal_access_tokens/last_used_service.rb
@@ -22,7 +22,14 @@ module PersonalAccessTokens
last_used = @personal_access_token.last_used_at
- last_used.nil? || (last_used <= 1.day.ago)
+ return true if last_used.nil?
+
+ if Feature.enabled?(:update_personal_access_token_usage_information_every_10_minutes) &&
+ last_used <= 10.minutes.ago
+ return true
+ end
+
+ last_used <= 1.day.ago
end
end
end
diff --git a/config/feature_flags/development/revert_daily_hll_events_to_weekly_aggregation.yml b/config/feature_flags/development/revert_daily_hll_events_to_weekly_aggregation.yml
deleted file mode 100644
index aca0bc96a33..00000000000
--- a/config/feature_flags/development/revert_daily_hll_events_to_weekly_aggregation.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: revert_daily_hll_events_to_weekly_aggregation
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114967
-rollout_issue_url:
-milestone: '16.0'
-type: development
-group: group::analytics instrumentation
-default_enabled: false
diff --git a/config/feature_flags/development/update_personal_access_token_usage_information_every_10_minutes.yml b/config/feature_flags/development/update_personal_access_token_usage_information_every_10_minutes.yml
new file mode 100644
index 00000000000..fbcd7bb15ae
--- /dev/null
+++ b/config/feature_flags/development/update_personal_access_token_usage_information_every_10_minutes.yml
@@ -0,0 +1,8 @@
+---
+name: update_personal_access_token_usage_information_every_10_minutes
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/123154
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414945
+milestone: '16.1'
+type: development
+group: group::authentication and authorization
+default_enabled: true
diff --git a/data/deprecations/15-6-deprecate-post-api-v4-runner.yml b/data/deprecations/15-6-deprecate-post-api-v4-runner.yml
index 17d07c0f119..786132b19b5 100644
--- a/data/deprecations/15-6-deprecate-post-api-v4-runner.yml
+++ b/data/deprecations/15-6-deprecate-post-api-v4-runner.yml
@@ -10,7 +10,7 @@
The support for registration tokens and certain runner configuration arguments in the `POST` method operation on the `/api/v4/runners` endpoint is deprecated.
This endpoint [registers](https://docs.gitlab.com/ee/api/runners.html#register-a-new-runner) a runner
with a GitLab instance at the instance, group, or project level through the API. Registration tokens, and support for certain configuration arguments,
- will be removed in GitLab 17.0. For more information, see [Migrating to the new runner registration workflow](../ci/runners/new_creation_workflow.md).
+ will start returning the HTTP `410 Gone` status code in GitLab 17.0. For more information, see [Migrating to the new runner registration workflow](../ci/runners/new_creation_workflow.md).
The configuration arguments disabled for authentication tokens are:
diff --git a/data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml b/data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml
index b3031850ca3..3a9690e0836 100644
--- a/data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml
+++ b/data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml
@@ -8,7 +8,7 @@
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383341 # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
The support for runner registration tokens is deprecated. As a consequence, the REST API endpoints to reset a registration token are also deprecated and will
- be removed in GitLab 17.0.
+ return the HTTP `410 Gone` status code in GitLab 17.0.
The deprecated endpoints are:
- `POST /runners/reset_registration_token`
diff --git a/db/post_migrate/20230328111013_re_migrate_redis_slot_keys.rb b/db/post_migrate/20230328111013_re_migrate_redis_slot_keys.rb
index 71ab958b601..17776d8e42e 100644
--- a/db/post_migrate/20230328111013_re_migrate_redis_slot_keys.rb
+++ b/db/post_migrate/20230328111013_re_migrate_redis_slot_keys.rb
@@ -3,6 +3,8 @@
class ReMigrateRedisSlotKeys < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
+ KEY_EXPIRY_LENGTH = 6.weeks
+
DAILY_EVENTS =
%w[g_edit_by_web_ide
g_edit_by_sfe
@@ -117,7 +119,7 @@ class ReMigrateRedisSlotKeys < Gitlab::Database::Migration[2.1]
end
def migrate_weekly_aggregated(event)
- weeks_back = Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH
+ weeks_back = KEY_EXPIRY_LENGTH
start_date = (Date.today - weeks_back).beginning_of_week - 1.day
end_date = Date.today.end_of_week + 1.day
@@ -136,7 +138,7 @@ class ReMigrateRedisSlotKeys < Gitlab::Database::Migration[2.1]
temp_key = new_key + "_#{Time.current.to_i}"
ttl = redis.ttl(old_key)
- ttl = ttl > 0 ? ttl : Gitlab::UsageDataCounters::HLLRedisCounter.send(:expiry, event)
+ ttl = ttl > 0 ? ttl : KEY_EXPIRY_LENGTH
redis.multi do |multi|
multi.set(temp_key, hll_blob, ex: 1.day.to_i)
diff --git a/db/post_migrate/20230608081257_delete_index_members_on_source_id_and_source_type.rb b/db/post_migrate/20230608081257_delete_index_members_on_source_id_and_source_type.rb
new file mode 100644
index 00000000000..5018d361841
--- /dev/null
+++ b/db/post_migrate/20230608081257_delete_index_members_on_source_id_and_source_type.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class DeleteIndexMembersOnSourceIdAndSourceType < Gitlab::Database::Migration[2.1]
+ INDEX_NAME = 'index_members_on_source_id_and_source_type'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name :members, name: INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :members, %i[source_id source_type], name: INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20230608081924_delete_index_unique_project_authorizations_on_project_id_user_id.rb b/db/post_migrate/20230608081924_delete_index_unique_project_authorizations_on_project_id_user_id.rb
new file mode 100644
index 00000000000..722f58b836a
--- /dev/null
+++ b/db/post_migrate/20230608081924_delete_index_unique_project_authorizations_on_project_id_user_id.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class DeleteIndexUniqueProjectAuthorizationsOnProjectIdUserId < Gitlab::Database::Migration[2.1]
+ INDEX_NAME = 'index_unique_project_authorizations_on_project_id_user_id'
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index_by_name :project_authorizations, name: INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :project_authorizations, %i[project_id user_id], name: INDEX_NAME
+ end
+end
diff --git a/db/schema_migrations/20230608081257 b/db/schema_migrations/20230608081257
new file mode 100644
index 00000000000..1e637e22e14
--- /dev/null
+++ b/db/schema_migrations/20230608081257
@@ -0,0 +1 @@
+cba57f523bb3d233ce1f7a0447ee25e37dafcd4ae3cd8a02ef052136e36b0938 \ No newline at end of file
diff --git a/db/schema_migrations/20230608081924 b/db/schema_migrations/20230608081924
new file mode 100644
index 00000000000..1db529907a1
--- /dev/null
+++ b/db/schema_migrations/20230608081924
@@ -0,0 +1 @@
+24b2ac86b56c1eb32409d921dfba52dc9f9ba6817c9aa19ae5058d46926e79ce \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 49f27281036..50c0796c2ef 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -31589,8 +31589,6 @@ CREATE INDEX index_members_on_requested_at ON members USING btree (requested_at)
CREATE INDEX index_members_on_source_and_type_and_access_level ON members USING btree (source_id, source_type, type, access_level);
-CREATE INDEX index_members_on_source_id_and_source_type ON members USING btree (source_id, source_type);
-
CREATE INDEX index_members_on_source_state_type_access_level_and_user_id ON members USING btree (source_id, source_type, state, type, access_level, user_id) WHERE ((requested_at IS NULL) AND (invite_token IS NULL));
CREATE INDEX index_members_on_user_id_and_access_level_requested_at_is_null ON members USING btree (user_id, access_level) WHERE (requested_at IS NULL);
@@ -32967,8 +32965,6 @@ CREATE UNIQUE INDEX index_unique_ci_runner_projects_on_runner_id_and_project_id
CREATE UNIQUE INDEX index_unique_issue_metrics_issue_id ON issue_metrics USING btree (issue_id);
-CREATE UNIQUE INDEX index_unique_project_authorizations_on_project_id_user_id ON project_authorizations USING btree (project_id, user_id);
-
CREATE INDEX index_unit_test_failures_failed_at ON ci_unit_test_failures USING btree (failed_at DESC);
CREATE UNIQUE INDEX index_unit_test_failures_unique_columns ON ci_unit_test_failures USING btree (unit_test_id, failed_at DESC, build_id);
diff --git a/doc/api/error_tracking.md b/doc/api/error_tracking.md
index 3515b080b12..d1ab67a93ae 100644
--- a/doc/api/error_tracking.md
+++ b/doc/api/error_tracking.md
@@ -138,13 +138,13 @@ Example response:
"id": 1,
"active": true,
"public_key": "glet_aa77551d849c083f76d0bc545ed053a3",
- "sentry_dsn": "https://glet_aa77551d849c083f76d0bc545ed053a3@gitlab.example.com/api/v4/error_tracking/collector/5"
+ "sentry_dsn": "https://glet_aa77551d849c083f76d0bc545ed053a3@example.com/errortracking/api/v1/projects/5"
},
{
"id": 3,
"active": true,
"public_key": "glet_0ff98b1d849c083f76d0bc545ed053a3",
- "sentry_dsn": "https://glet_0ff98b1d849c083f76d0bc545ed053a3@gitlab.example.com/api/v4/error_tracking/collector/5"
+ "sentry_dsn": "https://glet_aa77551d849c083f76d0bc545ed053a3@example.com/errortracking/api/v1/projects/5"
}
]
```
@@ -173,7 +173,7 @@ Example response:
"id": 3,
"active": true,
"public_key": "glet_0ff98b1d849c083f76d0bc545ed053a3",
- "sentry_dsn": "https://glet_0ff98b1d849c083f76d0bc545ed053a3@gitlab.example.com/api/v4/error_tracking/collector/5"
+ "sentry_dsn": "https://glet_aa77551d849c083f76d0bc545ed053a3@example.com/errortracking/api/v1/projects/5"
}
```
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index c6e1363b505..cb451eaab82 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -6755,6 +6755,29 @@ Input type: `UploadDeleteInput`
| <a id="mutationuploaddeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationuploaddeleteupload"></a>`upload` | [`FileUpload`](#fileupload) | Deleted upload. |
+### `Mutation.userAchievementsDelete`
+
+WARNING:
+**Introduced** in 16.1.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Input type: `UserAchievementsDeleteInput`
+
+#### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationuserachievementsdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationuserachievementsdeleteuserachievementid"></a>`userAchievementId` | [`AchievementsUserAchievementID!`](#achievementsuserachievementid) | Global ID of the user achievement being deleted. |
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="mutationuserachievementsdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
+| <a id="mutationuserachievementsdeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
+| <a id="mutationuserachievementsdeleteuserachievement"></a>`userAchievement` | [`UserAchievement`](#userachievement) | Deleted user achievement. |
+
### `Mutation.userCalloutCreate`
Input type: `UserCalloutCreateInput`
diff --git a/doc/architecture/blueprints/pods/index.md b/doc/architecture/blueprints/pods/index.md
deleted file mode 100644
index 5c15f880a54..00000000000
--- a/doc/architecture/blueprints/pods/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/index.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/index.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-admin-area.md b/doc/architecture/blueprints/pods/pods-feature-admin-area.md
deleted file mode 100644
index 0f02a4a88ba..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-admin-area.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-admin-area.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-admin-area.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-agent-for-kubernetes.md b/doc/architecture/blueprints/pods/pods-feature-agent-for-kubernetes.md
deleted file mode 100644
index f28cc447e0a..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-agent-for-kubernetes.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-agent-for-kubernetes.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-agent-for-kubernetes.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-backups.md b/doc/architecture/blueprints/pods/pods-feature-backups.md
deleted file mode 100644
index db22317cf75..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-backups.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-backups.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-backups.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-ci-runners.md b/doc/architecture/blueprints/pods/pods-feature-ci-runners.md
deleted file mode 100644
index 1985bb21884..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-ci-runners.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-ci-runners.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-ci-runners.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-container-registry.md b/doc/architecture/blueprints/pods/pods-feature-container-registry.md
deleted file mode 100644
index 9d2bbb3febe..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-container-registry.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-container-registry.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-container-registry.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-contributions-forks.md b/doc/architecture/blueprints/pods/pods-feature-contributions-forks.md
deleted file mode 100644
index 38bdef35329..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-contributions-forks.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-contributions-forks.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-contributions-forks.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-dashboard.md b/doc/architecture/blueprints/pods/pods-feature-dashboard.md
deleted file mode 100644
index 1d92b891aff..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-dashboard.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-dashboard.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-dashboard.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-data-migration.md b/doc/architecture/blueprints/pods/pods-feature-data-migration.md
deleted file mode 100644
index c06006a86dc..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-data-migration.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-data-migration.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-data-migration.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-database-sequences.md b/doc/architecture/blueprints/pods/pods-feature-database-sequences.md
deleted file mode 100644
index 9c4d6c5e290..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-database-sequences.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-database-sequences.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-database-sequences.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-git-access.md b/doc/architecture/blueprints/pods/pods-feature-git-access.md
deleted file mode 100644
index 1a0df0f9569..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-git-access.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-git-access.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-git-access.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-gitlab-pages.md b/doc/architecture/blueprints/pods/pods-feature-gitlab-pages.md
deleted file mode 100644
index 4c7f162434e..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-gitlab-pages.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-gitlab-pages.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-gitlab-pages.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-global-search.md b/doc/architecture/blueprints/pods/pods-feature-global-search.md
deleted file mode 100644
index 035e95219e4..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-global-search.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-global-search.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-global-search.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-graphql.md b/doc/architecture/blueprints/pods/pods-feature-graphql.md
deleted file mode 100644
index f0f01a2b120..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-graphql.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-graphql.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-graphql.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-organizations.md b/doc/architecture/blueprints/pods/pods-feature-organizations.md
deleted file mode 100644
index f801f739374..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-organizations.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-organizations.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-organizations.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-personal-namespaces.md b/doc/architecture/blueprints/pods/pods-feature-personal-namespaces.md
deleted file mode 100644
index 237eb5f9d64..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-personal-namespaces.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-personal-namespaces.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-personal-namespaces.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-router-endpoints-classification.md b/doc/architecture/blueprints/pods/pods-feature-router-endpoints-classification.md
deleted file mode 100644
index b9e85c29481..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-router-endpoints-classification.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-router-endpoints-classification.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-router-endpoints-classification.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-schema-changes.md b/doc/architecture/blueprints/pods/pods-feature-schema-changes.md
deleted file mode 100644
index a57f76ad9d4..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-schema-changes.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-schema-changes.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-schema-changes.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-secrets.md b/doc/architecture/blueprints/pods/pods-feature-secrets.md
deleted file mode 100644
index f33b98add21..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-secrets.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-secrets.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-secrets.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-snippets.md b/doc/architecture/blueprints/pods/pods-feature-snippets.md
deleted file mode 100644
index 42d3c401dba..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-snippets.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-snippets.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-snippets.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-template.md b/doc/architecture/blueprints/pods/pods-feature-template.md
deleted file mode 100644
index acc8e329725..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-template.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-template.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-template.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/pods-feature-uploads.md b/doc/architecture/blueprints/pods/pods-feature-uploads.md
deleted file mode 100644
index 1de4c138843..00000000000
--- a/doc/architecture/blueprints/pods/pods-feature-uploads.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/cells-feature-uploads.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/cells-feature-uploads.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/proposal-stateless-router-with-buffering-requests.md b/doc/architecture/blueprints/pods/proposal-stateless-router-with-buffering-requests.md
deleted file mode 100644
index 4c135c5dbc3..00000000000
--- a/doc/architecture/blueprints/pods/proposal-stateless-router-with-buffering-requests.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/proposal-stateless-router-with-buffering-requests.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/proposal-stateless-router-with-buffering-requests.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/pods/proposal-stateless-router-with-routes-learning.md b/doc/architecture/blueprints/pods/proposal-stateless-router-with-routes-learning.md
deleted file mode 100644
index 093d5d7acc6..00000000000
--- a/doc/architecture/blueprints/pods/proposal-stateless-router-with-routes-learning.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../cells/proposal-stateless-router-with-routes-learning.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../cells/proposal-stateless-router-with-routes-learning.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/architecture/blueprints/remote_development/index.md b/doc/architecture/blueprints/remote_development/index.md
index 1539a0878f0..16b71840f9e 100644
--- a/doc/architecture/blueprints/remote_development/index.md
+++ b/doc/architecture/blueprints/remote_development/index.md
@@ -100,7 +100,7 @@ A major goal is that each member of a development team should have the same deve
A workspace should allow access to an existing development environment from multiple machines and locations across a single or multiple teams. It should also allow a user to make use of tools or runtimes not available on their local OS or manage multiple versions of them.
-Additionally, remote development workspaces could provide a way to implement disaster recovery if we are able to leverage the capabilities of [Cells](../../../architecture/blueprints/pods/index.md).
+Additionally, remote development workspaces could provide a way to implement disaster recovery if we are able to leverage the capabilities of [Cells](../../../architecture/blueprints/cells/index.md).
### Scalability
diff --git a/doc/ci/runners/saas/macos_saas_runner.md b/doc/ci/runners/saas/macos_saas_runner.md
index 2d13d6e23e4..1c856925f10 100644
--- a/doc/ci/runners/saas/macos_saas_runner.md
+++ b/doc/ci/runners/saas/macos_saas_runner.md
@@ -111,3 +111,17 @@ Related topics:
- If the VM image does not include the specific software version you need for your job, then the job execution time will increase as the required software needs to be fetched and installed.
- At this time, it is not possible to bring your own OS image.
- The keychain for user `gitlab` is not publicly available. You must create a keychain instead.
+
+## Optimizing Homebrew
+
+By default, Homebrew checks for updates at the start of any operation. Homebrew has a
+release cycle that may be more frequent than the GitLab MacOS image release cycle. This
+difference in release cycles may cause steps that call `brew` to take extra time to complete
+while Homebrew makes updates.
+
+To reduce build time due to unintended Homebrew updates, set the `HOMEBREW_NO_AUTO_UPDATE` variable in `.gitlab-ci.yml` :
+
+```yaml
+variables:
+ HOMEBREW_NO_AUTO_UPDATE: 1
+```
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index 0c86c1ff055..358d6ec55e6 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -961,20 +961,20 @@ To open either project or group settings:
```markdown
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or group.
-1. On the left sidebar, select **Settings > CI/CD**.
+1. Select **Settings > CI/CD**.
1. Expand **General pipelines**.
```
To create a project:
```markdown
-1. On the left sidebar, at the top, select **Create new** (**{plus}**) and **New project/repository**.
+1. On the left sidebar, at the top, select **Create new...** (**{plus}**) and **New project/repository**.
```
To create a group:
```markdown
-1. On the left sidebar, at the top, select **Create new** (**{plus}**) and **New group**.
+1. On the left sidebar, at the top, select **Create new...** (**{plus}**) and **New group**.
```
To open the Admin Area:
@@ -999,6 +999,20 @@ To save the selection in some dropdown lists:
1. Select any area outside the dropdown list.
```
+To view all your projects:
+
+```markdown
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **View all your projects**.
+```
+
+To view all your groups:
+
+```markdown
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **View all your groups**.
+```
+
### Optional steps
If a step is optional, start the step with the word `Optional` followed by a period.
@@ -1029,7 +1043,7 @@ Use the phrase **Complete the fields**.
For example:
1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
-1. On the left sidebar, select **Settings > Repository**.
+1. Select **Settings > Repository**.
1. Expand **Push rules**.
1. Complete the fields.
diff --git a/doc/development/search/advanced_search_migration_styleguide.md b/doc/development/search/advanced_search_migration_styleguide.md
index 41a92c73b0e..d7c0dddee7b 100644
--- a/doc/development/search/advanced_search_migration_styleguide.md
+++ b/doc/development/search/advanced_search_migration_styleguide.md
@@ -54,7 +54,7 @@ The following migration helpers are available in `ee/app/workers/concerns/elasti
Backfills a specific field in an index. In most cases, the mapping for the field should already be added.
-Requires the `index_name` and `field_name` methods.
+Requires the `index_name` and `field_name` methods to backfill a single field.
```ruby
class MigrationName < Elastic::Migration
@@ -72,6 +72,24 @@ class MigrationName < Elastic::Migration
end
```
+Requires the `index_name` and `field_names` methods to backfill multiple fields if any field is null.
+
+```ruby
+class MigrationName < Elastic::Migration
+ include Elastic::MigrationBackfillHelper
+
+ private
+
+ def index_name
+ Issue.__elasticsearch__.index_name
+ end
+
+ def field_names
+ %w[schema_version visibility_level]
+ end
+end
+```
+
#### `Elastic::MigrationUpdateMappingsHelper`
Updates a mapping in an index by calling `put_mapping` with the mapping specified.
diff --git a/doc/development/workspace/index.md b/doc/development/workspace/index.md
deleted file mode 100644
index ca404702d72..00000000000
--- a/doc/development/workspace/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../organization/index.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../organization/index.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md
index 425b8927520..b5c4c78ac8e 100644
--- a/doc/gitlab-basics/add-file.md
+++ b/doc/gitlab-basics/add-file.md
@@ -25,7 +25,7 @@ If you are unfamiliar with the command line, use the
<!-- Original source for this list: doc/user/project/repository/web_editor.md#upload-a-file -->
<!-- For why we duplicated the info, see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111072#note_1267429478 -->
-1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
1. From the project dashboard or repository, next to the branch name, select the plus icon (**{plus}**).
1. From the dropdown list, select **Upload file**.
1. Complete the fields. To create a merge request with the uploaded file, ensure the **Start a new merge request with these changes** toggle is turned on.
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index 088ef50c005..8a3cd720079 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -248,7 +248,8 @@ in this section whenever you need to update GitLab.
To determine the version of GitLab you're currently running:
-1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
1. On the left sidebar, select **Overview > Dashboard**.
1. Find the version under the **Components** table.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 4f4bf9dfefe..1fd772c06da 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -329,7 +329,8 @@ To prepare the new server:
```
1. Disable periodic background jobs:
- 1. On the top bar, select **Main menu > Admin**.
+ 1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+ 1. Select **Admin Area**.
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. Under the Sidekiq dashboard, select **Cron** tab and then
**Disable All**.
@@ -409,7 +410,8 @@ To prepare the new server:
1. [Restore the GitLab backup](#restore-gitlab).
1. Verify that the Redis database restored correctly:
- 1. On the top bar, select **Main menu > Admin**.
+ 1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+ 1. Select **Admin Area**.
1. On the left sidebar, select **Monitoring > Background Jobs**.
1. Under the Sidekiq dashboard, verify that the numbers
match with what was shown on the old server.
diff --git a/doc/security/hardening_application_recommendations.md b/doc/security/hardening_application_recommendations.md
index 6ad72569b88..9d4d2d562fd 100644
--- a/doc/security/hardening_application_recommendations.md
+++ b/doc/security/hardening_application_recommendations.md
@@ -14,8 +14,9 @@ web interface.
## System hooks
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **System hooks**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **System Hooks**.
In a typical hardened environment, internal information is not transmitted or stored
outside of the system. For an offline environment system, this is
@@ -32,8 +33,9 @@ encouraged for communications through system hooks.
## Push rules
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Push rules**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Push Rules**.
Ensure that the following items are selected:
@@ -46,8 +48,9 @@ The adjustments help limit pushes to established and authorized users.
## Deploy keys
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Deploy keys**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Deploy Keys**.
Public deploy keys at are used to give read or read/write access to
**all** projects on the instance, and are intended for remote automation to access
@@ -58,9 +61,9 @@ the documentation on [deploy keys](../user/project/deploy_keys/index.md) and
## General
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings**.
-1. Select **General**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Settings > General**.
Hardening adjustments can be made in 4 sections.
@@ -177,9 +180,9 @@ For more detailed information, see
## Integrations
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings**.
-1. Select **Integrations**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Settings > Integrations**.
In general, as long as administrators control and monitor usage, integrations
are fine in a hardened environment. Be cautious about integrations that allow
@@ -189,9 +192,9 @@ process or authenticated user.
## Metrics and profiling
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings**.
-1. Under **Integrations**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Settings > Metrics and profiling**.
The main focus for hardening is **Usage statistics**:
@@ -207,9 +210,9 @@ help you make an informed decision, see
## Network
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings**.
-1. Under **Network**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Settings > Network**.
For any setting that enables rate limiting, make sure it is selected. Default values
should be fine. Additionally there are numerous settings that enable access, and all
diff --git a/doc/security/password_length_limits.md b/doc/security/password_length_limits.md
index 0211a326e0a..d8e9728f455 100644
--- a/doc/security/password_length_limits.md
+++ b/doc/security/password_length_limits.md
@@ -24,8 +24,10 @@ The user password length is set to a minimum of 8 characters by default.
To change the minimum password length using GitLab UI:
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings > General** and expand **Sign-up restrictions**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Sign-up restrictions**.
1. Enter a **Minimum password length** value greater than or equal to `8`.
1. Select **Save changes**.
diff --git a/doc/security/reset_user_password.md b/doc/security/reset_user_password.md
index f8ba3953379..7adb6256259 100644
--- a/doc/security/reset_user_password.md
+++ b/doc/security/reset_user_password.md
@@ -20,9 +20,10 @@ The user's new password must meet all [password requirements](../user/profile/us
To reset a user's password in the UI:
-1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
1. On the left sidebar, select **Overview > Users**.
-1. For the user whose password you want to update, select **Edit** (**{pencil-square}**).
+1. For the user whose password you want to update, select **Edit**.
1. In the **Password** area, type a password and password confirmation.
1. Select **Save changes**.
diff --git a/doc/security/ssh_keys_restrictions.md b/doc/security/ssh_keys_restrictions.md
index f15d71461d4..1e4a4226138 100644
--- a/doc/security/ssh_keys_restrictions.md
+++ b/doc/security/ssh_keys_restrictions.md
@@ -20,8 +20,9 @@ limit the allowed SSH key algorithms.
GitLab allows you to restrict the allowed SSH key technology as well as specify
the minimum key length for each technology:
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`).
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Settings > General** .
1. Expand the **Visibility and access controls** section:
![SSH keys restriction Admin Area settings](img/ssh_keys_restrictions_settings.png)
diff --git a/doc/security/two_factor_authentication.md b/doc/security/two_factor_authentication.md
index 9ac799610bb..25937993c16 100644
--- a/doc/security/two_factor_authentication.md
+++ b/doc/security/two_factor_authentication.md
@@ -29,8 +29,9 @@ cannot leave the 2FA configuration area at `/-/profile/two_factor_auth`.
To enable 2FA for all users:
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`).
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Settings > General**.
1. Expand the **Sign-in restrictions** section, where you can configure both.
If you want 2FA enforcement to take effect during the next sign-in attempt,
@@ -55,8 +56,8 @@ Prerequisites:
To enforce 2FA only for certain groups:
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Settings > General**.
1. Expand **Permissions and group features**.
1. Select **All users in this group must set up two-factor authentication**.
1. Select **Save changes**.
diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md
index ffe1bdd4d05..e8215616e1b 100644
--- a/doc/security/unlock_user.md
+++ b/doc/security/unlock_user.md
@@ -25,7 +25,8 @@ If 2FA is enabled, users are locked after five failed sign-in attempts within 10
## Unlock a user from the Admin Area
-1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
1. On the left sidebar, select **Overview > Users**.
1. Use the search bar to find the locked user.
1. From the **User administration** dropdown list, select **Unlock**.
diff --git a/doc/security/user_email_confirmation.md b/doc/security/user_email_confirmation.md
index 962f6f0fd61..f2c0052c2b8 100644
--- a/doc/security/user_email_confirmation.md
+++ b/doc/security/user_email_confirmation.md
@@ -11,9 +11,10 @@ GitLab can be configured to require confirmation of a user's email address when
the user signs up. When this setting is enabled, the user is unable to sign in until
they confirm their email address.
-1. On the top bar, select **Main menu > Admin**.
-1. On the left sidebar, select **Settings > General** (`/admin/application_settings/general`).
-1. Expand the **Sign-up restrictions** section and look for the **Email confirmation settings** options.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Sign-up restrictions** and look for the **Email confirmation settings** options.
## Confirmation token expiry
diff --git a/doc/security/user_file_uploads.md b/doc/security/user_file_uploads.md
index 498245c4efb..5895eda8cdd 100644
--- a/doc/security/user_file_uploads.md
+++ b/doc/security/user_file_uploads.md
@@ -47,8 +47,8 @@ Prerequisite:
To configure authentication settings for all media files:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > General**.
1. Expand **Visibility, project features, permissions**.
1. Scroll to **Project visibility** and select **Require authentication to view media files**.
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index 774bfa106f6..13b34d28614 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -50,7 +50,8 @@ To prevent exploitation of insecure internal web services, all webhook and integ
To allow access to these addresses:
-1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
1. On the left sidebar, select **Settings > Network**.
1. Expand **Outbound requests**.
1. Select the **Allow requests to the local network from webhooks and integrations** checkbox.
@@ -63,7 +64,8 @@ Prerequisite:
[System hooks](../administration/system_hooks.md) can make requests to the local network by default. To prevent system hook requests to the local network:
-1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
1. On the left sidebar, select **Settings > Network**.
1. Expand **Outbound requests**.
1. Clear the **Allow requests to the local network from system hooks** checkbox.
@@ -78,7 +80,8 @@ Prerequisite:
To filter requests by blocking many requests:
-1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
1. On the left sidebar, select **Settings > Network**.
1. Expand **Outbound requests**.
1. Select the **Block all requests, except for IP addresses, IP ranges, and domain names defined in the allowlist** checkbox.
@@ -103,7 +106,8 @@ Prerequisite:
To allow outbound requests to certain IP addresses and domains:
-1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, expand the top-most chevron (**{chevron-down}**).
+1. Select **Admin Area**.
1. On the left sidebar, select **Settings > Network**.
1. Expand **Outbound requests**.
1. In **Local IP addresses and domain names that hooks and integrations can access**, enter your IP addresses and domains.
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 0483dc4a3bf..287c81f6801 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -420,7 +420,7 @@ While the above approach is recommended for most instances, Sidekiq can also be
The support for registration tokens and certain runner configuration arguments in the `POST` method operation on the `/api/v4/runners` endpoint is deprecated.
This endpoint [registers](https://docs.gitlab.com/ee/api/runners.html#register-a-new-runner) a runner
with a GitLab instance at the instance, group, or project level through the API. Registration tokens, and support for certain configuration arguments,
-will be removed in GitLab 17.0. For more information, see [Migrating to the new runner registration workflow](../ci/runners/new_creation_workflow.md).
+will start returning the HTTP `410 Gone` status code in GitLab 17.0. For more information, see [Migrating to the new runner registration workflow](../ci/runners/new_creation_workflow.md).
The configuration arguments disabled for authentication tokens are:
@@ -576,7 +576,7 @@ we'll be introducing support in [this epic](https://gitlab.com/groups/gitlab-org
</div>
The support for runner registration tokens is deprecated. As a consequence, the REST API endpoints to reset a registration token are also deprecated and will
-be removed in GitLab 17.0.
+return the HTTP `410 Gone` status code in GitLab 17.0.
The deprecated endpoints are:
- `POST /runners/reset_registration_token`
diff --git a/doc/user/analytics/analytics_dashboards.md b/doc/user/analytics/analytics_dashboards.md
new file mode 100644
index 00000000000..f8f9059f99c
--- /dev/null
+++ b/doc/user/analytics/analytics_dashboards.md
@@ -0,0 +1,174 @@
+---
+stage: Analyze
+group: Product Analytics
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Analytics dashboards (Experiment) **(ULTIMATE)**
+
+> Introduced in GitLab 15.9 as an [Experiment](../../policy/experiment-beta-support.md#experiment) feature [with a flag](../../administration/feature_flags.md) named `combined_analytics_dashboards`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `combined_analytics_dashboards`.
+On GitLab.com, this feature is not available.
+This feature is not ready for production use.
+
+## Dashboards
+
+Each project can have an unlimited number of dashboards, only limited by the instances [repository size limits](../project/repository/reducing_the_repo_size_using_git.md#storage-limits).
+These dashboards are defined using the GitLab YAML schema, and stored in the `.gitlab/analytics/dashboards/` directory of a project repository.
+The dashboard file name and containing directory should be the same, for example `my_dashboard/my_dashboard.yaml`. For more information see [defining a dashboard](#define-a-dashboard).
+Each dashboard can reference one or more [visualizations](#define-a-chart-visualization), which are shared across dashboards.
+
+Project maintainers can enforce approval rules on dashboard changes using features such as [code owners](../project/codeowners/index.md) and [approval rules](../project/merge_requests/approvals/rules.md).
+Your dashboard files are versioned in source control with the rest of a project's code.
+
+### Data sources
+
+A data source is a connection to a database or collection of data which can be used by your dashboard
+filters and visualizations to query and retrieve results.
+
+The following data sources are configured for analytics dashboards:
+
+- [Product analytics](../product_analytics/index.md)
+
+### View project dashboards
+
+To view a list of dashboards for a project:
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Analytics > Dashboards**.
+1. From the list of available dashboards, select the dashboard you want to view.
+
+### Change the location of group dashboards
+
+NOTE:
+This feature will be connected to group-level dashboards as part of [issue #411572](https://gitlab.com/gitlab-org/gitlab/-/issues/411572).
+
+To change the location of a group's dashboards:
+
+1. On the top bar, select **Main menu > Projects** and find the project you want to store your dashboard files in.
+ The project must belong to the group for which you create the dashboards.
+1. On the top bar, select **Main menu > Groups** and find your group.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Analytics**.
+1. In the **Analytics Dashboards** section, select your dashboard files project.
+1. Select **Save changes**.
+
+### Change the location of project dashboards
+
+Dashboards are usually defined in the project where the analytics data is being retrieved from.
+However, you can also have a separate project for dashboards.
+This is recommended if you want to enforce specific access rules to the dashboard definitions or share dashboards across multiple projects.
+
+NOTE:
+You can share dashboards only between projects that are located in the same group.
+
+To change the location of project dashboards:
+
+1. On the top bar, select **Main menu > Projects** and find or create the project to store your dashboard files.
+1. On the top bar, select **Main menu > Projects** and find the analytics project.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Analytics**.
+1. In the **Analytics Dashboards** section, select your dashboard files project.
+1. Select **Save changes**.
+
+### Define a dashboard
+
+To define a dashboard:
+
+1. In `.gitlab/analytics/dashboards/`, create a directory named like the dashboard.
+
+ Each dashboard should have its own directory.
+1. In the new directory, create a `.yaml` file with the same name as the directory, for example `.gitlab/analytics/dashboards/my_dashboard/my_dashboard.yaml`.
+
+ This file contains the dashboard definition. It must conform to the JSON schema defined in `ee/app/validators/json_schemas/analytics_dashboard.json`.
+1. Optional. To create new visualizations to add to your dashboard see [defining a chart visualization](#define-a-chart-visualization).
+
+For [example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/analytics/product_analytics/dashboards/audience.yaml), if you want to create three dashboards (Conversion funnels, Demographic breakdown, and North star metrics)
+and one visualization (line chart) that applies to all dashboards, the file structure would be:
+
+```plaintext
+.gitlab/analytics/dashboards
+├── conversion_funnels
+│ └── conversion_funnels.yaml
+├── demographic_breakdown
+│ └── demographic_breakdown.yaml
+├── north_star_metrics
+| └── north_star_metrics.yaml
+├── visualizations
+│ └── example_line_chart.yaml
+```
+
+### Define a chart visualization
+
+You can define different charts, and add visualization options to some of them:
+
+- Line chart, with the options listed in the [ECharts documentation](https://echarts.apache.org/en/option.html).
+- Column chart, with the options listed in the [ECharts documentation](https://echarts.apache.org/en/option.html).
+- Data table, with the only option to render `links` (array of objects, each with `text` and `href` properties to specify the dimensions to be used in links). See [example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/validators/json_schemas/analytics_visualization.json?ref_type=heads#L112)).
+- Single stat, with the only option to set `decimalPlaces` (number, default value is 0).
+
+To define a chart for your dashboards:
+
+1. In the `.gitlab/analytics/dashboards/visualizations/` directory, create a `.yaml` file.
+ The filename should be descriptive of the visualization it defines.
+1. In the `.yaml` file, define the visualization configuration, according to the schema in
+ `ee/app/validators/json_schemas/analytics_visualization.json`.
+
+For [example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/gitlab/analytics/product_analytics/visualizations/events_over_time.yaml), to create a line chart that illustrates event count over time, in the `visualizations` folder
+create a `line_chart.yaml` file with the following required fields:
+
+- version
+- type
+- data
+- options
+
+## Dashboards designer
+
+> Introduced in GitLab 16.1 [with a flag](../../administration/feature_flags.md) named `combined_analytics_dashboards_editor`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `combined_analytics_dashboards_editor`.
+On GitLab.com, this feature is not available.
+This feature is not ready for production use.
+
+NOTE:
+This feature does not work in conjunction with the `product_analytics_snowplow_support` feature flag.
+
+You can use the dashboards designer to:
+
+- Create custom dashboards
+- Rename custom dashboards
+- Add visualizations to new and existing custom dashboards
+- Resize or move panels within custom dashboards
+
+You cannot edit the built-in dashboards labeled as `By GitLab`.
+To edit these dashboards you should create a new custom dashboard which uses the same visualizations.
+
+### Create a custom dashboard
+
+To create a custom dashboard:
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Analytics > Dashboards**.
+1. Select **New dashboard**.
+1. In the **New dashboard** input, enter the name of the dashboard.
+1. From the **Add visualizations** list on the right, select the visualizations to add to the dashboard.
+1. Optional. Drag or resize the selected panel how you prefer.
+1. Select **Save**.
+
+### Edit a custom dashboard
+
+You can edit your custom dashboard's title and add or resize visualizations within the dashboard designer.
+
+To edit an existing custom dashboard:
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Analytics > Dashboards**.
+1. From the list of available dashboards, select a custom dashboard (one without the `By GitLab` label) you want to edit.
+1. Select **Edit**.
+1. Optional. Change the title of the dashboard.
+1. Optional. From the **Add visualizations** list on the right, select other visualizations to add to the dashboard.
+1. Optional. In the dashboard, select a panel and drag or resize it how you prefer.
+1. Select **Save**.
diff --git a/doc/user/analytics/index.md b/doc/user/analytics/index.md
index 44af9dc9dea..ee03294d1ed 100644
--- a/doc/user/analytics/index.md
+++ b/doc/user/analytics/index.md
@@ -33,6 +33,8 @@ GitLab provides several analytics features at the group level. Some of these fea
You can use GitLab to review analytics at the project level. Some of these features require you to use a higher tier than GitLab Free.
+- [Analytics dashboards](analytics_dashboards.md), enabled with the `combined_analytics_dashboards_editor`
+ [feature flag](../../development/feature_flags/index.md#enabling-a-feature-flag-locally-in-development)
- [Application Security](../application_security/security_dashboard/index.md)
- [CI/CD & DORA](ci_cd_analytics.md)
- [Code Review](code_review_analytics.md)
diff --git a/doc/user/packages/infrastructure_registry/index.md b/doc/user/packages/infrastructure_registry/index.md
deleted file mode 100644
index 8c1e8a2ad8a..00000000000
--- a/doc/user/packages/infrastructure_registry/index.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-redirect_to: '../terraform_module_registry/index.md'
-remove_date: '2023-06-13'
----
-
-This document was moved to [another location](../terraform_module_registry/index.md).
-
-<!-- This redirect file can be deleted after <2023-06-13>. -->
-<!-- Redirects that point to other docs in the same project expire in three months. -->
-<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
diff --git a/doc/user/product_analytics/index.md b/doc/user/product_analytics/index.md
index 75d1ba344bf..9d9107d764a 100644
--- a/doc/user/product_analytics/index.md
+++ b/doc/user/product_analytics/index.md
@@ -83,139 +83,38 @@ Prerequisite:
1. Select **Enable product analytics** and enter the configuration values.
1. Select **Save changes**.
-## Product analytics dashboards
-
-> - Introduced in GitLab 15.5 behind the [feature flag](../../administration/feature_flags.md) named `product_analytics_internal_preview`. Disabled by default.
-> - `product_analytics_internal_preview` replaced with `product_analytics_dashboards` in GitLab 15.11.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `product_analytics_dashboards`.
-On GitLab.com, this feature is not available.
-This feature is not ready for production use.
-
-Each project can have an unlimited number of dashboards.
-These dashboards are defined using the GitLab YAML schema, and stored in the `.gitlab/analytics/dashboards/` directory of a project repository.
-The name of the file is the name of the dashboard.
-Each dashboard can contain one or more visualizations (charts), which are shared across dashboards.
-
-Project maintainers can enforce approval rules on dashboard changes using features such as code owners and approval rules.
-Dashboards are versioned in source control with the rest of a project's code.
-
-### View project dashboards
+### Project-level settings
-> Introduced in GitLab 15.9 behind the [feature flag](../../administration/feature_flags.md) named `combined_analytics_dashboards`. Disabled by default.
+You can override the instance-level settings defined by the administrator on a per-project basis. This allows you to
+have a different configured product analytics instance for your project.
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `combined_analytics_dashboards`.
-On GitLab.com, this feature is not available.
-This feature is not ready for production use.
+Prerequisites:
-To view a list of product analytics dashboards for a project:
+- Product analytics must be enabled at the instance-level.
+- You must have at least the Maintainer role for the project or group the project belongs to.
1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Dashboards**.
-1. From the list of available dashboards, select the dashboard you want to view.
-
-### Define a dashboard
-
-To define a dashboard:
-
-1. In `.gitlab/analytics/dashboards/`, create a directory named like the dashboard.
-
- Each dashboard should have its own directory.
-1. In the new directory, create a `.yaml` file with the same name as the directory.
-
- This file contains the dashboard definition. It must conform to the JSON schema defined in `ee/app/validators/json_schemas/product_analytics_dashboard.json`.
-1. In the `.gitlab/analytics/dashboards/visualizations/` directory, create a `.yaml` file.
-
- This file defines the visualization type for the dashboard. It must conform to the schema in
- `ee/app/validators/json_schemas/product_analytics_visualization.json`.
-
-For example, if you want to create three dashboards (Conversion funnels, Demographic breakdown, and North star metrics)
-and one visualization (line chart) that applies to all dashboards, the file structure would be:
-
-```plaintext
-.gitlab/analytics/dashboards
-├── conversion_funnels
-│ └── conversion_funnels.yaml
-├── demographic_breakdown
-│ └── demographic_breakdown.yaml
-├── north_star_metrics
-| └── north_star_metrics.yaml
-├── visualizations
-│ └── example_line_chart.yaml
-```
-
-### Define a chart visualization
-
-You can define different charts, and add visualization options to some of them:
-
-- Line chart, with the options listed in the [ECharts documentation](https://echarts.apache.org/en/option.html).
-- Column chart, with the options listed in the [ECharts documentation](https://echarts.apache.org/en/option.html).
-- Data table, with the only option to render `links` (array of objects, each with `text` and `href` properties to specify the dimensions to be used in links). See [example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/validators/json_schemas/analytics_visualization.json?ref_type=heads#L112)).
-- Single stat, with the only option to set `decimalPlaces` (number, default value is 0).
-
-To define a chart for your dashboards:
-
-1. In the `.gitlab/product_analytics/dashboards/visualizations/` directory, create a `.yaml` file.
-The filename should be descriptive of the visualization it defines.
-1. In the `.yaml` file, define the visualization options, according to the schema in
-`ee/app/validators/json_schemas/analytics_visualization.json`.
-
-For [example](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/assets/javascripts/analytics/analytics_dashboards/gl_dashboards/product_analytics/visualizations/events_over_time.json), to create a line chart that illustrates event count over time, in the `visualizations` folder
-create a `line_chart.yaml` file with the following required fields:
-
-- version
-- title
-- type
-- data
-- options
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Product Analytics**.
+1. In the **Connect to your instance** section, enter the configuration values.
+1. Select **Save changes**.
-## Dashboards editor
+## Product analytics dashboards
-> Introduced in GitLab 16.1 [with a flag](../../administration/feature_flags.md) named `combined_analytics_dashboards_editor`. Disabled by default.
+> - Introduced in GitLab 15.5 behind the [feature flag](../../administration/feature_flags.md) named `product_analytics_internal_preview`. Disabled by default.
+> - `product_analytics_internal_preview` replaced with `product_analytics_dashboards` in GitLab 15.11.
FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `combined_analytics_dashboards_editor`.
+On self-managed GitLab, by default this feature is not available. To make it available per project or for your entire instance, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `product_analytics_dashboards`.
On GitLab.com, this feature is not available.
This feature is not ready for production use.
-NOTE:
-This feature does not work in conjunction with the `product_analytics_snowplow_support` feature flag.
-
-You can use the dashboards editor to:
-
-- Create dashboards
-- Rename dashboards
-- Add visualizations to new and existing dashboards
-- Resize or move panels within dashboards
-
-### Create a dashboard
-
-To create a dashboard:
+Product analytics dashboards are a subset of dashboards under [Analytics dashboards](../analytics/analytics_dashboards.md).
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Dashboards**.
-1. Select **New dashboard**.
-1. In the **New dashboard** input, enter the name of the dashboard.
-1. From the **Add visualizations** list on the right, select the visualizations to add to the dashboard.
-1. Optional. Drag or resize the selected visualizations how you prefer.
-1. Select **Save**.
-
-### Edit a dashboard
-
-You can rename your created dashboards and add or resize visualizations within them.
-
-To edit an existing dashboard:
-
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Analytics > Dashboards**.
-1. From the list of available dashboards, select the dashboard you want to edit.
-1. Select **Edit**.
-1. Optional. Change the name of the dashboard.
-1. Optional. From the **Add visualizations** list on the right, select other visualizations to add to the dashboard.
-1. Optional. In the dashboard, select a visualization and drag or resize it how you prefer.
-1. Select **Save**.
+Specifically product analytics dashboards and visualizations make use of the `cube_analytics` data type.
+The `cube_analytics` data type connects to the Cube instance defined when [product analytics was enabled](#enable-product-analytics).
+All filters and queries are sent to the Cube instance and the returned data is processed by the
+product analytics data source to be rendered by the appropriate visualizations.
## Funnel analysis
diff --git a/doc/user/profile/achievements.md b/doc/user/profile/achievements.md
index 1313c714dd0..0e01f6e79bd 100644
--- a/doc/user/profile/achievements.md
+++ b/doc/user/profile/achievements.md
@@ -202,6 +202,36 @@ mutation {
}
```
+## Delete an awarded achievement
+
+If you awarded an achievement to a user by mistake, you can delete it.
+
+Prerequisites:
+
+- You must have the Owner role for the namespace.
+
+To delete an awarded achievement, call the [`userAchievementsDelete` GraphQL mutation](../../api/graphql/reference/index.md#mutationuserachievementsdelete).
+
+```graphql
+mutation {
+ userAchievementsDelete(input: {
+ userAchievementId: "gid://gitlab/Achievements::UserAchievement/<user achievement id>" }) {
+ userAchievement {
+ id
+ achievement {
+ id
+ name
+ }
+ user {
+ id
+ username
+ }
+ }
+ errors
+ }
+}
+```
+
## Delete an achievement
If you consider you no longer need an achievement, you can delete it.
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index e59d7313281..bda92ce8388 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -86,7 +86,12 @@ At any time, you can revoke a personal access token.
## View the last time a token was used
-Token usage information is updated every 24 hours. GitLab considers a token used when the token is used to:
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/414945) in GitLab 16.1 [with a flag](../../administration/feature_flags.md) named `update_personal_access_token_usage_information_every_10_minutes`. Enabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `update_personal_access_token_usage_information_every_10_minutes`. On GitLab.com, this feature is available.
+
+Token usage information is updated every 10 minutes. GitLab considers a token used when the token is used to:
- Authenticate with the [REST](../../api/rest/index.md) or [GraphQL](../../api/graphql/index.md) APIs.
- Perform a Git operation.
diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md
index 26708dece50..be47d6f18bd 100644
--- a/doc/user/project/badges.md
+++ b/doc/user/project/badges.md
@@ -130,8 +130,8 @@ If you find that you have to add the same badges to several projects, you may wa
To add a new badge to a project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > General**.
1. Expand **Badges**.
1. Under **Link**, enter the URL that the badges should point to.
1. Under **Badge image URL**, enter the URL of the image that should be displayed.
@@ -151,8 +151,8 @@ A common project badge presents the GitLab CI pipeline status.
To add this badge to a project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > General**.
1. Expand **Badges**.
1. Under **Name**, enter _Pipeline Status_.
1. Under **Link**, enter the following URL:
@@ -180,8 +180,8 @@ If you need individual badges for each project, either:
To add a new badge to a group:
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Settings > General**.
1. Expand **Badges**.
1. Under "Link", enter the URL that the badges should point to and under
"Badge image URL" the URL of the image that should be displayed.
@@ -202,8 +202,8 @@ Badges associated with a group can be edited or deleted only at the [group level
You can view the exact link for your badges.
Then you can use the link to embed the badge in your HTML or Markdown pages.
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Settings > CI/CD**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Settings > CI/CD**.
1. Expand **General pipelines**.
1. In the **Pipeline status**, **Coverage report**, or **Latest release** sections, view the URLs for the images.
@@ -269,8 +269,9 @@ https://gitlab.example.com/<project_path>/-/raw/<default_branch>/my-image.svg
To add a new badge with a custom image to a group or project:
-1. On the top bar, select **Main menu** and find your group or project.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project or
+ group.
+1. Select **Settings > General**.
1. Expand **Badges**.
1. Under **Name**, enter the name for the badge.
1. Under **Link**, enter the URL that the badge should point to.
diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md
index 551888d1875..11e538964a2 100644
--- a/doc/user/project/description_templates.md
+++ b/doc/user/project/description_templates.md
@@ -32,8 +32,8 @@ directory in your repository.
To create an issue description template:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Repository**.
1. Next to the default branch, select **{plus}**.
1. Select **New file**.
1. Next to the default branch, in the **File name** text box, enter `.gitlab/issue_templates/mytemplate.md`,
@@ -52,8 +52,8 @@ that depend on the contents of commit messages and branch names.
To create a merge request description template for a project:
-1. On the top bar, select **Main menu > Projects** and find your project.
-1. On the left sidebar, select **Repository**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+1. Select **Code > Repository**.
1. Next to the default branch, select **{plus}**.
1. Select **New file**.
1. Next to the default branch, in the **File name** text box, enter `.gitlab/merge_request_templates/mytemplate.md`,
@@ -124,8 +124,8 @@ As a result, you can use the same templates in issues and merge requests in all
To re-use templates [you've created](../project/description_templates.md#create-an-issue-template):
-1. On the top bar, select **Main menu > Groups** and find your group.
-1. On the left sidebar, select **Settings > General**.
+1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your group.
+1. Select **Settings > General**.
1. Expand **Templates**.
1. From the dropdown list, select your template project as the template repository at group level.
1. Select **Save changes**.
@@ -155,8 +155,8 @@ To set a default description template for merge requests, either:
This [doesn't overwrite](#priority-of-default-description-templates) the default template if one has been set in the project settings.
- Users on GitLab Premium and Ultimate: set the default template in project settings:
- 1. On the top bar, select **Main menu > Projects** and find your project.
- 1. On the left sidebar, select **Settings > Merge requests**.
+ 1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+ 1. Select **Settings > Merge requests**.
1. In the **Default description template for merge requests** section, fill in the text area.
1. Select **Save changes**.
@@ -167,8 +167,8 @@ To set a default description template for issues, either:
This [doesn't overwrite](#priority-of-default-description-templates) the default template if one has been set in the project settings.
- Users on GitLab Premium and Ultimate: set the default template in project settings:
- 1. On the top bar, select **Main menu > Projects** and find your project.
- 1. On the left sidebar, select **Settings > General**.
+ 1. On the left sidebar, at the top, select **Search GitLab** (**{search}**) to find your project.
+ 1. Select **Settings > General**.
1. Expand **Default description template for issues**.
1. Fill in the text area.
1. Select **Save changes**.
diff --git a/lib/api/admin/migrations.rb b/lib/api/admin/migrations.rb
new file mode 100644
index 00000000000..d4dbdbbb021
--- /dev/null
+++ b/lib/api/admin/migrations.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module API
+ module Admin
+ class Migrations < ::API::Base
+ feature_category :database
+ urgency :low
+
+ before do
+ authenticated_as_admin!
+ end
+
+ namespace 'admin' do
+ resources 'migrations/:timestamp/mark' do
+ desc 'Mark the migration as successfully executed' do
+ success [
+ { code: 201, message: '201 Created' }
+ ]
+ failure [
+ { code: 401, message: '401 Unauthorized' },
+ { code: 403, message: '403 Forbidden' },
+ { code: 404, message: '404 Not found' },
+ { code: 422, message: 'You can mark only pending migrations' }
+ ]
+ tags %w[migrations]
+ end
+ params do
+ optional :database,
+ type: String,
+ values: Gitlab::Database.all_database_names,
+ desc: 'The name of the database',
+ default: 'main'
+ requires :timestamp,
+ type: Integer,
+ desc: 'The migration version timestamp'
+ end
+ post do
+ response = Database::MarkMigrationService.new(
+ connection: base_model.connection,
+ version: params[:timestamp]
+ ).execute
+
+ if response.success?
+ created!
+ elsif response.reason == :not_found
+ not_found!
+ else
+ render_api_error!('You can mark only pending migrations', 422)
+ end
+ end
+ end
+ end
+
+ helpers do
+ def base_model
+ database = params[:database] || Gitlab::Database::MAIN_DATABASE_NAME
+ @base_model ||= Gitlab::Database.database_base_models[database]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 60a12ee7145..55bb549e5bc 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -182,6 +182,7 @@ module API
mount ::API::Admin::BatchedBackgroundMigrations
mount ::API::Admin::Ci::Variables
mount ::API::Admin::InstanceClusters
+ mount ::API::Admin::Migrations
mount ::API::Admin::PlanLimits
mount ::API::AlertManagementAlerts
mount ::API::Appearance
@@ -327,7 +328,6 @@ module API
mount ::API::Ci::PipelineSchedules
mount ::API::Ci::SecureFiles
mount ::API::Discussions
- mount ::API::ErrorTracking::Collector
mount ::API::GroupBoards
mount ::API::GroupLabels
mount ::API::GroupMilestones
diff --git a/lib/api/entities/error_tracking.rb b/lib/api/entities/error_tracking.rb
index 5e3b983c58c..180293a444d 100644
--- a/lib/api/entities/error_tracking.rb
+++ b/lib/api/entities/error_tracking.rb
@@ -21,7 +21,7 @@ module API
expose :id, documentation: { type: 'integer', example: 1 }
expose :active, documentation: { type: 'boolean' }
expose :public_key, documentation: { type: 'string', example: 'glet_aa77551d849c083f76d0bc545ed053a3' }
- expose :sentry_dsn, documentation: { type: 'string', example: 'https://glet_aa77551d849c083f76d0bc545ed053a3@gitlab.example.com/api/v4/error_tracking/collector/5' }
+ expose :sentry_dsn, documentation: { type: 'string', example: 'https://glet_aa77551d849c083f76d0bc545ed053a3@example.com/errortracking/api/v1/projects/5' }
end
end
end
diff --git a/lib/api/error_tracking/collector.rb b/lib/api/error_tracking/collector.rb
deleted file mode 100644
index e10125e02c6..00000000000
--- a/lib/api/error_tracking/collector.rb
+++ /dev/null
@@ -1,156 +0,0 @@
-# frozen_string_literal: true
-
-module API
- # This API is responsible for collecting error tracking information
- # from sentry client. It allows us to use GitLab as an alternative to
- # sentry backend. For more details see https://gitlab.com/gitlab-org/gitlab/-/issues/329596.
- class ErrorTracking::Collector < ::API::Base
- feature_category :error_tracking
- urgency :low
-
- content_type :envelope, 'application/x-sentry-envelope'
- content_type :json, 'application/json'
- content_type :txt, 'text/plain'
- default_format :envelope
-
- rescue_from Gitlab::ErrorTracking::ErrorRepository::DatabaseError do |e|
- render_api_error!(e.message, 400)
- end
-
- before do
- not_found!('Project') unless project
- not_found! unless feature_enabled?
- not_found! unless active_client_key?
- end
-
- helpers do
- def project
- @project ||= find_project(params[:id])
- end
-
- def feature_enabled?
- Feature.enabled?(:integrated_error_tracking, project) &&
- project.error_tracking_setting&.integrated_enabled?
- end
-
- def find_client_key(public_key)
- return unless public_key.present?
-
- project.error_tracking_client_keys.active.find_by_public_key(public_key)
- end
-
- def active_client_key?
- public_key = extract_public_key
-
- find_client_key(public_key)
- end
-
- def extract_public_key
- # Some SDK send public_key as a param. In this case we don't need to parse headers.
- return params[:sentry_key] if params[:sentry_key].present?
-
- begin
- ::ErrorTracking::Collector::SentryAuthParser.parse(request)[:public_key]
- rescue StandardError
- bad_request!('Failed to parse sentry request')
- end
- end
-
- def validate_payload(payload)
- unless ::ErrorTracking::Collector::PayloadValidator.new.valid?(payload)
- bad_request!('Unsupported sentry payload')
- end
- end
- end
-
- desc 'Submit error tracking event to the project as envelope' do
- detail 'This feature was introduced in GitLab 14.1.'
- end
- params do
- requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
- end
- post 'error_tracking/collector/api/:id/envelope' do
- # There is a reason why we have such uncommon path.
- # We depend on a client side error tracking software which
- # modifies URL for its own reasons.
- #
- # When we give user a URL like this
- # HOST/api/v4/error_tracking/collector/123
- #
- # Then error tracking software will convert it like this:
- # HOST/api/v4/error_tracking/collector/api/123/envelope/
-
- begin
- parsed_request = ::ErrorTracking::Collector::SentryRequestParser.parse(request)
- rescue StandardError
- bad_request!('Failed to parse sentry request')
- end
-
- type = parsed_request[:request_type]
-
- # Sentry sends 2 requests on each exception: transaction and event.
- # Everything else is not a desired behavior.
- unless type == 'transaction' || type == 'event'
- render_api_error!('400 Bad Request', 400)
-
- break
- end
-
- # We don't have use for transaction request yet,
- # so we record only event one.
- if type == 'event'
- validate_payload(parsed_request[:event])
-
- ::ErrorTracking::CollectErrorService
- .new(project, nil, event: parsed_request[:event])
- .execute
- end
-
- # Collector should never return any information back.
- # Because DSN and public key are designed for public use,
- # it is safe only for submission of new events.
- #
- # Some clients sdk require status 200 OK to work correctly.
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/343531.
- status 200
- end
-
- desc 'Submit error tracking event to the project' do
- detail 'This feature was introduced in GitLab 14.1.'
- end
- params do
- requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
- end
- post 'error_tracking/collector/api/:id/store' do
- # There is a reason why we have such uncommon path.
- # We depend on a client side error tracking software which
- # modifies URL for its own reasons.
- #
- # When we give user a URL like this
- # HOST/api/v4/error_tracking/collector/123
- #
- # Then error tracking software will convert it like this:
- # HOST/api/v4/error_tracking/collector/api/123/store/
-
- begin
- parsed_body = Gitlab::Json.parse(request.body.read)
- rescue StandardError
- bad_request!('Failed to parse sentry request')
- end
-
- validate_payload(parsed_body)
-
- ::ErrorTracking::CollectErrorService
- .new(project, nil, event: parsed_body)
- .execute
-
- # Collector should never return any information back.
- # Because DSN and public key are designed for public use,
- # it is safe only for submission of new events.
- #
- # Some clients sdk require status 200 OK to work correctly.
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/343531.
- status 200
- end
- end
-end
diff --git a/lib/generators/gitlab/analytics/internal_events_generator.rb b/lib/generators/gitlab/analytics/internal_events_generator.rb
index e945b4de3db..a85cdd352d5 100644
--- a/lib/generators/gitlab/analytics/internal_events_generator.rb
+++ b/lib/generators/gitlab/analytics/internal_events_generator.rb
@@ -114,7 +114,6 @@ module Gitlab
def known_event_entry
<<~YML
- name: #{event}
- aggregation: weekly
YML
end
diff --git a/lib/generators/gitlab/usage_metric/templates/instrumentation_class_spec.rb.template b/lib/generators/gitlab/usage_metric/templates/instrumentation_class_spec.rb.template
index f8bd502ab77..915b91e43da 100644
--- a/lib/generators/gitlab/usage_metric/templates/instrumentation_class_spec.rb.template
+++ b/lib/generators/gitlab/usage_metric/templates/instrumentation_class_spec.rb.template
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Usage::Metrics::Instrumentations::<%= class_name %>Metric do
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::<%= class_name %>Metric, feature_category: :service_ping do
let(:expected_value) { 1 }
it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
diff --git a/lib/gitlab/middleware/compressed_json.rb b/lib/gitlab/middleware/compressed_json.rb
index cc485d8a5db..1f15f1d5857 100644
--- a/lib/gitlab/middleware/compressed_json.rb
+++ b/lib/gitlab/middleware/compressed_json.rb
@@ -3,7 +3,6 @@
module Gitlab
module Middleware
class CompressedJson
- COLLECTOR_PATH = '/api/v4/error_tracking/collector'
INSTANCE_PACKAGES_PATH = %r{
\A/api/v4/packages/npm/-/npm/v1/security/
(?:(?:advisories/bulk)|(?:audits/quick))\z (?# end)
@@ -79,8 +78,7 @@ module Gitlab
end
def match_path?(env)
- env['PATH_INFO'].start_with?((File.join(relative_url, COLLECTOR_PATH))) ||
- match_packages_path?(env)
+ match_packages_path?(env)
end
def match_packages_path?(env)
diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
index badcda1def0..eaa4bf15fe1 100644
--- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb
+++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
@@ -3,18 +3,14 @@
module Gitlab
module UsageDataCounters
module HLLRedisCounter
- DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH = 6.weeks
- DEFAULT_DAILY_KEY_EXPIRY_LENGTH = 29.days
+ KEY_EXPIRY_LENGTH = 6.weeks
REDIS_SLOT = 'hll_counters'
EventError = Class.new(StandardError)
UnknownEvent = Class.new(EventError)
- UnknownAggregation = Class.new(EventError)
- AggregationMismatch = Class.new(EventError)
InvalidContext = Class.new(EventError)
KNOWN_EVENTS_PATH = File.expand_path('known_events/*.yml', __dir__)
- ALLOWED_AGGREGATIONS = %i(daily weekly).freeze
# Track event on entity_id
# Increment a Redis HLL counter for unique event_name and entity_id
@@ -24,7 +20,6 @@ module Gitlab
# Event example:
#
# - name: g_compliance_dashboard # Unique event name
- # aggregation: weekly # Aggregation level, keys are stored weekly
#
# Usage:
#
@@ -63,8 +58,7 @@ module Gitlab
# end_date - The end date of the time range.
# context - Event context, plan level tracking. Available if set when tracking.
def unique_events(event_names:, start_date:, end_date:, context: '')
- count_unique_events(event_names: event_names, start_date: start_date, end_date: end_date, context: context) do |events|
- raise AggregationMismatch, events unless events_same_aggregation?(events)
+ count_unique_events(event_names: event_names, start_date: start_date, end_date: end_date, context: context) do
raise InvalidContext if context.present? && !context.in?(valid_context_list)
end
end
@@ -78,9 +72,7 @@ module Gitlab
end
def calculate_events_union(event_names:, start_date:, end_date:)
- count_unique_events(event_names: event_names, start_date: start_date, end_date: end_date) do |events|
- raise AggregationMismatch, events unless events_same_aggregation?(events)
- end
+ count_unique_events(event_names: event_names, start_date: start_date, end_date: end_date)
end
private
@@ -94,12 +86,7 @@ module Gitlab
return if event.blank?
return unless Feature.enabled?(:redis_hll_tracking, type: :ops)
- if event[:aggregation].to_sym == :daily
- weekly_event = event.dup.tap { |e| e['aggregation'] = 'weekly' }
- Gitlab::Redis::HLL.add(key: redis_key(weekly_event, time, context), value: values, expiry: expiry(weekly_event))
- end
-
- Gitlab::Redis::HLL.add(key: redis_key(event, time, context), value: values, expiry: expiry(event))
+ Gitlab::Redis::HLL.add(key: redis_key(event, time, context), value: values, expiry: KEY_EXPIRY_LENGTH)
rescue StandardError => e
# Ignore any exceptions unless is dev or test env
@@ -117,25 +104,18 @@ module Gitlab
yield events if block_given?
- aggregation = events.first[:aggregation]
-
- if Feature.disabled?(:revert_daily_hll_events_to_weekly_aggregation)
- aggregation = 'weekly'
- events = events.map { |e| e.merge(aggregation: 'weekly') }
- end
+ keys = keys_for_aggregation(events: events, start_date: start_date, end_date: end_date, context: context)
- keys = keys_for_aggregation(aggregation, events: events, start_date: start_date, end_date: end_date, context: context)
return FALLBACK unless keys.any?
redis_usage_data { Gitlab::Redis::HLL.count(keys: keys) }
end
- def keys_for_aggregation(aggregation, events:, start_date:, end_date:, context: '')
- if aggregation.to_sym == :daily
- daily_redis_keys(events: events, start_date: start_date, end_date: end_date, context: context)
- else
- weekly_redis_keys(events: events, start_date: start_date, end_date: end_date, context: context)
- end
+ def keys_for_aggregation(events:, start_date:, end_date:, context: '')
+ end_date = end_date.end_of_week - 1.week
+ (start_date.to_date..end_date.to_date).map do |date|
+ events.map { |event| redis_key(event, date, context) }
+ end.flatten.uniq
end
def load_events(wildcard)
@@ -152,15 +132,6 @@ module Gitlab
known_events.map { |event| event[:name] }
end
- def events_same_aggregation?(events)
- aggregation = events.first[:aggregation]
- events.all? { |event| event[:aggregation] == aggregation }
- end
-
- def expiry(event)
- event[:aggregation].to_sym == :daily ? DEFAULT_DAILY_KEY_EXPIRY_LENGTH : DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH
- end
-
def event_for(event_name)
known_events.find { |event| event[:name] == event_name.to_s }
end
@@ -173,36 +144,13 @@ module Gitlab
def redis_key(event, time, context = '')
raise UnknownEvent, "Unknown event #{event[:name]}" unless known_events_names.include?(event[:name].to_s)
- # ToDo: remove during https://gitlab.com/groups/gitlab-org/-/epics/9542 cleanup
- raise UnknownAggregation, "Use :daily or :weekly aggregation" unless ALLOWED_AGGREGATIONS.include?(event[:aggregation].to_sym)
-
key = "{#{REDIS_SLOT}}_#{event[:name]}"
- key = apply_time_aggregation(key, time, event)
- key = "#{context}_#{key}" if context.present?
- key
- end
- def apply_time_aggregation(key, time, event)
- if event[:aggregation].to_sym == :daily
- year_day = time.strftime('%G-%j')
- "#{year_day}-#{key}"
- else
- year_week = time.strftime('%G-%V')
- "#{key}-#{year_week}"
- end
- end
+ year_week = time.strftime('%G-%V')
+ key = "#{key}-#{year_week}"
- def daily_redis_keys(events:, start_date:, end_date:, context: '')
- (start_date.to_date..end_date.to_date).map do |date|
- events.map { |event| redis_key(event, date, context) }
- end.flatten
- end
-
- def weekly_redis_keys(events:, start_date:, end_date:, context: '')
- end_date = end_date.end_of_week - 1.week
- (start_date.to_date..end_date.to_date).map do |date|
- events.map { |event| redis_key(event, date, context) }
- end.flatten.uniq
+ key = "#{context}_#{key}" if context.present?
+ key
end
end
end
diff --git a/lib/tasks/gitlab/packages/events.rake b/lib/tasks/gitlab/packages/events.rake
index 1234ba039a3..b5dfd163dba 100644
--- a/lib/tasks/gitlab/packages/events.rake
+++ b/lib/tasks/gitlab/packages/events.rake
@@ -45,10 +45,7 @@ namespace :gitlab do
events = event_pairs.each_with_object([]) do |(event_type, event_scope), events|
Packages::Event::ORIGINATOR_TYPES.excluding(:guest).each do |originator_type|
events_definition = Packages::Event.unique_counters_for(event_scope, event_type, originator_type).map do |event_name|
- {
- "name" => event_name,
- "aggregation" => "weekly"
- }
+ { "name" => event_name }
end
events.concat(events_definition)
diff --git a/lib/tasks/gitlab/usage_data.rake b/lib/tasks/gitlab/usage_data.rake
index fcbec4b0dba..f5bf1a266e5 100644
--- a/lib/tasks/gitlab/usage_data.rake
+++ b/lib/tasks/gitlab/usage_data.rake
@@ -85,12 +85,13 @@ namespace :gitlab do
end
end
+ # rubocop:disable Gitlab/NoCodeCoverageComment
+ # :nocov: remove in https://gitlab.com/gitlab-org/gitlab/-/issues/299453
def ci_template_event(event_name)
- {
- 'name' => event_name,
- 'aggregation' => 'weekly'
- }
+ { 'name' => event_name }
end
+ # :nocov:
+ # rubocop:enable Gitlab/NoCodeCoverageComment
def implicit_auto_devops_event(expanded_template_name)
event_name = Gitlab::UsageDataCounters::CiTemplateUniqueCounter.ci_template_event_name(expanded_template_name, :auto_devops_source)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 69a59255ae1..0c471879a34 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -39376,6 +39376,9 @@ msgstr ""
msgid "Runners|An error has occurred fetching instructions"
msgstr ""
+msgid "Runners|An error occurred while creating the runner. Please try again."
+msgstr ""
+
msgid "Runners|An error occurred while deleting. Some runners may not have been deleted."
msgstr ""
diff --git a/spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb b/spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb
index e15336f586e..54d0bfef9dd 100644
--- a/spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb
+++ b/spec/fixtures/lib/generators/gitlab/usage_metric_generator/sample_metric_test.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountFooMetric do
+RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountFooMetric, feature_category: :service_ping do
let(:expected_value) { 1 }
it_behaves_like 'a correct instrumented metric value', { time_frame: 'all', data_source: 'database' }
diff --git a/spec/frontend/ci/runner/components/runner_create_form_spec.js b/spec/frontend/ci/runner/components/runner_create_form_spec.js
index f11667ee415..c452e32b0e4 100644
--- a/spec/frontend/ci/runner/components/runner_create_form_spec.js
+++ b/spec/frontend/ci/runner/components/runner_create_form_spec.js
@@ -11,6 +11,7 @@ import {
INSTANCE_TYPE,
GROUP_TYPE,
PROJECT_TYPE,
+ I18N_CREATE_ERROR,
} from '~/ci/runner/constants';
import runnerCreateMutation from '~/ci/runner/graphql/new/runner_create.mutation.graphql';
import { captureException } from '~/ci/runner/sentry_utils';
@@ -188,5 +189,37 @@ describe('RunnerCreateForm', () => {
expect(captureException).not.toHaveBeenCalled();
});
});
+
+ describe('when no runner information is returned', () => {
+ beforeEach(async () => {
+ runnerCreateHandler.mockResolvedValue({
+ data: {
+ runnerCreate: {
+ errors: [],
+ runner: null,
+ },
+ },
+ });
+
+ findForm().vm.$emit('submit', { preventDefault });
+ await waitForPromises();
+ });
+
+ it('emits "error" result', () => {
+ expect(wrapper.emitted('error')[0]).toEqual([new TypeError(I18N_CREATE_ERROR)]);
+ });
+
+ it('does not show a saving state', () => {
+ expect(findSubmitBtn().props('loading')).toBe(false);
+ });
+
+ it('reports error', () => {
+ expect(captureException).toHaveBeenCalledTimes(1);
+ expect(captureException).toHaveBeenCalledWith({
+ component: 'RunnerCreateForm',
+ error: new Error(I18N_CREATE_ERROR),
+ });
+ });
+ });
});
});
diff --git a/spec/frontend/ci/runner/components/runner_managers_detail_spec.js b/spec/frontend/ci/runner/components/runner_managers_detail_spec.js
index b2551e67396..3435292394f 100644
--- a/spec/frontend/ci/runner/components/runner_managers_detail_spec.js
+++ b/spec/frontend/ci/runner/components/runner_managers_detail_spec.js
@@ -2,16 +2,12 @@ import { GlCollapse, GlSkeletonLoader, GlTableLite } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { __ } from '~/locale';
-import {
- shallowMountExtended,
- mountExtended,
- extendedWrapper,
-} from 'helpers/vue_test_utils_helper';
+import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import RunnerManagersDetail from '~/ci/runner/components/runner_managers_detail.vue';
-import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import RunnerManagersTable from '~/ci/runner/components/runner_managers_table.vue';
import runnerManagersQuery from '~/ci/runner/graphql/show/runner_managers.query.graphql';
import { runnerData, runnerManagersData } from '../mock_data';
@@ -33,8 +29,7 @@ describe('RunnerJobs', () => {
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findCollapse = () => wrapper.findComponent(GlCollapse);
- const findRows = () => wrapper.findAll('tbody tr');
- const findCell = ({ field, i }) => extendedWrapper(findRows().at(i)).findByTestId(`td-${field}`);
+ const findRunnerManagersTable = () => wrapper.findComponent(RunnerManagersTable);
const createComponent = ({ props, mountFn = shallowMountExtended } = {}) => {
wrapper = mountFn(RunnerManagersDetail, {
@@ -162,21 +157,7 @@ describe('RunnerJobs', () => {
it('shows rows', () => {
expect(findCollapse().attributes('visible')).toBe('true');
- expect(findRows()).toHaveLength(mockRunnerManagers.length);
- });
-
- it('shows system id', () => {
- expect(findCell({ field: 'systemId', i: 0 }).text()).toBe(mockRunnerManagers[0].systemId);
- expect(findCell({ field: 'systemId', i: 1 }).text()).toBe(mockRunnerManagers[1].systemId);
- });
-
- it('shows contacted at', () => {
- expect(findCell({ field: 'contactedAt', i: 0 }).findComponent(TimeAgo).props('time')).toBe(
- mockRunnerManagers[0].contactedAt,
- );
- expect(findCell({ field: 'contactedAt', i: 1 }).findComponent(TimeAgo).props('time')).toBe(
- mockRunnerManagers[1].contactedAt,
- );
+ expect(findRunnerManagersTable().props('items')).toEqual(mockRunnerManagers);
});
it('collapses when clicked', async () => {
diff --git a/spec/frontend/ci/runner/components/runner_managers_table_spec.js b/spec/frontend/ci/runner/components/runner_managers_table_spec.js
index a0ebf3b2578..e72ea60cdbd 100644
--- a/spec/frontend/ci/runner/components/runner_managers_table_spec.js
+++ b/spec/frontend/ci/runner/components/runner_managers_table_spec.js
@@ -1,20 +1,17 @@
import { GlTableLite } from '@gitlab/ui';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
import { s__ } from '~/locale';
import { mountExtended, extendedWrapper } from 'helpers/vue_test_utils_helper';
import RunnerManagersTable from '~/ci/runner/components/runner_managers_table.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { I18N_STATUS_NEVER_CONTACTED } from '~/ci/runner/constants';
import { runnerManagersData } from '../mock_data';
jest.mock('~/alert');
jest.mock('~/ci/runner/sentry_utils');
-const [runnerManager1, runnerManager2] = runnerManagersData.data.runner.managers.nodes;
-
-Vue.use(VueApollo);
+const mockItems = runnerManagersData.data.runner.managers.nodes;
describe('RunnerJobs', () => {
let wrapper;
@@ -25,9 +22,11 @@ describe('RunnerJobs', () => {
const findCellText = (opts) => findCell(opts).text().replace(/\s+/g, ' ');
const createComponent = ({ item } = {}) => {
+ const [mockItem, ...otherItems] = mockItems;
+
wrapper = mountExtended(RunnerManagersTable, {
propsData: {
- items: [{ ...runnerManager1, ...item }, runnerManager2],
+ items: [{ ...mockItem, ...item }, ...otherItems],
},
stubs: {
GlTableLite,
@@ -54,8 +53,8 @@ describe('RunnerJobs', () => {
it('shows system id', () => {
createComponent();
- expect(findCellText({ field: 'systemId', i: 0 })).toBe(runnerManager1.systemId);
- expect(findCellText({ field: 'systemId', i: 1 })).toBe(runnerManager2.systemId);
+ expect(findCellText({ field: 'systemId', i: 0 })).toBe(mockItems[0].systemId);
+ expect(findCellText({ field: 'systemId', i: 1 })).toBe(mockItems[1].systemId);
});
it('shows version', () => {
@@ -74,6 +73,14 @@ describe('RunnerJobs', () => {
expect(findCellText({ field: 'version', i: 0 })).toBe('1.0 (123456)');
});
+ it('shows revision without version', () => {
+ createComponent({
+ item: { version: null, revision: '123456' },
+ });
+
+ expect(findCellText({ field: 'version', i: 0 })).toBe('(123456)');
+ });
+
it('shows ip address', () => {
createComponent({
item: { ipAddress: '127.0.0.1' },
@@ -117,7 +124,14 @@ describe('RunnerJobs', () => {
it('shows contacted at', () => {
createComponent();
expect(findCell({ field: 'contactedAt', i: 0 }).findComponent(TimeAgo).props('time')).toBe(
- runnerManager1.contactedAt,
+ mockItems[0].contactedAt,
);
});
+
+ it('shows missing contacted at', () => {
+ createComponent({
+ item: { contactedAt: null },
+ });
+ expect(findCellText({ field: 'contactedAt', i: 0 })).toBe(I18N_STATUS_NEVER_CONTACTED);
+ });
});
diff --git a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
index c3e0818fc11..ca65d87f86c 100644
--- a/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
+++ b/spec/frontend/packages_and_registries/infrastructure_registry/components/details/components/package_files_spec.js
@@ -1,4 +1,4 @@
-import { GlDropdown, GlButton } from '@gitlab/ui';
+import { GlDisclosureDropdown, GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { nextTick } from 'vue/';
import stubChildren from 'helpers/stub_children';
@@ -19,7 +19,7 @@ describe('Package Files', () => {
const findSecondRowCommitLink = () => findSecondRow().find('[data-testid="commit-link"]');
const findFirstRowFileIcon = () => findFirstRow().findComponent(FileIcon);
const findFirstRowCreatedAt = () => findFirstRow().findComponent(TimeAgoTooltip);
- const findFirstActionMenu = () => findFirstRow().findComponent(GlDropdown);
+ const findFirstActionMenu = () => findFirstRow().findComponent(GlDisclosureDropdown);
const findActionMenuDelete = () => findFirstActionMenu().find('[data-testid="delete-file"]');
const findFirstToggleDetailsButton = () => findFirstRow().findComponent(GlButton);
const findFirstRowShaComponent = (id) => wrapper.find(`[data-testid="${id}"]`);
@@ -159,7 +159,7 @@ describe('Package Files', () => {
it('emits a delete event when clicked', () => {
createComponent();
- findActionMenuDelete().vm.$emit('click');
+ findActionMenuDelete().vm.$emit('action');
const [[{ id }]] = wrapper.emitted('delete-file');
expect(id).toBe(npmFiles[0].id);
diff --git a/spec/graphql/mutations/achievements/delete_user_achievement_spec.rb b/spec/graphql/mutations/achievements/delete_user_achievement_spec.rb
new file mode 100644
index 00000000000..d36b93bd3ea
--- /dev/null
+++ b/spec/graphql/mutations/achievements/delete_user_achievement_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Achievements::DeleteUserAchievement, feature_category: :user_profile do
+ include GraphqlHelpers
+
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:owner) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:achievement) { create(:achievement, namespace: group) }
+ let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) }
+
+ describe '#resolve' do
+ subject(:resolve_mutation) do
+ described_class.new(object: nil, context: { current_user: current_user }, field: nil).resolve(
+ user_achievement_id: user_achievement&.to_global_id
+ )
+ end
+
+ before_all do
+ group.add_maintainer(maintainer)
+ group.add_owner(owner)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { maintainer }
+
+ it 'raises an error' do
+ expect { resolve_mutation }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
+ .with_message(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { owner }
+
+ context 'when the params are invalid' do
+ let(:user_achievement) { nil }
+
+ it 'returns the validation error' do
+ expect { resolve_mutation }.to raise_error { Gitlab::Graphql::Errors::ArgumentError }
+ end
+ end
+
+ it 'deletes user_achievement' do
+ resolve_mutation
+
+ expect(Achievements::UserAchievement.find_by(id: user_achievement.id)).to be_nil
+ end
+ end
+ end
+
+ specify { expect(described_class).to require_graphql_authorizations(:destroy_user_achievement) }
+end
diff --git a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb
index 0afd3201853..517ba4d7699 100644
--- a/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb
+++ b/spec/lib/generators/gitlab/analytics/internal_events_generator_spec.rb
@@ -280,7 +280,7 @@ RSpec.describe Gitlab::Analytics::InternalEventsGenerator, :silence_stdout, feat
describe 'Creating known event entry' do
let(:time_frames) { %w[7d 28d] }
- let(:expected_known_events) { [{ "name" => event, "aggregation" => "weekly" }] }
+ let(:expected_known_events) { [{ "name" => event }] }
it 'creates a metric definition file using the template' do
described_class.new([], options).invoke_all
diff --git a/spec/lib/gitlab/middleware/compressed_json_spec.rb b/spec/lib/gitlab/middleware/compressed_json_spec.rb
index 5978b2422e0..c0e54c89222 100644
--- a/spec/lib/gitlab/middleware/compressed_json_spec.rb
+++ b/spec/lib/gitlab/middleware/compressed_json_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Middleware::CompressedJson do
+RSpec.describe Gitlab::Middleware::CompressedJson, feature_category: :shared do
let_it_be(:decompressed_input) { '{"foo": "bar"}' }
let_it_be(:input) { ActiveSupport::Gzip.compress(decompressed_input) }
@@ -70,24 +70,6 @@ RSpec.describe Gitlab::Middleware::CompressedJson do
end
describe '#call' do
- context 'with collector route' do
- let(:path) { '/api/v4/error_tracking/collector/1/store' }
-
- it_behaves_like 'decompress middleware'
-
- context 'with no Content-Type' do
- let(:content_type) { nil }
-
- it_behaves_like 'decompress middleware'
- end
-
- include_context 'with relative url' do
- let(:path) { "#{relative_url_root}/api/v4/error_tracking/collector/1/store" }
-
- it_behaves_like 'decompress middleware'
- end
- end
-
context 'with packages route' do
context 'with instance level endpoint' do
context 'with npm advisory bulk url' do
@@ -192,11 +174,11 @@ RSpec.describe Gitlab::Middleware::CompressedJson do
it_behaves_like 'passes input'
end
- context 'payload is too large' do
+ context 'when payload is too large' do
let(:body_limit) { Gitlab::Middleware::CompressedJson::MAXIMUM_BODY_SIZE }
let(:decompressed_input) { 'a' * (body_limit + 100) }
let(:input) { ActiveSupport::Gzip.compress(decompressed_input) }
- let(:path) { '/api/v4/error_tracking/collector/1/envelope' }
+ let(:path) { '/api/v4/packages/npm/-/npm/v1/security/advisories/bulk' }
it 'reads only limited size' do
expect(middleware.call(env))
diff --git a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
index b962757c35b..50fb9f9df6e 100644
--- a/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/hll_redis_counter_spec.rb
@@ -23,91 +23,10 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
described_class.clear_memoization(:known_events)
end
- describe '.track_event' do
- # ToDo: remove during https://gitlab.com/groups/gitlab-org/-/epics/9542 cleanup
- describe 'daily to weekly key migration precautions' do
- let(:event_a_name) { 'example_event_a' }
- let(:event_b_name) { 'example_event_b' }
- let(:known_events) do
- [
- { name: event_a_name, aggregation: 'daily' },
- { name: event_b_name, aggregation: 'weekly' }
- ].map(&:with_indifferent_access)
- end
-
- let(:start_date) { (Date.current - 1.week).beginning_of_week }
- let(:end_date) { Date.current }
-
- let(:daily_event) { known_events.first }
- let(:daily_key) { described_class.send(:redis_key, daily_event, start_date) }
- let(:weekly_key) do
- weekly_event = known_events.first.merge(aggregation: 'weekly')
- described_class.send(:redis_key, weekly_event, start_date)
- end
-
- before do
- allow(described_class).to receive(:load_events).with(described_class::KNOWN_EVENTS_PATH).and_return(known_events)
- allow(described_class).to receive(:load_events).with(/ee/).and_return([])
- end
-
- shared_examples 'writes daily events to daily and weekly keys' do
- it :aggregate_failures do
- expect(Gitlab::Redis::HLL).to receive(:add).with(expiry: 29.days, key: daily_key, value: 1).and_call_original
- expect(Gitlab::Redis::HLL).to receive(:add).with(expiry: 6.weeks, key: weekly_key, value: 1).and_call_original
-
- described_class.track_event(event_a_name, values: 1, time: start_date)
- end
- end
-
- context 'when revert_daily_hll_events_to_weekly_aggregation FF is disabled' do
- before do
- stub_feature_flags(revert_daily_hll_events_to_weekly_aggregation: false)
- end
-
- it_behaves_like 'writes daily events to daily and weekly keys'
-
- it 'aggregates weekly for daily keys', :aggregate_failures do
- expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [weekly_key]).and_call_original
- expect(Gitlab::Redis::HLL).not_to receive(:count).with(keys: [daily_key]).and_call_original
-
- described_class.unique_events(event_names: [event_a_name], start_date: start_date, end_date: end_date)
- end
-
- it 'does not persists changes to event aggregation attribute' do
- described_class.unique_events(event_names: [event_a_name], start_date: start_date, end_date: end_date)
-
- expect(described_class.known_events.find { |e| e[:name] == event_a_name }[:aggregation])
- .to eql 'daily'
- end
- end
-
- context 'when revert_daily_hll_events_to_weekly_aggregation FF is enabled' do
- before do
- stub_feature_flags(revert_daily_hll_events_to_weekly_aggregation: true)
- end
-
- # we want to write events no matter of the feature state
- it_behaves_like 'writes daily events to daily and weekly keys'
-
- it 'aggregates daily for daily keys', :aggregate_failures do
- expect(Gitlab::Redis::HLL).to receive(:count).with(keys: [daily_key]).and_call_original
- expect(Gitlab::Redis::HLL).not_to receive(:count).with(keys: [weekly_key]).and_call_original
-
- described_class.unique_events(event_names: [event_a_name], start_date: start_date, end_date: start_date)
- end
- end
- end
- end
-
describe '.known_events' do
let(:ce_temp_dir) { Dir.mktmpdir }
let(:ce_temp_file) { Tempfile.new(%w[common .yml], ce_temp_dir) }
- let(:ce_event) do
- {
- "name" => "ce_event",
- "aggregation" => "weekly"
- }
- end
+ let(:ce_event) { { "name" => "ce_event" } }
before do
stub_const("#{described_class}::KNOWN_EVENTS_PATH", File.expand_path('*.yml', ce_temp_dir))
@@ -144,13 +63,13 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
let(:known_events) do
[
- { name: weekly_event, aggregation: "weekly" },
- { name: daily_event, aggregation: "daily" },
- { name: category_productivity_event, aggregation: "weekly" },
- { name: compliance_slot_event, aggregation: "weekly" },
- { name: no_slot, aggregation: "daily" },
- { name: different_aggregation, aggregation: "monthly" },
- { name: context_event, aggregation: 'weekly' }
+ { name: weekly_event },
+ { name: daily_event },
+ { name: category_productivity_event },
+ { name: compliance_slot_event },
+ { name: no_slot },
+ { name: different_aggregation },
+ { name: context_event }
].map(&:with_indifferent_access)
end
@@ -203,15 +122,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
it 'tracks events with multiple values' do
values = [entity1, entity2]
expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_analytics_contribution/, value: values,
- expiry: described_class::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH)
+ expiry: described_class::KEY_EXPIRY_LENGTH)
described_class.track_event(:g_analytics_contribution, values: values)
end
- it "raise error if metrics don't have same aggregation" do
- expect { described_class.track_event(different_aggregation, values: entity1, time: Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownAggregation)
- end
-
it 'raise error if metrics of unknown event' do
expect { described_class.track_event('unknown', values: entity1, time: Date.current) }.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent)
end
@@ -248,22 +163,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
expect(keys).not_to be_empty
keys.each do |key|
- expect(redis.ttl(key)).to be_within(5.seconds).of(described_class::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH)
- end
- end
- end
- end
-
- context 'for daily events' do
- it 'sets the keys in Redis to expire' do
- described_class.track_event("no_slot", values: entity1)
-
- Gitlab::Redis::SharedState.with do |redis|
- keys = redis.scan_each(match: "*_no_slot").to_a
- expect(keys).not_to be_empty
-
- keys.each do |key|
- expect(redis.ttl(key)).to be_within(5.seconds).of(described_class::DEFAULT_DAILY_KEY_EXPIRY_LENGTH)
+ expect(redis.ttl(key)).to be_within(5.seconds).of(described_class::KEY_EXPIRY_LENGTH)
end
end
end
@@ -285,7 +185,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
values = [entity1, entity2]
expect(Gitlab::Redis::HLL).to receive(:add).with(key: /g_analytics_contribution/,
value: values,
- expiry: described_class::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH)
+ expiry: described_class::KEY_EXPIRY_LENGTH)
described_class.track_event_in_context(:g_analytics_contribution, values: values, context: default_context)
end
@@ -347,12 +247,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
expect(described_class.unique_events(event_names: [weekly_event], start_date: Date.current, end_date: 4.weeks.ago)).to eq(-1)
end
- it "raise error if metrics don't have same aggregation" do
- expect do
- described_class.unique_events(event_names: [daily_event, weekly_event], start_date: 4.weeks.ago, end_date: Date.current)
- end.to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::AggregationMismatch)
- end
-
context 'when data for the last complete week' do
it { expect(described_class.unique_events(event_names: [weekly_event], start_date: 1.week.ago, end_date: Date.current)).to eq(1) }
end
@@ -369,12 +263,6 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
it { expect(described_class.unique_events(event_names: [weekly_event.to_sym], start_date: 4.weeks.ago, end_date: 3.weeks.ago)).to eq(1) }
end
- context 'when using daily aggregation' do
- it { expect(described_class.unique_events(event_names: [daily_event], start_date: 7.days.ago, end_date: Date.current)).to eq(2) }
- it { expect(described_class.unique_events(event_names: [daily_event], start_date: 28.days.ago, end_date: Date.current)).to eq(3) }
- it { expect(described_class.unique_events(event_names: [daily_event], start_date: 28.days.ago, end_date: 21.days.ago)).to eq(1) }
- end
-
context 'when no slot is set' do
it { expect(described_class.unique_events(event_names: [no_slot], start_date: 7.days.ago, end_date: Date.current)).to eq(1) }
end
@@ -388,7 +276,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
end
- describe '.weekly_redis_keys' do
+ describe '.keys_for_aggregation' do
using RSpec::Parameterized::TableSyntax
let(:weekly_event) { 'i_search_total' }
@@ -398,7 +286,7 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
let(:week_three) { "{#{described_class::REDIS_SLOT}}_i_search_total-2021-01" }
let(:week_four) { "{#{described_class::REDIS_SLOT}}_i_search_total-2021-02" }
- subject(:weekly_redis_keys) { described_class.send(:weekly_redis_keys, events: [redis_event], start_date: DateTime.parse(start_date), end_date: DateTime.parse(end_date)) }
+ subject(:keys_for_aggregation) { described_class.send(:keys_for_aggregation, events: [redis_event], start_date: DateTime.parse(start_date), end_date: DateTime.parse(end_date)) }
where(:start_date, :end_date, :keys) do
'2020-12-21' | '2020-12-21' | []
@@ -421,11 +309,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
end
it 'returns 1 key for last for week' do
- expect(described_class.send(:weekly_redis_keys, events: [redis_event], start_date: 7.days.ago.to_date, end_date: Date.current).size).to eq 1
+ expect(described_class.send(:keys_for_aggregation, events: [redis_event], start_date: 7.days.ago.to_date, end_date: Date.current).size).to eq 1
end
it 'returns 4 key for last for weeks' do
- expect(described_class.send(:weekly_redis_keys, events: [redis_event], start_date: 4.weeks.ago.to_date, end_date: Date.current).size).to eq 4
+ expect(described_class.send(:keys_for_aggregation, events: [redis_event], start_date: 4.weeks.ago.to_date, end_date: Date.current).size).to eq 4
end
end
@@ -434,9 +322,9 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
let(:known_events) do
[
- { name: 'event_name_1', aggregation: "weekly" },
- { name: 'event_name_2', aggregation: "weekly" },
- { name: 'event_name_3', aggregation: "weekly" }
+ { name: 'event_name_1' },
+ { name: 'event_name_2' },
+ { name: 'event_name_3' }
].map(&:with_indifferent_access)
end
@@ -475,11 +363,11 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
let(:time_range) { { start_date: 7.days.ago, end_date: DateTime.current } }
let(:known_events) do
[
- { name: 'event1_slot', aggregation: "weekly" },
- { name: 'event2_slot', aggregation: "weekly" },
- { name: 'event3_slot', aggregation: "weekly" },
- { name: 'event5_slot', aggregation: "daily" },
- { name: 'event4', aggregation: "weekly" }
+ { name: 'event1_slot' },
+ { name: 'event2_slot' },
+ { name: 'event3_slot' },
+ { name: 'event5_slot' },
+ { name: 'event4' }
].map(&:with_indifferent_access)
end
diff --git a/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb b/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb
index d33a80b5e64..b4146761aa2 100644
--- a/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb
+++ b/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb
@@ -10,17 +10,13 @@ RSpec.describe ReMigrateRedisSlotKeys, :migration, feature_category: :service_pi
[
{
redis_slot: 'management',
- aggregation: 'daily',
name: 'g_project_management_epic_closed'
}, {
- aggregation: 'weekly',
- name: 'incident_management_incident_assigned'
+ name: 'incident_management_incident_assigned' # weekly event
},
{
- aggregation: 'weekly',
name: 'non_existing_event'
}, {
- aggregation: 'weekly',
name: 'event_without_expiry'
}
]
@@ -32,7 +28,7 @@ RSpec.describe ReMigrateRedisSlotKeys, :migration, feature_category: :service_pi
.and_return(known_events)
expiry_daily = 29.days
- expiry_weekly = Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH
+ expiry_weekly = described_class::KEY_EXPIRY_LENGTH
default_slot = Gitlab::UsageDataCounters::HLLRedisCounter::REDIS_SLOT
diff --git a/spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb b/spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb
index 21cdb6afa24..b5bf55f0d86 100644
--- a/spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb
+++ b/spec/migrations/20230317004428_migrate_daily_redis_hll_events_to_weekly_aggregation_spec.rb
@@ -15,29 +15,29 @@ RSpec.describe MigrateDailyRedisHllEventsToWeeklyAggregation, :migration, :clean
context 'with daily aggregation' do
let(:date_formatted) { date.strftime('%G-%j') }
- let(:event) { { aggregation: 'daily', name: 'wiki_action' } }
+ let(:event) { { name: 'g_edit_by_web_ide' } }
it 'returns correct key' do
- existing_key = "#{date_formatted}-{hll_counters}_wiki_action"
+ existing_key = "#{date_formatted}-{hll_counters}_g_edit_by_web_ide"
- expect(described_class.new.redis_key(event, date, event[:aggregation])).to eq(existing_key)
+ expect(described_class.new.redis_key(event, date, :daily)).to eq(existing_key)
end
end
context 'with weekly aggregation' do
let(:date_formatted) { date.strftime('%G-%V') }
- let(:event) { { aggregation: 'weekly', name: 'weekly_action' } }
+ let(:event) { { name: 'weekly_action' } }
it 'returns correct key' do
existing_key = "{hll_counters}_weekly_action-#{date_formatted}"
- expect(described_class.new.redis_key(event, date, event[:aggregation])).to eq(existing_key)
+ expect(described_class.new.redis_key(event, date, :weekly)).to eq(existing_key)
end
end
end
context 'with weekly events' do
- let(:events) { [{ aggregation: 'weekly', name: 'weekly_action' }] }
+ let(:events) { [{ name: 'weekly_action' }] }
before do
allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:known_events).and_return(events)
@@ -55,13 +55,12 @@ RSpec.describe MigrateDailyRedisHllEventsToWeeklyAggregation, :migration, :clean
context 'with daily events' do
let(:daily_expiry) { 29.days }
- let(:weekly_expiry) { Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH }
+ let(:weekly_expiry) { Gitlab::UsageDataCounters::HLLRedisCounter::KEY_EXPIRY_LENGTH }
it 'migrates with correct parameters', :aggregate_failures do
- events = [{ aggregation: 'daily', name: 'g_project_management_epic_blocked_removed' }]
- allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:known_events).and_return(events)
+ event = { name: 'g_project_management_epic_blocked_removed' }
+ allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:known_events).and_return([event])
- event = events.first.dup.tap { |e| e[:aggregation] = 'weekly' }
# For every day in the last 30 days, add a value to the daily key with daily expiry (including today)
31.times do |i|
key = described_class.new.send(:redis_key, event, Date.today - i.days, :weekly)
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index bca38fa5638..fcde094939a 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -1762,6 +1762,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
specify { is_expected.to be_allowed(:read_achievement) }
specify { is_expected.to be_allowed(:admin_achievement) }
specify { is_expected.to be_allowed(:award_achievement) }
+ specify { is_expected.to be_allowed(:destroy_user_achievement) }
context 'with feature flag disabled' do
before do
@@ -1771,6 +1772,7 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
specify { is_expected.to be_disallowed(:read_achievement) }
specify { is_expected.to be_disallowed(:admin_achievement) }
specify { is_expected.to be_disallowed(:award_achievement) }
+ specify { is_expected.to be_disallowed(:destroy_user_achievement) }
end
context 'when current user can not see the group' do
@@ -1778,6 +1780,12 @@ RSpec.describe GroupPolicy, feature_category: :system_access do
specify { is_expected.to be_allowed(:read_achievement) }
end
+
+ context 'when current user is not an owner' do
+ let(:current_user) { maintainer }
+
+ specify { is_expected.to be_disallowed(:destroy_user_achievement) }
+ end
end
describe 'admin_package ability' do
diff --git a/spec/requests/api/admin/migrations_spec.rb b/spec/requests/api/admin/migrations_spec.rb
new file mode 100644
index 00000000000..fc464300b56
--- /dev/null
+++ b/spec/requests/api/admin/migrations_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe API::Admin::Migrations, feature_category: :database do
+ let(:admin) { create(:admin) }
+
+ describe 'POST /admin/migrations/:version/mark' do
+ let(:database) { :main }
+ let(:params) { { database: database } }
+ let(:connection) { ApplicationRecord.connection }
+ let(:path) { "/admin/migrations/#{version}/mark" }
+ let(:version) { 1 }
+
+ subject(:mark) do
+ post api(path, admin, admin_mode: true), params: params
+ end
+
+ context 'when the migration exists' do
+ before do
+ double = instance_double(
+ Database::MarkMigrationService,
+ execute: ServiceResponse.success)
+
+ allow(Database::MarkMigrationService)
+ .to receive(:new)
+ .with(connection: connection, version: version)
+ .and_return(double)
+ end
+
+ it_behaves_like "POST request permissions for admin mode"
+
+ it 'marks the migration as successful' do
+ mark
+
+ expect(response).to have_gitlab_http_status(:created)
+ end
+ end
+
+ context 'when the migration does not exist' do
+ let(:version) { 123 }
+
+ it 'returns 404' do
+ mark
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when the migration was already executed' do
+ let(:version) { connection.migration_context.current_version }
+
+ it 'returns 422' do
+ mark
+
+ expect(response).to have_gitlab_http_status(:unprocessable_entity)
+ end
+ end
+
+ context 'when multiple database is enabled' do
+ let(:ci_model) { Ci::ApplicationRecord }
+ let(:database) { :ci }
+
+ before do
+ skip_if_multiple_databases_not_setup(:ci)
+ end
+
+ it 'uses the correct connection' do
+ expect(Database::MarkMigrationService)
+ .to receive(:new)
+ .with(connection: ci_model.connection, version: version)
+ .and_call_original
+
+ mark
+ end
+
+ context 'when the database name does not exist' do
+ let(:database) { :wrong_database }
+
+ it 'returns bad request', :aggregate_failures do
+ mark
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(response.body).to include('database does not have a valid value')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/error_tracking/collector_spec.rb b/spec/requests/api/error_tracking/collector_spec.rb
deleted file mode 100644
index 6a3e71bc859..00000000000
--- a/spec/requests/api/error_tracking/collector_spec.rb
+++ /dev/null
@@ -1,233 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe API::ErrorTracking::Collector, feature_category: :error_tracking do
- let_it_be(:project) { create(:project, :private) }
- let_it_be(:setting) { create(:project_error_tracking_setting, :integrated, project: project) }
- let_it_be(:client_key) { create(:error_tracking_client_key, project: project) }
-
- RSpec.shared_examples 'not found' do
- it 'reponds with 404' do
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- RSpec.shared_examples 'bad request' do
- it 'responds with 400' do
- subject
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- RSpec.shared_examples 'successful request' do
- it 'writes to the database and returns OK', :aggregate_failures do
- expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1)
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- describe "POST /error_tracking/collector/api/:id/envelope" do
- let_it_be(:raw_event) { fixture_file('error_tracking/event.txt') }
- let_it_be(:url) { "/error_tracking/collector/api/#{project.id}/envelope" }
-
- let(:params) { raw_event }
- let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}" } }
-
- subject { post api(url), params: params, headers: headers }
-
- it_behaves_like 'successful request'
-
- context 'intergrated error tracking feature flag is disabled' do
- before do
- stub_feature_flags(integrated_error_tracking: false)
- end
-
- it_behaves_like 'not found'
- end
-
- context 'error tracking feature is disabled' do
- before do
- setting.update!(enabled: false)
- end
-
- it_behaves_like 'not found'
- end
-
- context 'integrated error tracking is disabled' do
- before do
- setting.update!(integrated: false)
- end
-
- it_behaves_like 'not found'
- end
-
- context 'auth headers are missing' do
- let(:headers) { {} }
-
- it_behaves_like 'bad request'
- end
-
- context 'public key is wrong' do
- let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=glet_1fedb514e17f4b958435093deb02048c" } }
-
- it_behaves_like 'not found'
- end
-
- context 'public key is inactive' do
- let(:client_key) { create(:error_tracking_client_key, :disabled, project: project) }
-
- it_behaves_like 'not found'
- end
-
- context 'empty body' do
- let(:params) { '' }
-
- it_behaves_like 'bad request'
- end
-
- context 'unknown request type' do
- let(:params) { fixture_file('error_tracking/unknown.txt') }
-
- it_behaves_like 'bad request'
- end
-
- context 'transaction request type' do
- let(:params) { fixture_file('error_tracking/transaction.txt') }
-
- it 'does nothing and returns ok' do
- expect { subject }.not_to change { ErrorTracking::ErrorEvent.count }
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context 'gzip body' do
- let(:standard_headers) do
- {
- 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}",
- 'HTTP_CONTENT_ENCODING' => 'gzip'
- }
- end
-
- let(:params) { ActiveSupport::Gzip.compress(raw_event) }
-
- context 'with application/x-sentry-envelope Content-Type' do
- let(:headers) { standard_headers.merge({ 'CONTENT_TYPE' => 'application/x-sentry-envelope' }) }
-
- it_behaves_like 'successful request'
- end
-
- context 'with unexpected Content-Type' do
- let(:headers) { standard_headers.merge({ 'CONTENT_TYPE' => 'application/gzip' }) }
-
- it 'responds with 415' do
- subject
-
- expect(response).to have_gitlab_http_status(:unsupported_media_type)
- end
- end
- end
- end
-
- describe "POST /error_tracking/collector/api/:id/store" do
- let_it_be(:raw_event) { fixture_file('error_tracking/parsed_event.json') }
- let_it_be(:url) { "/error_tracking/collector/api/#{project.id}/store" }
-
- let(:params) { raw_event }
- let(:headers) { { 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}" } }
-
- subject { post api(url), params: params, headers: headers }
-
- it_behaves_like 'successful request'
-
- context 'empty headers' do
- let(:headers) { {} }
-
- it_behaves_like 'bad request'
- end
-
- context 'empty body' do
- let(:params) { '' }
-
- it_behaves_like 'bad request'
- end
-
- context 'body with string instead of json' do
- let(:params) { '"********"' }
-
- it_behaves_like 'bad request'
- end
-
- context 'collector fails with validation error' do
- before do
- allow(::ErrorTracking::CollectErrorService)
- .to receive(:new).and_raise(Gitlab::ErrorTracking::ErrorRepository::DatabaseError)
- end
-
- it_behaves_like 'bad request'
- end
-
- context 'with platform field too long' do
- let(:params) do
- event = Gitlab::Json.parse(raw_event)
- event['platform'] = 'a' * 256
- Gitlab::Json.dump(event)
- end
-
- it_behaves_like 'bad request'
- end
-
- context 'gzip body' do
- let(:headers) do
- {
- 'X-Sentry-Auth' => "Sentry sentry_key=#{client_key.public_key}",
- 'HTTP_CONTENT_ENCODING' => 'gzip',
- 'CONTENT_TYPE' => 'application/json'
- }
- end
-
- let(:params) { ActiveSupport::Gzip.compress(raw_event) }
-
- it_behaves_like 'successful request'
- end
-
- context 'body contains nullbytes' do
- let_it_be(:raw_event) { fixture_file('error_tracking/parsed_event_nullbytes.json') }
-
- it_behaves_like 'successful request'
- end
-
- context 'when JSON key transaction is empty string' do
- let_it_be(:raw_event) { fixture_file('error_tracking/php_empty_transaction.json') }
-
- it_behaves_like 'successful request'
- end
-
- context 'sentry_key as param and empty headers' do
- let(:url) { "/error_tracking/collector/api/#{project.id}/store?sentry_key=#{sentry_key}" }
- let(:headers) { {} }
-
- context 'key is wrong' do
- let(:sentry_key) { 'glet_1fedb514e17f4b958435093deb02048c' }
-
- it_behaves_like 'not found'
- end
-
- context 'key is empty' do
- let(:sentry_key) { '' }
-
- it_behaves_like 'bad request'
- end
-
- context 'key is correct' do
- let(:sentry_key) { client_key.public_key }
-
- it_behaves_like 'successful request'
- end
- end
- end
-end
diff --git a/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb b/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb
new file mode 100644
index 00000000000..f759e6dce08
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/achievements/delete_user_achievement_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Mutations::Achievements::DeleteUserAchievement, feature_category: :user_profile do
+ include GraphqlHelpers
+
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:owner) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:achievement) { create(:achievement, namespace: group) }
+ let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) }
+
+ let(:mutation) { graphql_mutation(:user_achievements_delete, params) }
+ let(:user_achievement_id) { user_achievement&.to_global_id }
+ let(:params) { { user_achievement_id: user_achievement_id } }
+
+ subject { post_graphql_mutation(mutation, current_user: current_user) }
+
+ before_all do
+ group.add_maintainer(maintainer)
+ group.add_owner(owner)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { maintainer }
+
+ it_behaves_like 'a mutation that returns a top-level access error'
+
+ it 'does not delete any user achievements' do
+ expect { subject }.not_to change { Achievements::UserAchievement.count }
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { owner }
+
+ context 'when the params are invalid' do
+ let(:user_achievement) { nil }
+
+ it 'returns the validation error' do
+ subject
+
+ expect(graphql_errors.to_s).to include('invalid value for userAchievementId (Expected value to not be null)')
+ end
+ end
+
+ context 'when the user_achievement_id is invalid' do
+ let(:user_achievement_id) { "gid://gitlab/Achievements::UserAchievement/#{non_existing_record_id}" }
+
+ it 'returns the relevant error' do
+ subject
+
+ expect(graphql_errors.to_s)
+ .to include("The resource that you are attempting to access does not exist or you don't have permission")
+ end
+ end
+
+ context 'when the feature flag is disabled' do
+ before do
+ stub_feature_flags(achievements: false)
+ end
+
+ it 'returns the relevant error' do
+ subject
+
+ expect(graphql_errors.to_s)
+ .to include("The resource that you are attempting to access does not exist or you don't have permission")
+ end
+ end
+
+ context 'when everything is ok' do
+ it 'deletes an user achievement' do
+ expect { subject }.to change { Achievements::UserAchievement.count }.by(-1)
+ end
+
+ it 'returns the deleted user achievement' do
+ subject
+
+ expect(graphql_data_at(:user_achievements_delete, :user_achievement, :achievement, :id))
+ .to eq(achievement.to_global_id.to_s)
+ end
+ end
+ end
+end
diff --git a/spec/services/achievements/destroy_user_achievement_service_spec.rb b/spec/services/achievements/destroy_user_achievement_service_spec.rb
new file mode 100644
index 00000000000..c5ff43fa1b2
--- /dev/null
+++ b/spec/services/achievements/destroy_user_achievement_service_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Achievements::DestroyUserAchievementService, feature_category: :user_profile do
+ describe '#execute' do
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:owner) { create(:user) }
+ let_it_be(:group) { create(:group) }
+
+ let_it_be(:achievement) { create(:achievement, namespace: group) }
+ let_it_be(:user_achievement) { create(:user_achievement, achievement: achievement) }
+
+ subject(:response) { described_class.new(current_user, user_achievement).execute }
+
+ before_all do
+ group.add_maintainer(maintainer)
+ group.add_owner(owner)
+ end
+
+ context 'when user does not have permission' do
+ let(:current_user) { maintainer }
+
+ it 'returns an error' do
+ expect(response).to be_error
+ expect(response.message).to match_array(
+ ['You have insufficient permissions to delete this user achievement'])
+ end
+ end
+
+ context 'when user has permission' do
+ let(:current_user) { owner }
+
+ it 'deletes the achievement' do
+ expect(response).to be_success
+ expect(Achievements::UserAchievement.find_by(id: user_achievement.id)).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/services/database/mark_migration_service_spec.rb b/spec/services/database/mark_migration_service_spec.rb
new file mode 100644
index 00000000000..5fd2268484e
--- /dev/null
+++ b/spec/services/database/mark_migration_service_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Database::MarkMigrationService, feature_category: :database do
+ let(:service) { described_class.new(connection: connection, version: version) }
+ let(:version) { 1 }
+ let(:connection) { ApplicationRecord.connection }
+
+ let(:migrations) do
+ [
+ instance_double(
+ ActiveRecord::MigrationProxy,
+ version: 1,
+ name: 'migration_pending',
+ filename: 'db/migrate/1_migration_pending.rb'
+ )
+ ]
+ end
+
+ before do
+ ctx = instance_double(ActiveRecord::MigrationContext, migrations: migrations)
+ allow(connection).to receive(:migration_context).and_return(ctx)
+ end
+
+ describe '#execute' do
+ subject(:execute) { service.execute }
+
+ it 'marks the migration as successful' do
+ expect { execute }
+ .to change { ActiveRecord::SchemaMigration.where(version: version).count }
+ .by(1)
+
+ is_expected.to be_success
+ end
+
+ context 'when the migration does not exist' do
+ let(:version) { 123 }
+
+ it { is_expected.to be_error }
+ it { expect(execute.reason).to eq(:not_found) }
+
+ it 'does not insert records' do
+ expect { execute }
+ .not_to change { ActiveRecord::SchemaMigration.where(version: version).count }
+ end
+ end
+
+ context 'when the migration was already executed' do
+ before do
+ allow(service).to receive(:all_versions).and_return([version])
+ end
+
+ it { is_expected.to be_error }
+ it { expect(execute.reason).to eq(:invalid) }
+
+ it 'does not insert records' do
+ expect { execute }
+ .not_to change { ActiveRecord::SchemaMigration.where(version: version).count }
+ end
+ end
+
+ context 'when the insert fails' do
+ it 'returns an error response' do
+ expect(service).to receive(:create_version).with(version).and_return(false)
+
+ is_expected.to be_error
+ end
+ end
+ end
+end
diff --git a/spec/services/personal_access_tokens/last_used_service_spec.rb b/spec/services/personal_access_tokens/last_used_service_spec.rb
index 20eabc20338..77ea5e10379 100644
--- a/spec/services/personal_access_tokens/last_used_service_spec.rb
+++ b/spec/services/personal_access_tokens/last_used_service_spec.rb
@@ -6,8 +6,8 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_
describe '#execute' do
subject { described_class.new(personal_access_token).execute }
- context 'when the personal access token has not been used recently' do
- let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.year.ago) }
+ context 'when the personal access token was used 10 minutes ago', :freeze_time do
+ let(:personal_access_token) { create(:personal_access_token, last_used_at: 10.minutes.ago) }
it 'updates the last_used_at timestamp' do
expect { subject }.to change { personal_access_token.last_used_at }
@@ -20,8 +20,8 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_
end
end
- context 'when the personal access token has been used recently' do
- let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: 1.minute.ago) }
+ context 'when the personal access token was used less than 10 minutes ago', :freeze_time do
+ let(:personal_access_token) { create(:personal_access_token, last_used_at: (10.minutes - 1.second).ago) }
it 'does not update the last_used_at timestamp' do
expect { subject }.not_to change { personal_access_token.last_used_at }
@@ -43,5 +43,49 @@ RSpec.describe PersonalAccessTokens::LastUsedService, feature_category: :system_
expect(subject).to be_nil
end
end
+
+ context 'when update_personal_access_token_usage_information_every_10_minutes is disabled' do
+ before do
+ stub_feature_flags(update_personal_access_token_usage_information_every_10_minutes: false)
+ end
+
+ context 'when the personal access token was used 1 day ago', :freeze_time do
+ let(:personal_access_token) { create(:personal_access_token, last_used_at: 1.day.ago) }
+
+ it 'updates the last_used_at timestamp' do
+ expect { subject }.to change { personal_access_token.last_used_at }
+ end
+
+ it 'does not run on read-only GitLab instances' do
+ allow(::Gitlab::Database).to receive(:read_only?).and_return(true)
+
+ expect { subject }.not_to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when the personal access token was used less than 1 day ago', :freeze_time do
+ let(:personal_access_token) { create(:personal_access_token, last_used_at: (1.day - 1.second).ago) }
+
+ it 'does not update the last_used_at timestamp' do
+ expect { subject }.not_to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when the last_used_at timestamp is nil' do
+ let_it_be(:personal_access_token) { create(:personal_access_token, last_used_at: nil) }
+
+ it 'updates the last_used_at timestamp' do
+ expect { subject }.to change { personal_access_token.last_used_at }
+ end
+ end
+
+ context 'when not a personal access token' do
+ let_it_be(:personal_access_token) { create(:oauth_access_token) }
+
+ it 'does not execute' do
+ expect(subject).to be_nil
+ end
+ end
+ end
end
end
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 354d175d825..b3b6854481f 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -8228,7 +8228,6 @@
- './spec/requests/api/doorkeeper_access_spec.rb'
- './spec/requests/api/environments_spec.rb'
- './spec/requests/api/error_tracking/client_keys_spec.rb'
-- './spec/requests/api/error_tracking/collector_spec.rb'
- './spec/requests/api/events_spec.rb'
- './spec/requests/api/feature_flags_spec.rb'
- './spec/requests/api/feature_flags_user_lists_spec.rb'
diff --git a/workhorse/go.mod b/workhorse/go.mod
index 5a3251b4643..0b08b06d0cc 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -26,7 +26,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/smartystreets/goconvey v1.7.2
github.com/stretchr/testify v1.8.4
- gitlab.com/gitlab-org/gitaly/v16 v16.0.2
+ gitlab.com/gitlab-org/gitaly/v16 v16.0.3
gitlab.com/gitlab-org/labkit v1.18.0
gocloud.dev v0.29.0
golang.org/x/image v0.7.0
diff --git a/workhorse/go.sum b/workhorse/go.sum
index 0e0b3087c68..1cd4d274eef 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -1928,8 +1928,8 @@ github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
-gitlab.com/gitlab-org/gitaly/v16 v16.0.2 h1:GzRaMkwtem5L7SyJiCDiTae1O3nepBiTdls/FM5akoI=
-gitlab.com/gitlab-org/gitaly/v16 v16.0.2/go.mod h1:GDDfuCzp8ewfG+cPaYJa6czKluTQhsZyEr5WAtaqbbk=
+gitlab.com/gitlab-org/gitaly/v16 v16.0.3 h1:13d1DlXBPiWGemF5MaoHqMIhoxZQfROjhkmy4WwrreU=
+gitlab.com/gitlab-org/gitaly/v16 v16.0.3/go.mod h1:GDDfuCzp8ewfG+cPaYJa6czKluTQhsZyEr5WAtaqbbk=
gitlab.com/gitlab-org/labkit v1.18.0 h1:uYCIqDt/5V1hLIecTR4UNc1sD2+xiYplyKeyfpNN26A=
gitlab.com/gitlab-org/labkit v1.18.0/go.mod h1:nlLJvKgXcIclqWMI+rga2TckNBVHOtRCHMxBoVByNoE=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=