Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
0c6a209989
commit
30f908f6b9
51 changed files with 491 additions and 171 deletions
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
1
db/schema_migrations/20211213102111
Normal file
1
db/schema_migrations/20211213102111
Normal file
|
@ -0,0 +1 @@
|
|||
3d011cc67fc6ac661788f2d0e3766e51d624a4248ac9dbd861a4db810d396091
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 |
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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).
|
||||
## Create a project access token
|
||||
|
||||
NOTE:
|
||||
For GitLab.com and self-managed instances, the default prefix is `glpat-`.
|
||||
To create a project access token:
|
||||
|
||||
## Creating a project access token
|
||||
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**.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Project bot users
|
||||
## Revoke a project access token
|
||||
|
||||
> - [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.
|
||||
To revoke a project access token:
|
||||
|
||||
Project bot users are [GitLab-created service accounts](../../../subscriptions/self_managed/index.md#billable-users) and do not count as licensed seats.
|
||||
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**.
|
||||
|
||||
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).
|
||||
## Scopes for a project access token
|
||||
|
||||
For the bot:
|
||||
The scope determines the actions you can perform when you authenticate with a 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, 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`
|
||||
|
||||
API calls made with a project access token are associated with the corresponding bot user.
|
||||
|
||||
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.
|
||||
|
||||
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).
|
||||
|
||||
## Revoking a project access token
|
||||
|
||||
At any time, you can revoke any project access token by clicking the
|
||||
respective **Revoke** button in **Settings > Access Tokens**.
|
||||
|
||||
## Limiting scopes of 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.
|
||||
|
||||
| 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**.
|
||||
|
||||
Even when creation is disabled, you can still use and revoke existing project access tokens.
|
||||
|
||||
## Group access tokens **(FREE SELF)**
|
||||
|
||||
With group access tokens, you can use a single token to:
|
||||
|
||||
- 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.
|
||||
|
||||
NOTE:
|
||||
This section describes a workaround and is subject to change.
|
||||
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.
|
||||
|
||||
Group access tokens let you use a single token to:
|
||||
|
||||
- Perform actions at the group level.
|
||||
- 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.
|
||||
|
||||
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).
|
||||
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:
|
||||
To create a group access token:
|
||||
|
||||
```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
|
||||
```
|
||||
1. Run the following commands in a [Rails console](../../../administration/operations/rails_console.md):
|
||||
|
||||
Test if the generated group access token works:
|
||||
```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
|
||||
```
|
||||
|
||||
1. Pass the group access token in the `PRIVATE-TOKEN` header to GitLab REST APIs. For example:
|
||||
1. Test if the generated group access token works:
|
||||
|
||||
- [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. 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).
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -58,6 +58,7 @@ class Feature
|
|||
introduced_by_url
|
||||
rollout_issue_url
|
||||
milestone
|
||||
log_state_changes
|
||||
type
|
||||
group
|
||||
default_enabled
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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') }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue