diff options
33 files changed, 447 insertions, 321 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml index b42b32ea44d..49fac128096 100644 --- a/.gitlab/ci/docs.gitlab-ci.yml +++ b/.gitlab/ci/docs.gitlab-ci.yml @@ -10,17 +10,18 @@ # because some repos are private and CI_JOB_TOKEN cannot access files. # See https://gitlab.com/gitlab-org/gitlab/issues/191273 GIT_DEPTH: 1 + # By default, deploy the Review App using the `master` branch of the `gitlab-org/gitlab-docs` project + DOCS_BRANCH: master environment: - name: review-docs/$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID + name: review-docs/mr-${CI_MERGE_REQUEST_IID} # DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables # Discussion: https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/14236/diffs#note_40140693 auto_stop_in: 2 weeks - url: http://docs-preview-$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX + url: http://${DOCS_BRANCH}-${DOCS_GITLAB_REPO_SUFFIX}-${CI_MERGE_REQUEST_IID}.${DOCS_REVIEW_APPS_DOMAIN}/${DOCS_GITLAB_REPO_SUFFIX} on_stop: review-docs-cleanup before_script: - - apk add --update openssl - - gem install httparty --no-document --version 0.17.3 - - gem install gitlab --no-document --version 4.13.0 + - source ./scripts/utils.sh + - install_gitlab_gem # Always trigger a docs build in gitlab-docs only on docs-only branches. # Useful to preview the docs changes live. @@ -33,7 +34,7 @@ review-docs-deploy: review-docs-cleanup: extends: .review-docs environment: - name: review-docs/$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID + name: review-docs/mr-${CI_MERGE_REQUEST_IID} action: stop script: - ./scripts/trigger-build docs cleanup diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 9c35e8bc841..6916fad7b04 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0cc0f3d488f96261608d7c06261be8a0cce0d668 +eb6ecf6f96946849761c1fcdcaf0bda15fc000f9 diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue index b088678fee8..f6e88738002 100644 --- a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue +++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue @@ -124,7 +124,7 @@ export default { type="submit" class="js-no-auto-disable" category="primary" - variant="success" + variant="confirm" :disabled="submitDisabled" :loading="isSaving" > 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 b1ea464be99..a8fbec3a563 100644 --- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue +++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue @@ -1,9 +1,11 @@ <script> import { GlIcon, GlLink, GlLoadingIcon, GlSprintf } from '@gitlab/ui'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { truncateSha } from '~/lib/utils/text_utility'; import { s__ } from '~/locale'; import getCommitSha from '~/pipeline_editor/graphql/queries/client/commit_sha.graphql'; import getPipelineQuery from '~/pipeline_editor/graphql/queries/client/pipeline.graphql'; +import { toggleQueryPollingByVisibility } from '~/pipelines/components/graph/utils'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; const POLL_INTERVAL = 10000; @@ -38,13 +40,11 @@ export default { }; }, update: (data) => { - const { id, commitPath = '', shortSha = '', detailedStatus = {} } = - data.project?.pipeline || {}; + const { id, commitPath = '', detailedStatus = {} } = data.project?.pipeline || {}; return { id, commitPath, - shortSha, detailedStatus, }; }, @@ -61,24 +61,34 @@ export default { }, computed: { hasPipelineData() { - return Boolean(this.$apollo.queries.pipeline?.id); + return Boolean(this.pipeline?.id); }, - isQueryLoading() { - return this.$apollo.queries.pipeline.loading && !this.hasPipelineData; + pipelineId() { + return getIdFromGraphQLId(this.pipeline.id); + }, + showLoadingState() { + // the query is set to poll regularly, so if there is no pipeline data + // (e.g. pipeline is null during fetch when the pipeline hasn't been + // triggered yet), we can just show the loading state until the pipeline + // details are ready to be fetched + return this.$apollo.queries.pipeline.loading || (!this.hasPipelineData && !this.hasError); + }, + shortSha() { + return truncateSha(this.commitSha); }, status() { return this.pipeline.detailedStatus; }, - pipelineId() { - return getIdFromGraphQLId(this.pipeline.id); - }, + }, + mounted() { + toggleQueryPollingByVisibility(this.$apollo.queries.pipeline, POLL_INTERVAL); }, }; </script> <template> <div class="gl-white-space-nowrap gl-max-w-full"> - <template v-if="isQueryLoading"> + <template v-if="showLoadingState"> <gl-loading-icon class="gl-mr-auto gl-display-inline-block" size="sm" /> <span data-testid="pipeline-loading-msg">{{ $options.i18n.fetchLoading }}</span> </template> @@ -110,7 +120,7 @@ export default { target="_blank" data-testid="pipeline-commit" > - {{ pipeline.shortSha }} + {{ shortSha }} </gl-link> </template> </gl-sprintf> diff --git a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql index 7cc7f92fb60..d3a7387ad2d 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql +++ b/app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql @@ -1,10 +1,9 @@ query getPipeline($fullPath: ID!, $sha: String!) { - project(fullPath: $fullPath) @client { + project(fullPath: $fullPath) { pipeline(sha: $sha) { commitPath id iid - shortSha status detailedStatus { detailsPath diff --git a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js index 13f6200693b..81e75c32846 100644 --- a/app/assets/javascripts/pipeline_editor/graphql/resolvers.js +++ b/app/assets/javascripts/pipeline_editor/graphql/resolvers.js @@ -11,29 +11,6 @@ export const resolvers = { }), }; }, - - /* eslint-disable @gitlab/require-i18n-strings */ - project() { - return { - __typename: 'Project', - pipeline: { - __typename: 'Pipeline', - commitPath: `/-/commit/aabbccdd`, - id: 'gid://gitlab/Ci::Pipeline/118', - iid: '28', - shortSha: 'aabbccdd', - status: 'SUCCESS', - detailedStatus: { - __typename: 'DetailedStatus', - detailsPath: '/root/sample-ci-project/-/pipelines/118"', - group: 'success', - icon: 'status_success', - text: 'passed', - }, - }, - }; - }, - /* eslint-enable @gitlab/require-i18n-strings */ }, Mutation: { lintCI: (_, { endpoint, content, dry_run }) => { diff --git a/app/graphql/mutations/release_asset_links/delete.rb b/app/graphql/mutations/release_asset_links/delete.rb new file mode 100644 index 00000000000..dd450f36cdd --- /dev/null +++ b/app/graphql/mutations/release_asset_links/delete.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Mutations + module ReleaseAssetLinks + class Delete < BaseMutation + graphql_name 'ReleaseAssetLinkDelete' + + authorize :destroy_release + + ReleaseAssetLinkID = ::Types::GlobalIDType[::Releases::Link] + + argument :id, ReleaseAssetLinkID, + required: true, + description: 'ID of the release asset link to delete.' + + field :link, + Types::ReleaseAssetLinkType, + null: true, + description: 'The deleted release asset link.' + + def resolve(id:) + link = authorized_find!(id) + + unless link.destroy + return { link: nil, errors: link.errors.full_messages } + end + + { link: link, errors: [] } + end + + def find_object(id) + # TODO: remove this line when the compatibility layer is removed + # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883 + id = ReleaseAssetLinkID.coerce_isolated_input(id) + + GitlabSchema.find_by_gid(id) + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 76ffddf416f..5a9c7b32deb 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -68,6 +68,7 @@ module Types mount_mutation Mutations::Releases::Delete mount_mutation Mutations::ReleaseAssetLinks::Create mount_mutation Mutations::ReleaseAssetLinks::Update + mount_mutation Mutations::ReleaseAssetLinks::Delete mount_mutation Mutations::Terraform::State::Delete mount_mutation Mutations::Terraform::State::Lock mount_mutation Mutations::Terraform::State::Unlock diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml index 8a369202555..7def4e97ce9 100644 --- a/app/views/projects/pipeline_schedules/_form.html.haml +++ b/app/views/projects/pipeline_schedules/_form.html.haml @@ -39,5 +39,5 @@ = f.check_box :active, required: false, value: @schedule.active? = f.label :active, _('Active'), class: 'gl-font-weight-normal' .footer-block.row-content-block - = f.submit _('Save pipeline schedule'), class: 'btn gl-button btn-success' + = f.submit _('Save pipeline schedule'), class: 'btn gl-button btn-confirm' = link_to _('Cancel'), pipeline_schedules_path(@project), class: 'btn gl-button btn-default btn-cancel' diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index 558c12c04e4..a56e8f7f5c7 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -9,7 +9,7 @@ - if can?(current_user, :create_pipeline_schedule, @project) .nav-controls - = link_to new_project_pipeline_schedule_path(@project), class: 'btn gl-button btn-success' do + = link_to new_project_pipeline_schedule_path(@project), class: 'btn gl-button btn-confirm' do %span= _('New schedule') - if @schedules.present? diff --git a/changelogs/unreleased/btn-confirm-pipeline_editor.yml b/changelogs/unreleased/btn-confirm-pipeline_editor.yml new file mode 100644 index 00000000000..6484bf76655 --- /dev/null +++ b/changelogs/unreleased/btn-confirm-pipeline_editor.yml @@ -0,0 +1,5 @@ +--- +title: Move to confirm varient from success in pipeline_editor directory +merge_request: 56200 +author: Yogi (@yo) +type: changed diff --git a/changelogs/unreleased/btn-confirm-schedules.yml b/changelogs/unreleased/btn-confirm-schedules.yml new file mode 100644 index 00000000000..d3bc28d3555 --- /dev/null +++ b/changelogs/unreleased/btn-confirm-schedules.yml @@ -0,0 +1,5 @@ +--- +title: Move from btn-success to btn-confirm in pipeline_schedules directory +merge_request: 56201 +author: Yogi (@yo) +type: changed diff --git a/changelogs/unreleased/nfriend-add-release-asset-link-delete-mutation.yml b/changelogs/unreleased/nfriend-add-release-asset-link-delete-mutation.yml new file mode 100644 index 00000000000..3b39bb351c3 --- /dev/null +++ b/changelogs/unreleased/nfriend-add-release-asset-link-delete-mutation.yml @@ -0,0 +1,5 @@ +--- +title: Add GraphQL mutation to delete an existing release asset link +merge_request: 56417 +author: +type: added diff --git a/changelogs/unreleased/update-ci-template-default-branch-5.yml b/changelogs/unreleased/update-ci-template-default-branch-5.yml new file mode 100644 index 00000000000..a89f0a85cdf --- /dev/null +++ b/changelogs/unreleased/update-ci-template-default-branch-5.yml @@ -0,0 +1,5 @@ +--- +title: Update android template to default branch +merge_request: 56738 +author: +type: other diff --git a/config/initializers/active_record_preloader.rb b/config/initializers/active_record_preloader.rb index 349ca6c4831..257a8a9e955 100644 --- a/config/initializers/active_record_preloader.rb +++ b/config/initializers/active_record_preloader.rb @@ -9,6 +9,7 @@ module ActiveRecord end def self.run + self end def self.preloaded_records diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 7039ee9fab0..2e68806f68a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -4709,7 +4709,7 @@ An edge in a connection. | `alertManagementIntegrations` | [`AlertManagementIntegrationConnection`](#alertmanagementintegrationconnection) | Integrations which can receive alerts for the project. | | `alertManagementPayloadFields` | [`[AlertManagementPayloadAlertField!]`](#alertmanagementpayloadalertfield) | Extract alert fields from payload for custom mapping. | | `allowMergeOnSkippedPipeline` | [`Boolean`](#boolean) | If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs. | -| `apiFuzzingCiConfiguration` | [`ApiFuzzingCiConfiguration`](#apifuzzingciconfiguration) | API fuzzing configuration for the project. Available only when feature flag `api_fuzzing_configuration_ui` is enabled. | +| `apiFuzzingCiConfiguration` | [`ApiFuzzingCiConfiguration`](#apifuzzingciconfiguration) | API fuzzing configuration for the project. Null unless feature flag `api_fuzzing_configuration_ui` is enabled. | | `archived` | [`Boolean`](#boolean) | Indicates the archived status of the project. | | `autocloseReferencedIssues` | [`Boolean`](#boolean) | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically. | | `avatarUrl` | [`String`](#string) | URL to avatar image file of the project. | @@ -5048,6 +5048,16 @@ Autogenerated return type of ReleaseAssetLinkCreate. | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | `link` | [`ReleaseAssetLink`](#releaseassetlink) | The asset link after mutation. | +### `ReleaseAssetLinkDeletePayload` + +Autogenerated return type of ReleaseAssetLinkDelete. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| `link` | [`ReleaseAssetLink`](#releaseassetlink) | The deleted release asset link. | + ### `ReleaseAssetLinkEdge` An edge in a connection. diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 53e7ba35831..fe60c963f7f 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -387,11 +387,13 @@ To preview your changes to documentation locally, follow this The live preview is currently enabled for the following projects: - [`gitlab`](https://gitlab.com/gitlab-org/gitlab) +- [`omnibus-gitlab`](https://gitlab.com/gitlab-org/omnibus-gitlab) - [`gitlab-runner`](https://gitlab.com/gitlab-org/gitlab-runner) If your merge request has docs changes, you can use the manual `review-docs-deploy` job to deploy the docs review app for your merge request. -You need at least Maintainer permissions to be able to run it. +As the job creates a pipeline in `gitlab-org/gitlab-docs`, you need at least Maintainer +permission in the `gitlab-org/gitlab-docs` project in order to successfully run the job. ![Manual trigger a docs build](img/manual_build_docs.png) @@ -399,11 +401,6 @@ You must push a branch to those repositories, as it doesn't work for forks. The `review-docs-deploy*` job: -1. Creates a new branch in the [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) - project named after the scheme: `docs-preview-$DOCS_GITLAB_REPO_SUFFIX-$CI_MERGE_REQUEST_IID`, - where `DOCS_GITLAB_REPO_SUFFIX` is the suffix for each product, e.g, `ee` for - EE, `omnibus` for Omnibus GitLab, etc, and `CI_MERGE_REQUEST_IID` is the ID - of the respective merge request. 1. Triggers a cross project pipeline and build the docs site with your changes. In case the review app URL returns 404, this means that either the site is not @@ -412,11 +409,6 @@ minutes and it should appear online, otherwise you can check the status of the remote pipeline from the link in the merge request's job output. If the pipeline failed or got stuck, drop a line in the `#docs` chat channel. -Make sure that you always delete the branch of the merge request you were -working on. If you don't, the remote docs branch isn't removed either, -and the server where the Review Apps are hosted can eventually run out of -disk space. - NOTE: Someone with no merge rights to the GitLab projects (think of forks from contributors) cannot run the manual job. In that case, you can diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index c0e9aa82247..7feb2753b4e 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -6416,30 +6416,6 @@ Status: `data_available` Tiers: `premium`, `ultimate` -### `g_project_management_epic_created_monthly` - -Count of MAU creating epics - -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210305144719_g_product_planning_epic_created_monthly.yml) - -Group: `group::product planning` - -Status: `implemented` - -Tiers: `premium`, `ultimate` - -### `g_project_management_epic_created_weekly` - -Count of WAU creating epics - -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210305145820_g_product_planning_epic_created_weekly.yml) - -Group: `group::product planning` - -Status: `implemented` - -Tiers: `premium`, `ultimate` - ### `geo_enabled` Is Geo enabled? @@ -9812,6 +9788,30 @@ Status: `implemented` Tiers: `free`, `premium`, `ultimate` +### `redis_hll_counters.epics_usage.g_project_management_epic_created_monthly` + +Count of MAU creating epics + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_28d/20210305144719_g_product_planning_epic_created_monthly.yml) + +Group: `group::product planning` + +Status: `implemented` + +Tiers: `premium`, `ultimate` + +### `redis_hll_counters.epics_usage.g_project_management_epic_created_weekly` + +Count of WAU creating epics + +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210305145820_g_product_planning_epic_created_weekly.yml) + +Group: `group::product planning` + +Status: `implemented` + +Tiers: `premium`, `ultimate` + ### `redis_hll_counters.ide_edit.g_edit_by_sfe_monthly` Missing description diff --git a/doc/development/usage_ping/index.md b/doc/development/usage_ping/index.md index 604d59f42e1..4e0e98cff82 100644 --- a/doc/development/usage_ping/index.md +++ b/doc/development/usage_ping/index.md @@ -957,7 +957,10 @@ Example aggregated metric entries: ```yaml - name: example_metrics_union operator: OR - events: ['i_search_total', 'i_search_advanced', 'i_search_paid'] + events: + - 'i_search_total' + - 'i_search_advanced' + - 'i_search_paid' source: redis time_frame: - 7d @@ -968,7 +971,9 @@ Example aggregated metric entries: time_frame: - 28d - all - events: ['dependency_scanning_pipeline_all_time', 'container_scanning_pipeline_all_time'] + events: + - 'dependency_scanning_pipeline_all_time' + - 'container_scanning_pipeline_all_time' feature_flag: example_aggregated_metric ``` @@ -1099,7 +1104,9 @@ Example definition: - name: example_metrics_intersection_database_sourced operator: AND source: database - events: ['dependency_scanning_pipeline', 'container_scanning_pipeline'] + events: + - 'dependency_scanning_pipeline' + - 'container_scanning_pipeline' time_frame: - 28d - all diff --git a/doc/development/usage_ping/metrics_dictionary.md b/doc/development/usage_ping/metrics_dictionary.md index 3c08fb0cc87..85d4b957640 100644 --- a/doc/development/usage_ping/metrics_dictionary.md +++ b/doc/development/usage_ping/metrics_dictionary.md @@ -32,7 +32,7 @@ Each metric is defined in a separate YAML file consisting of a number of fields: | `product_stage` | no | The [stage](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) for the metric. | | `product_group` | yes | The [group](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) that owns the metric. | | `product_category` | no | The [product category](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml) for the metric. | -| `value_type` | yes | `string`; one of `string`, `number`, `boolean`, `object`. | +| `value_type` | yes | `string`; one of [`string`, `number`, `boolean`, `object`](https://json-schema.org/understanding-json-schema/reference/type.html). | | `status` | yes | `string`; status of the metric, may be set to `data_available`, `planned`, `in_progress`, `implemented`, `not_used`, `deprecated` | | `time_frame` | yes | `string`; may be set to a value like `7d`, `28d`, `all`, `none`. | | `data_source` | yes | `string`; may be set to a value like `database`, `redis`, `redis_hll`, `prometheus`, `ruby`. | diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml index 5ebbbf15682..2ff36bcc657 100644 --- a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml @@ -113,9 +113,10 @@ promoteBeta: promoteProduction: extends: .promote_job stage: production - # We only allow production promotion on `master` because - # it has its own production scoped secret variables + # We only allow production promotion on the default branch because + # it has its own production scoped secret variables. only: - - master + variables: + - $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH script: - bundle exec fastlane promote_beta_to_production diff --git a/lib/gitlab/tracking.rb b/lib/gitlab/tracking.rb index 1b64c97b6b5..2f0e7e99305 100644 --- a/lib/gitlab/tracking.rb +++ b/lib/gitlab/tracking.rb @@ -14,6 +14,8 @@ module Gitlab snowplow.event(category, action, label: label, property: property, value: value, context: contexts) product_analytics.event(category, action, label: label, property: property, value: value, context: contexts) + rescue => error + Gitlab::ErrorTracking.track_and_raise_for_dev_exception(error, snowplow_category: category, snowplow_action: action) end def self_describing_event(schema_url, data:, context: nil) diff --git a/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml b/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml index 4c2355d526a..a2e931bd217 100644 --- a/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml +++ b/lib/gitlab/usage_data_counters/aggregated_metrics/code_review.yml @@ -12,97 +12,94 @@ feature_flag: usage_data_code_review_aggregation source: redis time_frame: [7d, 28d] - events: [ - 'i_code_review_user_single_file_diffs', - 'i_code_review_user_create_mr', - 'i_code_review_user_close_mr', - 'i_code_review_user_reopen_mr', - 'i_code_review_user_resolve_thread', - 'i_code_review_user_unresolve_thread', - 'i_code_review_edit_mr_title', - 'i_code_review_edit_mr_desc', - 'i_code_review_user_merge_mr', - 'i_code_review_user_create_mr_comment', - 'i_code_review_user_edit_mr_comment', - 'i_code_review_user_remove_mr_comment', - 'i_code_review_user_create_review_note', - 'i_code_review_user_publish_review', - 'i_code_review_user_create_multiline_mr_comment', - 'i_code_review_user_edit_multiline_mr_comment', - 'i_code_review_user_remove_multiline_mr_comment', - 'i_code_review_user_add_suggestion', - 'i_code_review_user_apply_suggestion', - 'i_code_review_user_assigned', - 'i_code_review_user_review_requested', - 'i_code_review_user_approve_mr', - 'i_code_review_user_unapprove_mr', - 'i_code_review_user_marked_as_draft', - 'i_code_review_user_unmarked_as_draft', - 'i_code_review_user_approval_rule_added', - 'i_code_review_user_approval_rule_deleted', - 'i_code_review_user_approval_rule_edited', - 'i_code_review_user_vs_code_api_request', - 'i_code_review_user_toggled_task_item_status', - 'i_code_review_user_create_mr_from_issue', - 'i_code_review_user_mr_discussion_locked', - 'i_code_review_user_mr_discussion_unlocked', - 'i_code_review_user_time_estimate_changed', - 'i_code_review_user_time_spent_changed', - 'i_code_review_user_assignees_changed', - 'i_code_review_user_reviewers_changed', - 'i_code_review_user_milestone_changed', - 'i_code_review_user_labels_changed' - ] + events: + - 'i_code_review_user_single_file_diffs' + - 'i_code_review_user_create_mr' + - 'i_code_review_user_close_mr' + - 'i_code_review_user_reopen_mr' + - 'i_code_review_user_resolve_thread' + - 'i_code_review_user_unresolve_thread' + - 'i_code_review_edit_mr_title' + - 'i_code_review_edit_mr_desc' + - 'i_code_review_user_merge_mr' + - 'i_code_review_user_create_mr_comment' + - 'i_code_review_user_edit_mr_comment' + - 'i_code_review_user_remove_mr_comment' + - 'i_code_review_user_create_review_note' + - 'i_code_review_user_publish_review' + - 'i_code_review_user_create_multiline_mr_comment' + - 'i_code_review_user_edit_multiline_mr_comment' + - 'i_code_review_user_remove_multiline_mr_comment' + - 'i_code_review_user_add_suggestion' + - 'i_code_review_user_apply_suggestion' + - 'i_code_review_user_assigned' + - 'i_code_review_user_review_requested' + - 'i_code_review_user_approve_mr' + - 'i_code_review_user_unapprove_mr' + - 'i_code_review_user_marked_as_draft' + - 'i_code_review_user_unmarked_as_draft' + - 'i_code_review_user_approval_rule_added' + - 'i_code_review_user_approval_rule_deleted' + - 'i_code_review_user_approval_rule_edited' + - 'i_code_review_user_vs_code_api_request' + - 'i_code_review_user_toggled_task_item_status' + - 'i_code_review_user_create_mr_from_issue' + - 'i_code_review_user_mr_discussion_locked' + - 'i_code_review_user_mr_discussion_unlocked' + - 'i_code_review_user_time_estimate_changed' + - 'i_code_review_user_time_spent_changed' + - 'i_code_review_user_assignees_changed' + - 'i_code_review_user_reviewers_changed' + - 'i_code_review_user_milestone_changed' + - 'i_code_review_user_labels_changed' - name: code_review_category_monthly_active_users operator: OR feature_flag: usage_data_code_review_aggregation source: redis time_frame: [7d, 28d] - events: [ - 'i_code_review_user_single_file_diffs', - 'i_code_review_user_create_mr', - 'i_code_review_user_close_mr', - 'i_code_review_user_reopen_mr', - 'i_code_review_user_resolve_thread', - 'i_code_review_user_unresolve_thread', - 'i_code_review_edit_mr_title', - 'i_code_review_edit_mr_desc', - 'i_code_review_user_merge_mr', - 'i_code_review_user_create_mr_comment', - 'i_code_review_user_edit_mr_comment', - 'i_code_review_user_remove_mr_comment', - 'i_code_review_user_create_review_note', - 'i_code_review_user_publish_review', - 'i_code_review_user_create_multiline_mr_comment', - 'i_code_review_user_edit_multiline_mr_comment', - 'i_code_review_user_remove_multiline_mr_comment', - 'i_code_review_user_add_suggestion', - 'i_code_review_user_apply_suggestion', - 'i_code_review_user_assigned', - 'i_code_review_user_review_requested', - 'i_code_review_user_approve_mr', - 'i_code_review_user_unapprove_mr', - 'i_code_review_user_marked_as_draft', - 'i_code_review_user_unmarked_as_draft', - 'i_code_review_user_approval_rule_added', - 'i_code_review_user_approval_rule_deleted', - 'i_code_review_user_approval_rule_edited', - 'i_code_review_user_toggled_task_item_status', - 'i_code_review_user_create_mr_from_issue', - 'i_code_review_user_mr_discussion_locked', - 'i_code_review_user_mr_discussion_unlocked', - 'i_code_review_user_time_estimate_changed', - 'i_code_review_user_time_spent_changed', - 'i_code_review_user_assignees_changed', - 'i_code_review_user_reviewers_changed', - 'i_code_review_user_milestone_changed', - 'i_code_review_user_labels_changed' - ] + events: + - 'i_code_review_user_single_file_diffs' + - 'i_code_review_user_create_mr' + - 'i_code_review_user_close_mr' + - 'i_code_review_user_reopen_mr' + - 'i_code_review_user_resolve_thread' + - 'i_code_review_user_unresolve_thread' + - 'i_code_review_edit_mr_title' + - 'i_code_review_edit_mr_desc' + - 'i_code_review_user_merge_mr' + - 'i_code_review_user_create_mr_comment' + - 'i_code_review_user_edit_mr_comment' + - 'i_code_review_user_remove_mr_comment' + - 'i_code_review_user_create_review_note' + - 'i_code_review_user_publish_review' + - 'i_code_review_user_create_multiline_mr_comment' + - 'i_code_review_user_edit_multiline_mr_comment' + - 'i_code_review_user_remove_multiline_mr_comment' + - 'i_code_review_user_add_suggestion' + - 'i_code_review_user_apply_suggestion' + - 'i_code_review_user_assigned' + - 'i_code_review_user_review_requested' + - 'i_code_review_user_approve_mr' + - 'i_code_review_user_unapprove_mr' + - 'i_code_review_user_marked_as_draft' + - 'i_code_review_user_unmarked_as_draft' + - 'i_code_review_user_approval_rule_added' + - 'i_code_review_user_approval_rule_deleted' + - 'i_code_review_user_approval_rule_edited' + - 'i_code_review_user_toggled_task_item_status' + - 'i_code_review_user_create_mr_from_issue' + - 'i_code_review_user_mr_discussion_locked' + - 'i_code_review_user_mr_discussion_unlocked' + - 'i_code_review_user_time_estimate_changed' + - 'i_code_review_user_time_spent_changed' + - 'i_code_review_user_assignees_changed' + - 'i_code_review_user_reviewers_changed' + - 'i_code_review_user_milestone_changed' + - 'i_code_review_user_labels_changed' - name: code_review_extension_category_monthly_active_users operator: OR feature_flag: usage_data_code_review_aggregation source: redis time_frame: [7d, 28d] - events: [ - 'i_code_review_user_vs_code_api_request' - ] + events: + - 'i_code_review_user_vs_code_api_request' diff --git a/lib/gitlab/usage_data_counters/aggregated_metrics/common.yml b/lib/gitlab/usage_data_counters/aggregated_metrics/common.yml index 73a55b5d5fa..abf04c18e19 100644 --- a/lib/gitlab/usage_data_counters/aggregated_metrics/common.yml +++ b/lib/gitlab/usage_data_counters/aggregated_metrics/common.yml @@ -21,52 +21,60 @@ operator: OR source: redis time_frame: [7d, 28d] - events: ['g_compliance_audit_events', 'g_compliance_dashboard', 'i_compliance_audit_events', 'a_compliance_audit_events_api', 'i_compliance_credential_inventory'] + events: + - 'g_compliance_audit_events' + - 'g_compliance_dashboard' + - 'i_compliance_audit_events' + - 'a_compliance_audit_events_api' + - 'i_compliance_credential_inventory' - name: product_analytics_test_metrics_union operator: OR source: redis time_frame: [7d, 28d] - events: ['i_search_total', 'i_search_advanced', 'i_search_paid'] + events: + - 'i_search_total' + - 'i_search_advanced' + - 'i_search_paid' - name: product_analytics_test_metrics_intersection operator: AND source: redis time_frame: [7d, 28d] - events: ['i_search_total', 'i_search_advanced', 'i_search_paid'] + events: + - 'i_search_total' + - 'i_search_advanced' + - 'i_search_paid' - name: incident_management_alerts_total_unique_counts operator: OR source: redis time_frame: [7d, 28d] - events: [ - 'incident_management_alert_status_changed', - 'incident_management_alert_assigned', - 'incident_management_alert_todo', - 'incident_management_alert_create_incident' - ] + events: + - 'incident_management_alert_status_changed' + - 'incident_management_alert_assigned' + - 'incident_management_alert_todo' + - 'incident_management_alert_create_incident' - name: incident_management_incidents_total_unique_counts operator: OR source: redis time_frame: [7d, 28d] - events: [ - 'incident_management_incident_created', - 'incident_management_incident_reopened', - 'incident_management_incident_closed', - 'incident_management_incident_assigned', - 'incident_management_incident_todo', - 'incident_management_incident_comment', - 'incident_management_incident_zoom_meeting', - 'incident_management_incident_published', - 'incident_management_incident_relate', - 'incident_management_incident_unrelate', - 'incident_management_incident_change_confidential' - ] + events: + - 'incident_management_incident_created' + - 'incident_management_incident_reopened' + - 'incident_management_incident_closed' + - 'incident_management_incident_assigned' + - 'incident_management_incident_todo' + - 'incident_management_incident_comment' + - 'incident_management_incident_zoom_meeting' + - 'incident_management_incident_published' + - 'incident_management_incident_relate' + - 'incident_management_incident_unrelate' + - 'incident_management_incident_change_confidential' - name: i_testing_paid_monthly_active_user_total operator: OR source: redis time_frame: [7d, 28d] - events: [ - 'i_testing_web_performance_widget_total', - 'i_testing_full_code_quality_report_total', - 'i_testing_group_code_coverage_visit_total', - 'i_testing_load_performance_widget_total', - 'i_testing_metrics_report_widget_total' - ] + events: + - 'i_testing_web_performance_widget_total' + - 'i_testing_full_code_quality_report_total' + - 'i_testing_group_code_coverage_visit_total' + - 'i_testing_load_performance_widget_total' + - 'i_testing_metrics_report_widget_total' diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml index 80a79682338..a6a2c62a483 100644 --- a/lib/gitlab/usage_data_counters/known_events/common.yml +++ b/lib/gitlab/usage_data_counters/known_events/common.yml @@ -444,13 +444,3 @@ redis_slot: pipeline_authoring aggregation: weekly feature_flag: usage_data_o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile -# Epic events -# -# We are using the same slot of issue events 'project_management' for -# epic events to allow data aggregation. -# More information in: https://gitlab.com/gitlab-org/gitlab/-/issues/322405 -- name: g_project_management_epic_created - category: epics_usage - redis_slot: project_management - aggregation: daily - feature_flag: track_epics_activity diff --git a/lib/gitlab/usage_data_counters/known_events/epic_events.yml b/lib/gitlab/usage_data_counters/known_events/epic_events.yml new file mode 100644 index 00000000000..c478d96b1a9 --- /dev/null +++ b/lib/gitlab/usage_data_counters/known_events/epic_events.yml @@ -0,0 +1,10 @@ +# Epic events +# +# We are using the same slot of issue events 'project_management' for +# epic events to allow data aggregation. +# More information in: https://gitlab.com/gitlab-org/gitlab/-/issues/322405 +- name: g_project_management_epic_created + category: epics_usage + redis_slot: project_management + aggregation: daily + feature_flag: track_epics_activity diff --git a/scripts/trigger-build b/scripts/trigger-build index 29d53609026..28f514dd799 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -214,15 +214,7 @@ module Trigger => If something doesn't work, drop a line in the #docs chat channel. MSG - # Create a remote branch in gitlab-docs and immediately cancel the pipeline - # to avoid race conditions, since a triggered pipeline will also run right - # after the branch creation. This only happens the very first time a branch - # is created and will be skipped in subsequent runs. Read more in - # https://gitlab.com/gitlab-org/gitlab-docs/issues/154. - # def deploy! - create_remote_branch! - cancel_latest_pipeline! invoke!.wait! display_success_message end @@ -231,31 +223,47 @@ module Trigger # Remove a remote branch in gitlab-docs. # def cleanup! - gitlab_client(:downstream).delete_branch(downstream_project_path, ref) - puts "=> Remote branch '#{downstream_project_path}' deleted" + environment = gitlab_client(:downstream).environments(downstream_project_path, name: downstream_environment).first + return unless environment + + environment = gitlab_client(:downstream).stop_environment(downstream_project_path, environment.id) + if environment.state == 'stopped' + puts "=> Downstream environment '#{downstream_environment}' stopped" + else + puts "=> Downstream environment '#{downstream_environment}' failed to stop." + end end private + def downstream_environment + "review/#{ref}#{review_slug}" + end + + # We prepend the `-` here because we cannot use variable substitution in `environment.name`/`environment.url` + # Some projects (e.g. `omnibus-gitlab`) use this script for branch pipelines, so we fallback to using `CI_COMMIT_REF_SLUG` for those cases. + def review_slug + identifier = ENV['CI_MERGE_REQUEST_IID'] || ENV['CI_COMMIT_REF_SLUG'] + + "-#{project_slug}-#{identifier}" + end + def downstream_project_path ENV['DOCS_PROJECT_PATH'] || 'gitlab-org/gitlab-docs' end def ref - if ENV['CI_MERGE_REQUEST_IID'].nil? - "docs-preview-#{slug}-#{ENV['CI_COMMIT_REF_SLUG']}" - else - "docs-preview-#{slug}-#{ENV['CI_MERGE_REQUEST_IID']}" - end + ENV['DOCS_BRANCH'] || 'master' end def extra_variables { - "BRANCH_#{slug.upcase}" => ENV['CI_COMMIT_REF_NAME'] + "BRANCH_#{project_slug.upcase}" => ENV['CI_COMMIT_REF_NAME'], + "REVIEW_SLUG" => review_slug } end - def slug + def project_slug case ENV['CI_PROJECT_PATH'] when 'gitlab-org/gitlab-foss' 'ce' @@ -270,37 +278,14 @@ module Trigger end end + # app_url is the URL of the `gitlab-docs` Review App URL defined in + # https://gitlab.com/gitlab-org/gitlab-docs/-/blob/b38038132cf82a24271bbb294dead7c2f529e275/.gitlab-ci.yml#L383 def app_url - "http://#{ref}.#{ENV['DOCS_REVIEW_APPS_DOMAIN']}/#{slug}" - end - - def create_remote_branch! - gitlab_client(:downstream).create_branch(downstream_project_path, ref, 'master') - puts "=> Remote branch '#{ref}' created" - rescue Gitlab::Error::BadRequest - puts "=> Remote branch '#{ref}' already exists!" - end - - def cancel_latest_pipeline! - pipelines = nil - - # Wait until the pipeline is started - loop do - sleep 1 - puts "=> Waiting for pipeline to start..." - pipelines = gitlab_client(:downstream).pipelines(downstream_project_path, { ref: ref }) - break if pipelines.any? - end - - # Get the first pipeline ID which should be the only one for the branch - pipeline_id = pipelines.first.id - - # Cancel the pipeline - gitlab_client(:downstream).cancel_pipeline(downstream_project_path, pipeline_id) + "http://#{ref}#{review_slug}.#{ENV['DOCS_REVIEW_APPS_DOMAIN']}/#{project_slug}" end def display_success_message - format(SUCCESS_MESSAGE, app_url: app_url) + puts format(SUCCESS_MESSAGE, app_url: app_url) end end diff --git a/scripts/utils.sh b/scripts/utils.sh index c598afc4582..bcd7dd6c42b 100644 --- a/scripts/utils.sh +++ b/scripts/utils.sh @@ -32,7 +32,7 @@ function install_api_client_dependencies_with_apt() { function install_gitlab_gem() { gem install httparty --no-document --version 0.18.1 - gem install gitlab --no-document --version 4.14.1 + gem install gitlab --no-document --version 4.17.0 } function install_tff_gem() { 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 de6e112866b..b6d49d0d0f8 100644 --- a/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js +++ b/spec/frontend/pipeline_editor/components/header/pipeline_status_spec.js @@ -4,6 +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 CiIcon from '~/vue_shared/components/ci_icon.vue'; import { mockCommitSha, mockProjectPipeline, mockProjectFullPath } from '../../mock_data'; @@ -19,32 +20,9 @@ describe('Pipeline Status', () => { let mockApollo; let mockPipelineQuery; - const createComponent = ({ hasPipeline = true, isQueryLoading = false }) => { - const pipeline = hasPipeline - ? { loading: isQueryLoading, ...mockProjectPipeline.pipeline } - : { loading: isQueryLoading }; - - wrapper = shallowMount(PipelineStatus, { - provide: mockProvide, - stubs: { GlLink, GlSprintf }, - data: () => (hasPipeline ? { pipeline } : {}), - mocks: { - $apollo: { - queries: { - pipeline, - }, - }, - }, - }); - }; - const createComponentWithApollo = () => { - const resolvers = { - Query: { - project: mockPipelineQuery, - }, - }; - mockApollo = createMockApollo([], resolvers); + const handlers = [[getPipelineQuery, mockPipelineQuery]]; + mockApollo = createMockApollo(handlers); wrapper = shallowMount(PipelineStatus, { localVue, @@ -78,16 +56,17 @@ describe('Pipeline Status', () => { wrapper = null; }); - describe('while querying', () => { - it('renders loading icon', () => { - createComponent({ isQueryLoading: true, hasPipeline: false }); + describe('loading icon', () => { + it('renders while query is being fetched', () => { + createComponentWithApollo(); expect(findLoadingIcon().exists()).toBe(true); expect(findPipelineLoadingMsg().text()).toBe(i18n.fetchLoading); }); - it('does not render loading icon if pipeline data is already set', () => { - createComponent({ isQueryLoading: true }); + it('does not render if query is no longer loading', async () => { + createComponentWithApollo(); + await waitForPromises(); expect(findLoadingIcon().exists()).toBe(false); }); @@ -96,7 +75,9 @@ describe('Pipeline Status', () => { describe('when querying data', () => { describe('when data is set', () => { beforeEach(async () => { - mockPipelineQuery.mockResolvedValue(mockProjectPipeline); + mockPipelineQuery.mockResolvedValue({ + data: { project: mockProjectPipeline }, + }); createComponentWithApollo(); await waitForPromises(); @@ -104,14 +85,10 @@ describe('Pipeline Status', () => { it('query is called with correct variables', async () => { expect(mockPipelineQuery).toHaveBeenCalledTimes(1); - expect(mockPipelineQuery).toHaveBeenCalledWith( - expect.anything(), - { - fullPath: mockProjectFullPath, - }, - expect.anything(), - expect.anything(), - ); + expect(mockPipelineQuery).toHaveBeenCalledWith({ + fullPath: mockProjectFullPath, + sha: mockCommitSha, + }); }); it('does not render error', () => { diff --git a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js index 196a4133eea..d39c0d80296 100644 --- a/spec/frontend/pipeline_editor/graphql/resolvers_spec.js +++ b/spec/frontend/pipeline_editor/graphql/resolvers_spec.js @@ -46,24 +46,6 @@ describe('~/pipeline_editor/graphql/resolvers', () => { await expect(result.rawData).resolves.toBe(mockCiYml); }); }); - - describe('pipeline', () => { - it('resolves pipeline data with type names', async () => { - const result = await resolvers.Query.project(null); - - // eslint-disable-next-line no-underscore-dangle - expect(result.__typename).toBe('Project'); - }); - - it('resolves pipeline data with necessary data', async () => { - const result = await resolvers.Query.project(null); - const pipelineKeys = Object.keys(result.pipeline); - const statusKeys = Object.keys(result.pipeline.detailedStatus); - - expect(pipelineKeys).toContain('id', 'commitPath', 'detailedStatus', 'shortSha'); - expect(statusKeys).toContain('detailsPath', 'text'); - }); - }); }); describe('Mutation', () => { diff --git a/spec/graphql/mutations/release_asset_links/delete_spec.rb b/spec/graphql/mutations/release_asset_links/delete_spec.rb new file mode 100644 index 00000000000..15d320b58ee --- /dev/null +++ b/spec/graphql/mutations/release_asset_links/delete_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Mutations::ReleaseAssetLinks::Delete do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :private, :repository) } + let_it_be_with_reload(:release) { create(:release, project: project) } + let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } } + let_it_be(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } } + let_it_be_with_reload(:release_link) { create(:release_link, release: release) } + + let(:mutation) { described_class.new(object: nil, context: { current_user: current_user }, field: nil) } + let(:mutation_arguments) { { id: release_link.to_global_id } } + + describe '#resolve' do + subject(:resolve) do + mutation.resolve(**mutation_arguments) + end + + let(:deleted_link) { subject[:link] } + + context 'when the current user has access to delete the link' do + let(:current_user) { maintainer } + + it 'deletes the link and returns it', :aggregate_failures do + expect(deleted_link).to eq(release_link) + + expect(release.links).to be_empty + end + + context "when the link doesn't exist" do + let(:mutation_arguments) { super().merge(id: "gid://gitlab/Releases::Link/#{non_existing_record_id}") } + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + + context "when the provided ID is invalid" do + let(:mutation_arguments) { super().merge(id: 'not-a-valid-gid') } + + it 'raises an error' do + expect { subject }.to raise_error(::GraphQL::CoercionError) + end + end + end + + context 'when the current user does not have access to delete the link' do + let(:current_user) { developer } + + it 'raises an error' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end + end + end +end diff --git a/spec/lib/gitlab/tracking_spec.rb b/spec/lib/gitlab/tracking_spec.rb index ac052bd7a80..c1e69b94406 100644 --- a/spec/lib/gitlab/tracking_spec.rb +++ b/spec/lib/gitlab/tracking_spec.rb @@ -36,12 +36,12 @@ RSpec.describe Gitlab::Tracking do end describe '.event' do - before do - allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow).to receive(:event) - allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event) - end - shared_examples 'delegates to destination' do |klass| + before do + allow_any_instance_of(Gitlab::Tracking::Destinations::Snowplow).to receive(:event) + allow_any_instance_of(Gitlab::Tracking::Destinations::ProductAnalytics).to receive(:event) + end + it "delegates to #{klass} destination" do other_context = double(:context) @@ -70,8 +70,17 @@ RSpec.describe Gitlab::Tracking do end end - include_examples 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow - include_examples 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics + it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::Snowplow + it_behaves_like 'delegates to destination', Gitlab::Tracking::Destinations::ProductAnalytics + + it 'tracks errors' do + expect(Gitlab::ErrorTracking).to receive(:track_and_raise_for_dev_exception).with( + an_instance_of(ContractError), + snowplow_category: nil, snowplow_action: 'some_action' + ) + + described_class.event(nil, 'some_action') + end end describe '.self_describing_event' do diff --git a/spec/requests/api/graphql/mutations/release_asset_links/delete_spec.rb b/spec/requests/api/graphql/mutations/release_asset_links/delete_spec.rb new file mode 100644 index 00000000000..57489c82ec2 --- /dev/null +++ b/spec/requests/api/graphql/mutations/release_asset_links/delete_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Deletes a release asset link' do + include GraphqlHelpers + + let_it_be(:project) { create(:project, :private, :repository) } + let_it_be(:release) { create(:release, project: project) } + let_it_be(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } } + let_it_be(:release_link) { create(:release_link, release: release) } + + let(:current_user) { maintainer } + let(:mutation_name) { :release_asset_link_delete } + let(:mutation_arguments) { { id: release_link.to_global_id.to_s } } + + let(:mutation) do + graphql_mutation(mutation_name, mutation_arguments, <<~FIELDS) + link { + id + name + url + linkType + directAssetUrl + external + } + errors + FIELDS + end + + let(:delete_link) { post_graphql_mutation(mutation, current_user: current_user) } + let(:mutation_response) { graphql_mutation_response(mutation_name)&.with_indifferent_access } + + it 'deletes the release asset link and returns the deleted link', :aggregate_failures do + delete_link + + expected_response = { + id: release_link.to_global_id.to_s, + name: release_link.name, + url: release_link.url, + linkType: release_link.link_type.upcase, + directAssetUrl: end_with(release_link.filepath), + external: true + }.with_indifferent_access + + expect(mutation_response[:link]).to match(expected_response) + expect(mutation_response[:errors]).to eq([]) + end +end |