summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/ci_lint/components/ci_lint.vue2
-rw-r--r--app/assets/javascripts/ide/services/index.js2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue8
-rw-r--r--app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue6
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue4
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue2
-rw-r--r--app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue2
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_app_status.mutation.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/mutations/update_app_status.mutation.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_current_branch.mutation.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/mutations/update_current_branch.mutation.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_last_commit_branch.mutation.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/mutations/update_last_commit_branch.mutation.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_pipeline_etag.mutation.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.query.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/blob_content.query.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/queries/blob_content.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.query.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.query.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.query.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/queries/pipeline.query.graphql (renamed from app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql)0
-rw-r--r--app/assets/javascripts/pipeline_editor/graphql/resolvers.js6
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js6
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue10
-rw-r--r--app/experiments/application_experiment.rb17
-rw-r--r--app/views/projects/settings/access_tokens/index.html.haml2
-rw-r--r--app/views/shared/members/_filter_2fa_dropdown.html.haml11
-rw-r--r--db/post_migrate/20211213102111_drop_ci_pipelines_mr_metrics_fk.rb15
-rw-r--r--db/schema_migrations/202112131021111
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/resource_access_tokens.md2
-rw-r--r--doc/development/fe_guide/graphql.md4
-rw-r--r--doc/user/profile/account/delete_account.md2
-rw-r--r--doc/user/project/settings/project_access_tokens.md209
-rw-r--r--lib/feature.rb2
-rw-r--r--lib/feature/definition.rb18
-rw-r--r--lib/feature/shared.rb1
-rw-r--r--lib/gitlab/database/gitlab_loose_foreign_keys.yml4
-rw-r--r--locale/gitlab.pot6
-rw-r--r--spec/experiments/application_experiment_spec.rb69
-rw-r--r--spec/frontend/ci_lint/components/ci_lint_spec.js2
-rw-r--r--spec/frontend/ide/services/index_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/commit/commit_section_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js2
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js7
-rw-r--r--spec/lib/feature/definition_spec.rb72
-rw-r--r--spec/lib/feature_spec.rb87
-rw-r--r--spec/lib/gitlab/lograge/custom_options_spec.rb18
-rw-r--r--spec/models/merge_request/metrics_spec.rb6
-rw-r--r--spec/models/terraform/state_version_spec.rb5
-rw-r--r--spec/support/helpers/snowplow_helpers.rb14
-rw-r--r--spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb25
51 files changed, 489 insertions, 169 deletions
diff --git a/app/assets/javascripts/ci_lint/components/ci_lint.vue b/app/assets/javascripts/ci_lint/components/ci_lint.vue
index bc8a1f05ef5..d541e89756a 100644
--- a/app/assets/javascripts/ci_lint/components/ci_lint.vue
+++ b/app/assets/javascripts/ci_lint/components/ci_lint.vue
@@ -1,7 +1,7 @@
<script>
import { GlButton, GlFormCheckbox, GlIcon, GlLink, GlAlert } from '@gitlab/ui';
import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
-import lintCiMutation from '~/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql';
+import lintCiMutation from '~/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
export default {
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index fd2b064160b..37a08bc4feb 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -3,7 +3,7 @@ import Api from '~/api';
import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql';
import axios from '~/lib/utils/axios_utils';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
-import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.graphql';
+import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.query.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { query, mutate } from './gql';
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
index d8043f749ab..0d28dd4e461 100644
--- a/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
+++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_section.vue
@@ -8,10 +8,10 @@ import {
COMMIT_SUCCESS,
} from '../../constants';
import commitCIFile from '../../graphql/mutations/commit_ci_file.mutation.graphql';
-import updateCurrentBranchMutation from '../../graphql/mutations/update_current_branch.mutation.graphql';
-import updateLastCommitBranchMutation from '../../graphql/mutations/update_last_commit_branch.mutation.graphql';
-import updatePipelineEtag from '../../graphql/mutations/update_pipeline_etag.mutation.graphql';
-import getCurrentBranch from '../../graphql/queries/client/current_branch.graphql';
+import updateCurrentBranchMutation from '../../graphql/mutations/client/update_current_branch.mutation.graphql';
+import updateLastCommitBranchMutation from '../../graphql/mutations/client/update_last_commit_branch.mutation.graphql';
+import updatePipelineEtag from '../../graphql/mutations/client/update_pipeline_etag.mutation.graphql';
+import getCurrentBranch from '../../graphql/queries/client/current_branch.query.graphql';
import CommitForm from './commit_form.vue';
diff --git a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
index 8f2e529fb7c..7ac7bffc833 100644
--- a/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
+++ b/app/assets/javascripts/pipeline_editor/components/file_nav/branch_switcher.vue
@@ -18,9 +18,9 @@ import {
BRANCH_SEARCH_DEBOUNCE,
DEFAULT_FAILURE,
} from '~/pipeline_editor/constants';
-import updateCurrentBranchMutation from '~/pipeline_editor/graphql/mutations/update_current_branch.mutation.graphql';
-import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.graphql';
-import getCurrentBranchQuery from '~/pipeline_editor/graphql/queries/client/current_branch.graphql';
+import updateCurrentBranchMutation from '~/pipeline_editor/graphql/mutations/client/update_current_branch.mutation.graphql';
+import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.query.graphql';
+import getCurrentBranchQuery from '~/pipeline_editor/graphql/queries/client/current_branch.query.graphql';
import getLastCommitBranchQuery from '~/pipeline_editor/graphql/queries/client/last_commit_branch.query.graphql';
export default {
diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
index 78d9b6fb70c..7d042e02052 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
@@ -3,8 +3,8 @@ import { GlButton, GlIcon, GlLink, GlLoadingIcon, GlSprintf, GlTooltipDirective
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { truncateSha } from '~/lib/utils/text_utility';
import { s__ } from '~/locale';
-import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
-import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.graphql';
+import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.graphql';
+import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql';
import {
getQueryHeaders,
toggleQueryPollingByVisibility,
diff --git a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
index 63bffe702ad..798e337b493 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
@@ -1,7 +1,7 @@
<script>
import { GlIcon, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { __, s__, sprintf } from '~/locale';
-import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.graphql';
+import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.query.graphql';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import {
EDITOR_APP_STATUS_EMPTY,
diff --git a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
index 0cd0d17d944..bd4f8d6ebc3 100644
--- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
+++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
@@ -17,7 +17,7 @@ import {
TABS_INDEX,
VISUALIZE_TAB,
} from '../constants';
-import getAppStatus from '../graphql/queries/client/app_status.graphql';
+import getAppStatus from '../graphql/queries/client/app_status.query.graphql';
import CiConfigMergedPreview from './editor/ci_config_merged_preview.vue';
import CiEditorHeader from './editor/ci_editor_header.vue';
import TextEditor from './editor/text_editor.vue';
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql
index 5091d63111f..5091d63111f 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_app_status.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_app_status.mutation.graphql
index 7487e328668..7487e328668 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_app_status.mutation.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_app_status.mutation.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_current_branch.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_current_branch.mutation.graphql
index b722c147f5f..b722c147f5f 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_current_branch.mutation.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_current_branch.mutation.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_last_commit_branch.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_last_commit_branch.mutation.graphql
index 9561312f2b6..9561312f2b6 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_last_commit_branch.mutation.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_last_commit_branch.mutation.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_pipeline_etag.mutation.graphql
index 9025f00b343..9025f00b343 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/mutations/client/update_pipeline_etag.mutation.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.query.graphql
index 359b4a846c7..359b4a846c7 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/available_branches.query.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/blob_content.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/blob_content.query.graphql
index 5928d90f7c4..5928d90f7c4 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/blob_content.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/blob_content.query.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.query.graphql
index df7de6a1f54..df7de6a1f54 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/ci_config.query.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.query.graphql
index 938f36c7d5c..938f36c7d5c 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/app_status.query.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.query.graphql
index acd46013f5b..acd46013f5b 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/current_branch.query.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql
index b9946a9e233..b9946a9e233 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/pipeline.query.graphql
index 021b858d72e..021b858d72e 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql
+++ b/app/assets/javascripts/pipeline_editor/graphql/queries/pipeline.query.graphql
diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
index e4965e00af3..bb1f5a863cd 100644
--- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
+++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js
@@ -1,8 +1,8 @@
import axios from '~/lib/utils/axios_utils';
-import getAppStatus from './queries/client/app_status.graphql';
-import getCurrentBranchQuery from './queries/client/current_branch.graphql';
+import getAppStatus from './queries/client/app_status.query.graphql';
+import getCurrentBranchQuery from './queries/client/current_branch.query.graphql';
import getLastCommitBranchQuery from './queries/client/last_commit_branch.query.graphql';
-import getPipelineEtag from './queries/client/pipeline_etag.graphql';
+import getPipelineEtag from './queries/client/pipeline_etag.query.graphql';
export const resolvers = {
Mutation: {
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index 4f7f2743aca..299f593a19e 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -5,10 +5,10 @@ import createDefaultClient from '~/lib/graphql';
import { resetServiceWorkersPublicPath } from '../lib/utils/webpack';
import { EDITOR_APP_STATUS_LOADING } from './constants';
import { CODE_SNIPPET_SOURCE_SETTINGS } from './components/code_snippet_alert/constants';
-import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
-import getAppStatus from './graphql/queries/client/app_status.graphql';
+import getCurrentBranch from './graphql/queries/client/current_branch.query.graphql';
+import getAppStatus from './graphql/queries/client/app_status.query.graphql';
import getLastCommitBranchQuery from './graphql/queries/client/last_commit_branch.query.graphql';
-import getPipelineEtag from './graphql/queries/client/pipeline_etag.graphql';
+import getPipelineEtag from './graphql/queries/client/pipeline_etag.query.graphql';
import { resolvers } from './graphql/resolvers';
import typeDefs from './graphql/typedefs.graphql';
import PipelineEditorApp from './pipeline_editor_app.vue';
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index 563defdc2c1..0c922979b14 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -17,11 +17,11 @@ import {
LOAD_FAILURE_UNKNOWN,
STARTER_TEMPLATE_NAME,
} from './constants';
-import updateAppStatus from './graphql/mutations/update_app_status.mutation.graphql';
-import getBlobContent from './graphql/queries/blob_content.graphql';
-import getCiConfigData from './graphql/queries/ci_config.graphql';
-import getAppStatus from './graphql/queries/client/app_status.graphql';
-import getCurrentBranch from './graphql/queries/client/current_branch.graphql';
+import updateAppStatus from './graphql/mutations/client/update_app_status.mutation.graphql';
+import getBlobContent from './graphql/queries/blob_content.query.graphql';
+import getCiConfigData from './graphql/queries/ci_config.query.graphql';
+import getAppStatus from './graphql/queries/client/app_status.query.graphql';
+import getCurrentBranch from './graphql/queries/client/current_branch.query.graphql';
import getTemplate from './graphql/queries/get_starter_template.query.graphql';
import getLatestCommitShaQuery from './graphql/queries/latest_commit_sha.query.graphql';
import PipelineEditorHome from './pipeline_editor_home.vue';
diff --git a/app/experiments/application_experiment.rb b/app/experiments/application_experiment.rb
index bf8d43de6aa..859716b4739 100644
--- a/app/experiments/application_experiment.rb
+++ b/app/experiments/application_experiment.rb
@@ -41,6 +41,10 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
# define a default nil control behavior so we can omit it when not needed
end
+ def track(action, **event_args)
+ super(action, **tracking_context.merge(event_args))
+ end
+
# TODO: remove
# This is deprecated logic as of v0.6.0 and should eventually be removed, but
# needs to stay intact for actively running experiments. The new strategy
@@ -60,6 +64,19 @@ class ApplicationExperiment < Gitlab::Experiment # rubocop:disable Gitlab/Namesp
private
+ def tracking_context
+ {
+ namespace: context.try(:namespace) || context.try(:group),
+ project: context.try(:project),
+ user: user_or_actor
+ }.compact || {}
+ end
+
+ def user_or_actor
+ actor = context.try(:actor)
+ actor.respond_to?(:id) ? actor : context.try(:user)
+ end
+
def feature_flag_name
name.tr('/', '_')
end
diff --git a/app/views/projects/settings/access_tokens/index.html.haml b/app/views/projects/settings/access_tokens/index.html.haml
index 52ef2e7d1ee..4e946050881 100644
--- a/app/views/projects/settings/access_tokens/index.html.haml
+++ b/app/views/projects/settings/access_tokens/index.html.haml
@@ -39,7 +39,7 @@
access_levels: ProjectMember.access_level_roles,
default_access_level: Gitlab::Access::MAINTAINER,
prefix: :project_access_token,
- help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'limiting-scopes-of-a-project-access-token')
+ help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
= render 'shared/access_tokens/table',
active_tokens: @active_project_access_tokens,
diff --git a/app/views/shared/members/_filter_2fa_dropdown.html.haml b/app/views/shared/members/_filter_2fa_dropdown.html.haml
deleted file mode 100644
index 8187a9bde15..00000000000
--- a/app/views/shared/members/_filter_2fa_dropdown.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-- filter = params[:two_factor] || 'everyone'
-- filter_options = { 'everyone' => _('Everyone'), 'enabled' => _('Enabled'), 'disabled' => _('Disabled') }
-.dropdown.inline.member-filter-2fa-dropdown{ data: { testid: 'member-filter-2fa-dropdown' } }
- = dropdown_toggle(filter_options[filter], { toggle: 'dropdown', testid: 'dropdown-toggle' })
- %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
- %li.dropdown-header
- = _("Filter by two-factor authentication")
- - filter_options.each do |value, title|
- %li
- = link_to filter_group_project_member_path(two_factor: value), class: ("is-active" if filter == value) do
- = title
diff --git a/db/post_migrate/20211213102111_drop_ci_pipelines_mr_metrics_fk.rb b/db/post_migrate/20211213102111_drop_ci_pipelines_mr_metrics_fk.rb
new file mode 100644
index 00000000000..49f498c911d
--- /dev/null
+++ b/db/post_migrate/20211213102111_drop_ci_pipelines_mr_metrics_fk.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class DropCiPipelinesMrMetricsFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ remove_foreign_key_if_exists(:merge_request_metrics, :ci_pipelines, name: "fk_rails_33ae169d48")
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:merge_request_metrics, :ci_pipelines, name: "fk_rails_33ae169d48", column: :pipeline_id, target_column: :id, on_delete: "cascade")
+ end
+end
diff --git a/db/schema_migrations/20211213102111 b/db/schema_migrations/20211213102111
new file mode 100644
index 00000000000..214d061f265
--- /dev/null
+++ b/db/schema_migrations/20211213102111
@@ -0,0 +1 @@
+3d011cc67fc6ac661788f2d0e3766e51d624a4248ac9dbd861a4db810d396091 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 5f378e6396e..44f753796c1 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -30338,9 +30338,6 @@ ALTER TABLE ONLY container_repositories
ALTER TABLE ONLY clusters_applications_jupyter
ADD CONSTRAINT fk_rails_331f0aff78 FOREIGN KEY (oauth_application_id) REFERENCES oauth_applications(id) ON DELETE SET NULL;
-ALTER TABLE ONLY merge_request_metrics
- ADD CONSTRAINT fk_rails_33ae169d48 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
-
ALTER TABLE ONLY suggestions
ADD CONSTRAINT fk_rails_33b03a535c FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
diff --git a/doc/api/resource_access_tokens.md b/doc/api/resource_access_tokens.md
index fa1e7aace9a..90e9769b896 100644
--- a/doc/api/resource_access_tokens.md
+++ b/doc/api/resource_access_tokens.md
@@ -58,7 +58,7 @@ POST projects/:id/access_tokens
|-----------|---------|----------|---------------------|
| `id` | integer or string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
| `name` | String | yes | The name of the project access token |
-| `scopes` | `Array[String]` | yes | [List of scopes](../user/project/settings/project_access_tokens.md#limiting-scopes-of-a-project-access-token) |
+| `scopes` | `Array[String]` | yes | [List of scopes](../user/project/settings/project_access_tokens.md#scopes-for-a-project-access-token) |
| `access_level` | Integer | no | A valid access level. Default value is 40 (Maintainer). Other allowed values are 10 (Guest), 20 (Reporter), and 30 (Developer). |
| `expires_at` | Date | no | The token expires at midnight UTC on that date |
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index 0f91065fead..ed71f612061 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -886,7 +886,7 @@ Here, the apollo query is watching for changes in `graphqlResourceEtag`. If your
You can see an example of this in the pipeline status of the pipeline editor. The pipeline editor watches for changes in the latest pipeline. When the user creates a new commit, we update the pipeline query to poll for changes in the new pipeline.
```graphql
-# pipeline_etag.graphql
+# pipeline_etag.query.graphql
query getPipelineEtag {
pipelineEtag @client
@@ -896,7 +896,7 @@ query getPipelineEtag {
```javascript
/* pipeline_editor/components/header/pipeline_status.vue */
-import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.graphql';
+import getPipelineEtag from '~/pipeline_editor/graphql/queries/client/pipeline_etag.query.graphql';
apollo: {
pipelineEtag: {
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index 6a824df68f3..96415279de4 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -42,7 +42,7 @@ Using the **Delete user and contributions** option may result
in removing more data than intended. Please see [associated records](#associated-records)
below for additional details.
-### Associated Records
+### Associated records
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/7393) for issues in GitLab 9.0.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/10467) for merge requests, award emoji, notes, and abuse reports in GitLab 9.1.
diff --git a/doc/user/project/settings/project_access_tokens.md b/doc/user/project/settings/project_access_tokens.md
index 1418500f8eb..f06e2068088 100644
--- a/doc/user/project/settings/project_access_tokens.md
+++ b/doc/user/project/settings/project_access_tokens.md
@@ -12,111 +12,88 @@ type: reference, howto
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in GitLab 13.5.
> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/342327) in GitLab 14.5. Default prefix added.
-Project access tokens are similar to [personal access tokens](../../profile/personal_access_tokens.md)
-except they are attached to a project rather than a user. They can be used to:
+You can use a project access token to authenticate:
-- Authenticate with the [GitLab API](../../../api/index.md#personalproject-access-tokens).
-- Authenticate with Git using HTTP Basic Authentication. If you are asked for a username when
- authenticating, you can use any non-empty value because only the token is needed.
+- With the [GitLab API](../../../api/index.md#personalproject-access-tokens).
+- With Git, when using HTTP Basic Authentication.
-Project access tokens:
+After you configure a project access token, you don't need a password when you authenticate.
+Instead, you can enter any non-blank value.
-- Expire on the date you define, at midnight UTC.
-- Are supported for self-managed instances on Free tier and above. Free self-managed instances
- should:
- - Review their security and compliance policies with regards to
+Project access tokens are similar to [personal access tokens](../../profile/personal_access_tokens.md),
+except they are associated with a project rather than a user.
+
+You can use project access tokens:
+
+- On GitLab SaaS if you have the Premium license tier or higher. Personal access tokens are not available with a [trial license](https://about.gitlab.com/free-trial/).
+- On self-managed instances of GitLab, with any license tier. If you have the Free tier:
+ - Review your security and compliance policies around
[user self-enrollment](../../admin_area/settings/sign_up_restrictions.md#disable-new-sign-ups).
- Consider [disabling project access tokens](#enable-or-disable-project-access-token-creation) to
lower potential abuse.
-- Are also supported on GitLab SaaS Premium and above (excluding [trial licenses](https://about.gitlab.com/free-trial/).)
-
-For examples of how you can use a project access token to authenticate with the API, see the
-[relevant section from our API Docs](../../../api/index.md#personalproject-access-tokens).
-
-NOTE:
-For GitLab.com and self-managed instances, the default prefix is `glpat-`.
-
-## Creating a project access token
-
-1. Log in to GitLab.
-1. Navigate to the project you would like to create an access token for.
-1. In the **Settings** menu choose **Access Tokens**.
-1. Choose a name and optional expiry date for the token.
-1. Choose a role for the token.
-1. Choose the [desired scopes](#limiting-scopes-of-a-project-access-token).
-1. Click the **Create project access token** button.
-1. Save the project access token somewhere safe. Once you leave or refresh
- the page, you don't have access to it again.
-
-## Project bot users
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210181) in GitLab 13.0.
-> - [Excluded from license seat use](https://gitlab.com/gitlab-org/gitlab/-/issues/223695) in GitLab 13.5.
-
-Project bot users are [GitLab-created service accounts](../../../subscriptions/self_managed/index.md#billable-users) and do not count as licensed seats.
-For each project access token created, a bot user is created and added to the project with
-the [specified level permissions](../../permissions.md#project-members-permissions).
+## Create a project access token
-For the bot:
-
-- The name is set to the name of the token.
-- The username is set to `project_{project_id}_bot` for the first access token, such as `project_123_bot`.
-- The email is set to `project{project_id}_bot@example.com`, for example `project123_bot@example.com`.
-- For additional access tokens in the same project, the username is set to `project_{project_id}_bot{bot_count}`, for example `project_123_bot1`.
-- For additional access tokens in the same project, the email is set to `project{project_id}_bot{bot_count}@example.com`, for example `project123_bot1@example.com`
+To create a project access token:
-API calls made with a project access token are associated with the corresponding bot user.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > Access Tokens**.
+1. Enter a name.
+1. Optional. Enter an expiry date for the token. The token will expire on that date at midnight UTC.
+1. Select a role for the token.
+1. Select the [desired scopes](#scopes-for-a-project-access-token).
+1. Select **Create project access token**.
-These bot users are included in a project's **Project information > Members** list but cannot be modified. Also, a bot
-user cannot be added to any other project.
+A project access token is displayed. Save the project access token somewhere safe. After you leave or refresh the page, you can't view it again.
-When the project access token is [revoked](#revoking-a-project-access-token), the bot user is deleted
-and all records are moved to a system-wide user with the username "Ghost User". For more
-information, see [Associated Records](../../profile/account/delete_account.md#associated-records).
+## Revoke a project access token
-## Revoking a project access token
+To revoke a project access token:
-At any time, you can revoke any project access token by clicking the
-respective **Revoke** button in **Settings > Access Tokens**.
+1. On the top bar, select **Menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > Access Tokens**.
+1. Next to the project access token to revoke, select **Revoke**.
-## Limiting scopes of a project access token
+## Scopes for a project access token
-Project access tokens can be created with one or more scopes that allow various
-actions that a given token can perform. The available scopes are depicted in
-the following table.
+The scope determines the actions you can perform when you authenticate with a project access token.
-| Scope | Description |
-| ------------------ | ----------- |
-| `api` | Grants complete read/write access to the scoped project API, including the [Package Registry](../../packages/package_registry/index.md). |
-| `read_api` | Grants read access to the scoped project API, including the [Package Registry](../../packages/package_registry/index.md). |
-| `read_registry` | Allows read-access (pull) to [container registry](../../packages/container_registry/index.md) images if a project is private and authorization is required. |
-| `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). |
-| `read_repository` | Allows read-only access (pull) to the repository. |
-| `write_repository` | Allows read-write access (pull, push) to the repository. |
+| Scope | Description |
+|:-------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `api` | Grants complete read and write access to the scoped project API, including the [Package Registry](../../packages/package_registry/index.md). |
+| `read_api` | Grants read access to the scoped project API, including the [Package Registry](../../packages/package_registry/index.md). |
+| `read_registry` | Allows read access (pull) to the [Container Registry](../../packages/container_registry/index.md) images if a project is private and authorization is required. |
+| `write_registry` | Allows write access (push) to the [Container Registry](../../packages/container_registry/index.md). |
+| `read_repository` | Allows read access (pull) to the repository. |
+| `write_repository` | Allows read and write access (pull and push) to the repository. |
## Enable or disable project access token creation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/287707) in GitLab 13.11.
-You may enable or disable project access token creation for all projects in a group in **Group > Settings > General > Permissions, LFS, 2FA > Allow project access token creation**.
-Even when creation is disabled, you can still use and revoke existing project access tokens.
-This setting is available only on top-level groups.
+To enable or disable project access token creation for all projects in a top-level group:
-## Group access token workaround **(FREE SELF)**
+1. On the top bar, select **Menu > Groups** and find your group.
+1. On the left sidebar, select **Settings > General**.
+1. Expand **Permissions, LFS, 2FA**.
+1. Under **Permissions**, turn on or off **Allow project access token creation**.
-NOTE:
-This section describes a workaround and is subject to change.
+Even when creation is disabled, you can still use and revoke existing project access tokens.
+
+## Group access tokens **(FREE SELF)**
-Group access tokens let you use a single token to:
+With group access tokens, you can use a single token to:
-- Perform actions at the group level.
+- Perform actions for groups.
- Manage the projects within the group.
-- In [GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/330718) and later, authenticate
- with Git over HTTPS.
+- In [GitLab 14.2](https://gitlab.com/gitlab-org/gitlab/-/issues/330718) and later, authenticate with Git over HTTPS.
-We don't support group access tokens in the GitLab UI, though GitLab self-managed
-administrators can create them using the [Rails console](../../../administration/operations/rails_console.md).
+NOTE:
+You cannot use the UI to create a group access token. [An issue exists](https://gitlab.com/gitlab-org/gitlab/-/issues/214045)
+to add this functionality. This section describes a workaround.
+
+If you are an administrator of a self-managed GitLab instance, you can create a group access token in the
+[Rails console](../../../administration/operations/rails_console.md).
<div class="video-fallback">
For a demo of the group access token workaround, see <a href="https://www.youtube.com/watch?v=W2fg1P1xmU0">Demo: Group Level Access Tokens</a>.
@@ -127,37 +104,71 @@ administrators can create them using the [Rails console](../../../administration
### Create a group access token
-To create a group access token, run the following in a Rails console:
-
-```ruby
-admin = User.find(1) # group admin
-group = Group.find(109) # the group you want to create a token for
-bot = Users::CreateService.new(admin, { name: 'group_token', username: "group_#{group.id}_bot", email: "group_#{group.id}_bot@example.com", user_type: :project_bot }).execute # create the group bot user
-# for further group access tokens, the username should be group_#{group.id}_bot#{bot_count}, e.g. group_109_bot2, and their email should be group_109_bot2@example.com
-bot.confirm # confirm the bot
-group.add_user(bot, :maintainer) # add the bot to the group at the desired access level
-token = bot.personal_access_tokens.create(scopes:[:api, :write_repository], name: 'group_token') # give it a PAT
-gtoken = token.token # get the token value
-```
+To create a group access token:
-Test if the generated group access token works:
+1. Run the following commands in a [Rails console](../../../administration/operations/rails_console.md):
-1. Pass the group access token in the `PRIVATE-TOKEN` header to GitLab REST APIs. For example:
+ ```ruby
+ admin = User.find(1) # group admin
+ group = Group.find(109) # the group you want to create a token for
+ bot = Users::CreateService.new(admin, { name: 'group_token', username: "group_#{group.id}_bot", email: "group_#{group.id}_bot@example.com", user_type: :project_bot }).execute # create the group bot user
+ # for further group access tokens, the username should be group_#{group.id}_bot#{bot_count}, e.g. group_109_bot2, and their email should be group_109_bot2@example.com
+ bot.confirm # confirm the bot
+ group.add_user(bot, :maintainer) # add the bot to the group at the desired access level
+ token = bot.personal_access_tokens.create(scopes:[:api, :write_repository], name: 'group_token') # give it a PAT
+ gtoken = token.token # get the token value
+ ```
- - [Create an epic](../../../api/epics.md#new-epic) on the group.
- - [Create a project pipeline](../../../api/pipelines.md#create-a-new-pipeline)
- in one of the group's projects.
- - [Create an issue](../../../api/issues.md#new-issue) in one of the group's projects.
+1. Test if the generated group access token works:
-1. Use the group token to [clone a group's project](../../../gitlab-basics/start-using-git.md#clone-with-https)
- using HTTPS.
+ 1. Use the group access token in the `PRIVATE-TOKEN` header with GitLab REST APIs. For example:
+
+ - [Create an epic](../../../api/epics.md#new-epic) in the group.
+ - [Create a project pipeline](../../../api/pipelines.md#create-a-new-pipeline) in one of the group's projects.
+ - [Create an issue](../../../api/issues.md#new-issue) in one of the group's projects.
+
+ 1. Use the group token to [clone a group's project](../../../gitlab-basics/start-using-git.md#clone-with-https)
+ using HTTPS.
### Revoke a group access token
-To revoke a group access token, run the following in a Rails console:
+To revoke a group access token, run the following command in a [Rails console](../../../administration/operations/rails_console.md):
```ruby
bot = User.find_by(username: 'group_109_bot') # the owner of the token you want to revoke
token = bot.personal_access_tokens.last # the token you want to revoke
token.revoke!
```
+
+## Project bot users
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/210181) in GitLab 13.0.
+> - [Excluded from license seat use](https://gitlab.com/gitlab-org/gitlab/-/issues/223695) in GitLab 13.5.
+
+Project bot users are [GitLab-created service accounts](../../../subscriptions/self_managed/index.md#billable-users).
+Each time you create a project access token, a bot user is created and added to the project.
+These bot users do not count as licensed seats.
+
+The bot users have [permissions](../../permissions.md#project-members-permissions) that correspond with the
+selected role and [scope](#scopes-for-a-project-access-token) of the project access token.
+
+- The name is set to the name of the token.
+- The username is set to `project_{project_id}_bot` for the first access token. For example, `project_123_bot`.
+- The email is set to `project{project_id}_bot@example.com`. For example, `project123_bot@example.com`.
+- For additional access tokens in the same project, the username is set to `project_{project_id}_bot{bot_count}`. For
+ example, `project_123_bot1`.
+- For additional access tokens in the same project, the email is set to `project{project_id}_bot{bot_count}@example.com`.
+ For example, `project123_bot1@example.com`.
+
+API calls made with a project access token are associated with the corresponding bot user.
+
+Bot users:
+
+- Are included in a project's member list but cannot be modified.
+- Cannot be added to any other project.
+
+When the project access token is [revoked](#revoke-a-project-access-token):
+
+- The bot user is deleted.
+- All records are moved to a system-wide user with the username `Ghost User`. For more information, see
+ [associated records](../../profile/account/delete_account.md#associated-records).
diff --git a/lib/feature.rb b/lib/feature.rb
index 1a299323dda..f301f206b46 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -159,7 +159,7 @@ class Feature
end
def log_feature_flag_states?(key)
- key != :feature_flag_state_logs && Feature.enabled?(:feature_flag_state_logs, type: :ops)
+ Feature::Definition.log_states?(key)
end
def log_feature_flag_state(key, feature_value)
diff --git a/lib/feature/definition.rb b/lib/feature/definition.rb
index cd2f5cb07a2..61f7e395769 100644
--- a/lib/feature/definition.rb
+++ b/lib/feature/definition.rb
@@ -82,6 +82,16 @@ class Feature
attributes
end
+ def for_upcoming_milestone?
+ return false unless milestone
+
+ Gitlab::VersionInfo.parse(milestone + '.999') >= Gitlab.version_info
+ end
+
+ def force_log_state_changes?
+ attributes[:log_state_changes]
+ end
+
class << self
def paths
@paths ||= [Rails.root.join('config', 'feature_flags', '**', '*.yml')]
@@ -106,6 +116,14 @@ class Feature
definitions.has_key?(key.to_sym)
end
+ def log_states?(key)
+ return false if key == :feature_flag_state_logs
+ return false if Feature.disabled?(:feature_flag_state_logs, type: :ops)
+ return false unless (feature = get(key))
+
+ feature.force_log_state_changes? || feature.for_upcoming_milestone?
+ end
+
def valid_usage!(key, type:, default_enabled:)
if definition = get(key)
definition.valid_usage!(type_in_code: type, default_enabled_in_code: default_enabled)
diff --git a/lib/feature/shared.rb b/lib/feature/shared.rb
index 70e5b523adf..2ce078b2f02 100644
--- a/lib/feature/shared.rb
+++ b/lib/feature/shared.rb
@@ -58,6 +58,7 @@ class Feature
introduced_by_url
rollout_issue_url
milestone
+ log_state_changes
type
group
default_enabled
diff --git a/lib/gitlab/database/gitlab_loose_foreign_keys.yml b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
index 782ce61b8ad..3abd2023f14 100644
--- a/lib/gitlab/database/gitlab_loose_foreign_keys.yml
+++ b/lib/gitlab/database/gitlab_loose_foreign_keys.yml
@@ -45,3 +45,7 @@ terraform_state_versions:
- table: ci_builds
column: ci_build_id
on_delete: async_nullify
+merge_request_metrics:
+ - table: ci_pipelines
+ column: pipeline_id
+ on_delete: async_delete
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0c45b78f073..715bf92bd76 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -14142,9 +14142,6 @@ msgstr ""
msgid "Every year on %{day} at %{time} %{timezone}"
msgstr ""
-msgid "Everyone"
-msgstr ""
-
msgid "Everyone With Access"
msgstr ""
@@ -15045,9 +15042,6 @@ msgstr ""
msgid "Filter by test cases that are currently open."
msgstr ""
-msgid "Filter by two-factor authentication"
-msgstr ""
-
msgid "Filter by user"
msgstr ""
diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb
index e01c5522b51..5146fe3e752 100644
--- a/spec/experiments/application_experiment_spec.rb
+++ b/spec/experiments/application_experiment_spec.rb
@@ -233,6 +233,75 @@ RSpec.describe ApplicationExperiment, :experiment do
]
)
end
+
+ context "when using known context resources" do
+ let(:user) { build(:user, id: non_existing_record_id) }
+ let(:project) { build(:project, id: non_existing_record_id) }
+ let(:namespace) { build(:namespace, id: non_existing_record_id) }
+ let(:group) { build(:group, id: non_existing_record_id) }
+ let(:actor) { user }
+
+ let(:context) { { user: user, project: project, namespace: namespace } }
+
+ it "includes those using the gitlab standard context" do
+ subject.track(:action)
+
+ expect_snowplow_event(
+ category: 'namespaced/stub',
+ action: 'action',
+ user: user,
+ project: project,
+ namespace: namespace,
+ context: an_instance_of(Array)
+ )
+ end
+
+ it "falls back to using the group key" do
+ subject.context(namespace: nil, group: group)
+
+ subject.track(:action)
+
+ expect_snowplow_event(
+ category: 'namespaced/stub',
+ action: 'action',
+ user: user,
+ project: project,
+ namespace: group,
+ context: an_instance_of(Array)
+ )
+ end
+
+ context "with the actor key" do
+ it "provides it to the tracking call as the user" do
+ subject.context(user: nil, actor: actor)
+
+ subject.track(:action)
+
+ expect_snowplow_event(
+ category: 'namespaced/stub',
+ action: 'action',
+ user: actor,
+ project: project,
+ namespace: namespace,
+ context: an_instance_of(Array)
+ )
+ end
+
+ it "handles when it's not a user record" do
+ subject.context(user: nil, actor: nil)
+
+ subject.track(:action)
+
+ expect_snowplow_event(
+ category: 'namespaced/stub',
+ action: 'action',
+ project: project,
+ namespace: namespace,
+ context: an_instance_of(Array)
+ )
+ end
+ end
+ end
end
describe "#key_for" do
diff --git a/spec/frontend/ci_lint/components/ci_lint_spec.js b/spec/frontend/ci_lint/components/ci_lint_spec.js
index 36d860b1ccd..70d116c12d3 100644
--- a/spec/frontend/ci_lint/components/ci_lint_spec.js
+++ b/spec/frontend/ci_lint/components/ci_lint_spec.js
@@ -3,7 +3,7 @@ import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises';
import CiLint from '~/ci_lint/components/ci_lint.vue';
import CiLintResults from '~/pipeline_editor/components/lint/ci_lint_results.vue';
-import lintCIMutation from '~/pipeline_editor/graphql/mutations/lint_ci.mutation.graphql';
+import lintCIMutation from '~/pipeline_editor/graphql/mutations/client/lint_ci.mutation.graphql';
import SourceEditor from '~/vue_shared/components/source_editor.vue';
import { mockLintDataValid } from '../mock_data';
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index d250eb7f6ad..3cf6240c2c5 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -6,7 +6,7 @@ import dismissUserCallout from '~/graphql_shared/mutations/dismiss_user_callout.
import services from '~/ide/services';
import { query, mutate } from '~/ide/services/gql';
import { escapeFileUrl } from '~/lib/utils/url_utility';
-import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.graphql';
+import ciConfig from '~/pipeline_editor/graphql/queries/ci_config.query.graphql';
import { projectData } from '../mock_data';
jest.mock('~/api');
diff --git a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
index 7ec322d355f..bc77b7045eb 100644
--- a/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
+++ b/spec/frontend/pipeline_editor/components/commit/commit_section_spec.js
@@ -12,7 +12,7 @@ import {
COMMIT_SUCCESS,
} from '~/pipeline_editor/constants';
import commitCreate from '~/pipeline_editor/graphql/mutations/commit_ci_file.mutation.graphql';
-import updatePipelineEtag from '~/pipeline_editor/graphql/mutations/update_pipeline_etag.mutation.graphql';
+import updatePipelineEtag from '~/pipeline_editor/graphql/mutations/client/update_pipeline_etag.mutation.graphql';
import {
mockCiConfigPath,
diff --git a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
index 6532c4e289d..3235842dca3 100644
--- a/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
+++ b/spec/frontend/pipeline_editor/components/file-nav/branch_switcher_spec.js
@@ -11,7 +11,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import BranchSwitcher from '~/pipeline_editor/components/file_nav/branch_switcher.vue';
import { DEFAULT_FAILURE } from '~/pipeline_editor/constants';
-import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.graphql';
+import getAvailableBranchesQuery from '~/pipeline_editor/graphql/queries/available_branches.query.graphql';
import {
mockBranchPaginationLimit,
mockDefaultBranch,
diff --git a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
index a30edb4778b..c101b1d21c7 100644
--- a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
+++ b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js
@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import PipelineStatus, { i18n } from '~/pipeline_editor/components/header/pipeline_status.vue';
-import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
+import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.graphql';
import PipelineEditorMiniGraph from '~/pipeline_editor/components/header/pipeline_editor_mini_graph.vue';
import { mockCommitSha, mockProjectPipeline, mockProjectFullPath } from '../../mock_data';
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index b8755e2ced1..09d7d4f7ca6 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -9,12 +9,11 @@ import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tab
import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue';
import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_editor_messages.vue';
import { COMMIT_SUCCESS, COMMIT_FAILURE, LOAD_FAILURE_UNKNOWN } from '~/pipeline_editor/constants';
-import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql';
-import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
+import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.query.graphql';
+import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.query.graphql';
import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql';
import getLatestCommitShaQuery from '~/pipeline_editor/graphql/queries/latest_commit_sha.query.graphql';
-
-import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql';
+import getPipelineQuery from '~/pipeline_editor/graphql/queries/pipeline.query.graphql';
import PipelineEditorApp from '~/pipeline_editor/pipeline_editor_app.vue';
import PipelineEditorHome from '~/pipeline_editor/pipeline_editor_home.vue';
diff --git a/spec/lib/feature/definition_spec.rb b/spec/lib/feature/definition_spec.rb
index 21120012927..2f95f8eeab7 100644
--- a/spec/lib/feature/definition_spec.rb
+++ b/spec/lib/feature/definition_spec.rb
@@ -161,6 +161,41 @@ RSpec.describe Feature::Definition do
end
end
+ describe '.for_upcoming_milestone?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:definition) do
+ Feature::Definition.new("development/enabled_feature_flag.yml",
+ name: :enabled_feature_flag,
+ type: 'development',
+ milestone: milestone,
+ default_enabled: false)
+ end
+
+ before do
+ allow(Feature::Definition).to receive(:definitions) do
+ { definition.key => definition }
+ end
+
+ allow(Gitlab).to receive(:version_info).and_return(Gitlab::VersionInfo.parse(current_milestone))
+ end
+
+ subject { definition.for_upcoming_milestone? }
+
+ where(:ctx, :milestone, :current_milestone, :expected) do
+ 'no milestone' | nil | '1.0.0' | false
+ 'upcoming milestone - major' | '2.3' | '1.9.999' | true
+ 'upcoming milestone - minor' | '2.3' | '2.2.999' | true
+ 'current milestone' | '2.3' | '2.3.999' | true
+ 'past milestone - major' | '1.9' | '2.3.999' | false
+ 'past milestone - minor' | '2.2' | '2.3.999' | false
+ end
+
+ with_them do
+ it {is_expected.to be(expected)}
+ end
+ end
+
describe '.valid_usage!' do
before do
allow(described_class).to receive(:definitions) do
@@ -215,7 +250,42 @@ RSpec.describe Feature::Definition do
end
end
- describe '.defaul_enabled?' do
+ describe '.log_states?' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:definition) do
+ Feature::Definition.new("development/enabled_feature_flag.yml",
+ name: :enabled_feature_flag,
+ type: 'development',
+ milestone: milestone,
+ log_state_changes: log_state_change,
+ default_enabled: false)
+ end
+
+ before do
+ allow(Feature::Definition).to receive(:definitions) do
+ { definition.key => definition }
+ end
+
+ allow(Gitlab).to receive(:version_info).and_return(Gitlab::VersionInfo.new(10, 0, 0))
+ end
+
+ subject { Feature::Definition.log_states?(key) }
+
+ where(:ctx, :key, :milestone, :log_state_change, :expected) do
+ 'When flag does not exist' | :no_flag | "0.0" | true | false
+ 'When flag is old, and logging is not forced' | :enabled_feature_flag | "0.0" | false | false
+ 'When flag is old, but logging is forced' | :enabled_feature_flag | "0.0" | true | true
+ 'When flag is current' | :enabled_feature_flag | "10.0" | true | true
+ 'Flag is upcoming' | :enabled_feature_flag | "10.0" | true | true
+ end
+
+ with_them do
+ it { is_expected.to be(expected) }
+ end
+ end
+
+ describe '.default_enabled?' do
subject { described_class.default_enabled?(key) }
context 'when feature flag exist' do
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 2c4b0a85537..82580d5d700 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -186,6 +186,17 @@ RSpec.describe Feature, stub_feature_flags: false do
context 'logging is enabled', :request_store do
before do
allow(Feature).to receive(:log_feature_flag_states?).and_call_original
+
+ definition = Feature::Definition.new("development/enabled_feature_flag.yml",
+ name: :enabled_feature_flag,
+ type: 'development',
+ log_state_changes: true,
+ default_enabled: false)
+
+ allow(Feature::Definition).to receive(:definitions) do
+ { definition.key => definition }
+ end
+
described_class.enable(:feature_flag_state_logs)
described_class.enable(:enabled_feature_flag)
described_class.enabled?(:enabled_feature_flag)
@@ -513,6 +524,82 @@ RSpec.describe Feature, stub_feature_flags: false do
end
end
+ describe '.log_feature_flag_states?' do
+ let(:log_state_changes) { false }
+ let(:milestone) { "0.0" }
+ let(:flag_name) { :some_flag }
+ let(:definition) do
+ Feature::Definition.new("development/#{flag_name}.yml",
+ name: flag_name,
+ type: 'development',
+ milestone: milestone,
+ log_state_changes: log_state_changes,
+ default_enabled: false)
+ end
+
+ before do
+ Feature.enable(:feature_flag_state_logs)
+ Feature.enable(:some_flag)
+
+ allow(Feature).to receive(:log_feature_flag_states?).and_return(false)
+ allow(Feature).to receive(:log_feature_flag_states?).with(:feature_flag_state_logs).and_call_original
+ allow(Feature).to receive(:log_feature_flag_states?).with(:some_flag).and_call_original
+
+ allow(Feature::Definition).to receive(:definitions) do
+ { definition.key => definition }
+ end
+ end
+
+ subject { described_class.log_feature_flag_states?(flag_name) }
+
+ context 'when flag is feature_flag_state_logs' do
+ let(:milestone) { "14.6" }
+ let(:flag_name) { :feature_flag_state_logs }
+ let(:log_state_changes) { true }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when flag is old' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when flag is old while log_state_changes is not present ' do
+ let(:definition) do
+ Feature::Definition.new("development/#{flag_name}.yml",
+ name: flag_name,
+ type: 'development',
+ milestone: milestone,
+ default_enabled: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when flag is old but log_state_changes is true' do
+ let(:log_state_changes) { true }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when flag is new and not feature_flag_state_logs' do
+ let(:milestone) { "14.6" }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when milestone is nil' do
+ let(:definition) do
+ Feature::Definition.new("development/#{flag_name}.yml",
+ name: flag_name,
+ type: 'development',
+ default_enabled: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
context 'caching with stale reads from the database', :use_clean_rails_redis_caching, :request_store, :aggregate_failures do
let(:actor) { stub_feature_flag_gate('CustomActor:5') }
let(:another_actor) { stub_feature_flag_gate('CustomActor:10') }
diff --git a/spec/lib/gitlab/lograge/custom_options_spec.rb b/spec/lib/gitlab/lograge/custom_options_spec.rb
index 5c9cd86ce7d..d8f351bb8a3 100644
--- a/spec/lib/gitlab/lograge/custom_options_spec.rb
+++ b/spec/lib/gitlab/lograge/custom_options_spec.rb
@@ -98,15 +98,23 @@ RSpec.describe Gitlab::Lograge::CustomOptions do
context 'when feature flags are present', :request_store do
before do
- allow(Feature::Definition).to receive(:valid_usage!).and_return(true)
-
allow(Feature).to receive(:log_feature_flag_states?).and_return(false)
+ definitions = {}
+ [:enabled_feature, :disabled_feature].each do |flag_name|
+ definitions[flag_name] = Feature::Definition.new("development/enabled_feature.yml",
+ name: flag_name,
+ type: 'development',
+ log_state_changes: true,
+ default_enabled: false)
+
+ allow(Feature).to receive(:log_feature_flag_states?).with(flag_name).and_call_original
+ end
+
+ allow(Feature::Definition).to receive(:definitions).and_return(definitions)
+
Feature.enable(:enabled_feature)
Feature.disable(:disabled_feature)
-
- allow(Feature).to receive(:log_feature_flag_states?).with(:enabled_feature).and_call_original
- allow(Feature).to receive(:log_feature_flag_states?).with(:disabled_feature).and_call_original
end
context 'and :feature_flag_log_states is enabled' do
diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb
index 13ff239a306..a4bdac39074 100644
--- a/spec/models/merge_request/metrics_spec.rb
+++ b/spec/models/merge_request/metrics_spec.rb
@@ -48,4 +48,10 @@ RSpec.describe MergeRequest::Metrics do
end
end
end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:merge_request) { create(:merge_request) }
+ let!(:parent) { create(:ci_pipeline, project: merge_request.target_project) }
+ let!(:model) { merge_request.metrics.tap { |metrics| metrics.update!(pipeline: parent) } }
+ end
end
diff --git a/spec/models/terraform/state_version_spec.rb b/spec/models/terraform/state_version_spec.rb
index ac2e8d167b3..7af9b7897ff 100644
--- a/spec/models/terraform/state_version_spec.rb
+++ b/spec/models/terraform/state_version_spec.rb
@@ -92,4 +92,9 @@ RSpec.describe Terraform::StateVersion do
end
end
end
+
+ it_behaves_like 'cleanup by a loose foreign key' do
+ let!(:model) { create(:terraform_state_version) }
+ let!(:parent) { model.build }
+ end
end
diff --git a/spec/support/helpers/snowplow_helpers.rb b/spec/support/helpers/snowplow_helpers.rb
index 553739b5d30..c8b194919ed 100644
--- a/spec/support/helpers/snowplow_helpers.rb
+++ b/spec/support/helpers/snowplow_helpers.rb
@@ -48,11 +48,15 @@ module SnowplowHelpers
# )
def expect_snowplow_event(category:, action:, context: nil, **kwargs)
if context
- kwargs[:context] = []
- context.each do |c|
- expect(SnowplowTracker::SelfDescribingJson).to have_received(:new)
- .with(c[:schema], c[:data]).at_least(:once)
- kwargs[:context] << an_instance_of(SnowplowTracker::SelfDescribingJson)
+ if context.is_a?(Array)
+ kwargs[:context] = []
+ context.each do |c|
+ expect(SnowplowTracker::SelfDescribingJson).to have_received(:new)
+ .with(c[:schema], c[:data]).at_least(:once)
+ kwargs[:context] << an_instance_of(SnowplowTracker::SelfDescribingJson)
+ end
+ else
+ kwargs[:context] = context
end
end
diff --git a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
index 8c98254e134..8f3a93de509 100644
--- a/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
+++ b/spec/support/shared_examples/loose_foreign_keys/have_loose_foreign_key.rb
@@ -47,3 +47,28 @@ RSpec.shared_examples 'it has loose foreign keys' do
expect(deleted_records.status_processed.count).to be(1)
end
end
+
+RSpec.shared_examples 'cleanup by a loose foreign key' do
+ let(:foreign_key_definition) do
+ foreign_keys_for_parent = Gitlab::Database::LooseForeignKeys.definitions_by_table[parent.class.table_name]
+ foreign_keys_for_parent.find { |definition| definition.from_table == model.class.table_name }
+ end
+
+ def find_model
+ model.class.find_by(id: model.id)
+ end
+
+ it 'deletes the model' do
+ parent.delete
+
+ expect(find_model).to be_present
+
+ LooseForeignKeys::ProcessDeletedRecordsService.new(connection: model.connection).execute
+
+ if foreign_key_definition.on_delete.eql?(:async_delete)
+ expect(find_model).not_to be_present
+ else
+ expect(find_model[foreign_key_definition.column]).to eq(nil)
+ end
+ end
+end