diff options
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 |