diff options
74 files changed, 571 insertions, 474 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue index d9e5878b9e3..717d4cb01ab 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue @@ -155,7 +155,7 @@ export default { <span v-if="item.active" data-testid="integration-activated-status"> <gl-icon v-gl-tooltip - name="check-circle-filled" + name="check" :size="16" class="gl-text-green-500 gl-hover-cursor-pointer gl-mr-3" :title="$options.i18n.status.enabled.tooltip" diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue index 3917e4c5fdd..97a3cdbd45a 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue @@ -167,7 +167,7 @@ export default { if (testAfterSubmit) { this.viewIntegration(integration, tabIndices.sendTestAlert); } else { - this.clearCurrentIntegration(type); + this.clearCurrentIntegration({ type }); } createFlash({ diff --git a/app/assets/javascripts/alerts_settings/graphql.js b/app/assets/javascripts/alerts_settings/graphql.js index 72817f636ff..15862f4034a 100644 --- a/app/assets/javascripts/alerts_settings/graphql.js +++ b/app/assets/javascripts/alerts_settings/graphql.js @@ -1,9 +1,15 @@ +import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import produce from 'immer'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import introspectionQueryResultData from './graphql/fragmentTypes.json'; import getCurrentIntegrationQuery from './graphql/queries/get_current_integration.query.graphql'; +const fragmentMatcher = new IntrospectionFragmentMatcher({ + introspectionQueryResultData, +}); + Vue.use(VueApollo); const resolvers = { @@ -50,7 +56,9 @@ const resolvers = { export default new VueApollo({ defaultClient: createDefaultClient(resolvers, { - cacheConfig: {}, + cacheConfig: { + fragmentMatcher, + }, assumeImmutableResults: true, }), }); diff --git a/app/assets/javascripts/alerts_settings/graphql/fragmentTypes.json b/app/assets/javascripts/alerts_settings/graphql/fragmentTypes.json new file mode 100644 index 00000000000..07dfc43aa6c --- /dev/null +++ b/app/assets/javascripts/alerts_settings/graphql/fragmentTypes.json @@ -0,0 +1 @@ +{"__schema":{"types":[{"kind":"UNION","name":"AlertManagementIntegration","possibleTypes":[{"name":"AlertManagementHttpIntegration"},{"name":"AlertManagementPrometheusIntegration"}]}]}} diff --git a/app/assets/javascripts/blob/components/table_contents.vue b/app/assets/javascripts/blob/components/table_contents.vue new file mode 100644 index 00000000000..3a0a385d494 --- /dev/null +++ b/app/assets/javascripts/blob/components/table_contents.vue @@ -0,0 +1,74 @@ +<script> +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; + +function getHeaderNumber(el) { + return parseInt(el.tagName.match(/\d+/)[0], 10); +} + +export default { + components: { + GlDropdown, + GlDropdownItem, + }, + data() { + return { + isHidden: false, + items: [], + }; + }, + mounted() { + this.blobViewer = document.querySelector('.blob-viewer[data-type="rich"]'); + + this.observer = new MutationObserver(() => { + if (this.blobViewer.classList.contains('hidden')) { + this.isHidden = true; + } else if (this.blobViewer.getAttribute('data-loaded') === 'true') { + this.isHidden = false; + this.generateHeaders(); + } + }); + + if (this.blobViewer) { + this.observer.observe(this.blobViewer, { + attributes: true, + }); + } + }, + beforeDestroy() { + if (this.observer) { + this.observer.disconnect(); + } + }, + methods: { + generateHeaders() { + const headers = [...this.blobViewer.querySelectorAll('h1,h2,h3,h4,h5,h6')]; + + if (headers.length) { + const firstHeader = getHeaderNumber(headers[0]); + + headers.forEach((el) => { + this.items.push({ + text: el.textContent.trim(), + anchor: el.querySelector('a').getAttribute('id'), + spacing: Math.max((getHeaderNumber(el) - firstHeader) * 8, 0), + }); + }); + } + }, + }, +}; +</script> + +<template> + <gl-dropdown v-if="!isHidden && items.length" icon="list-bulleted" class="gl-mr-2"> + <gl-dropdown-item v-for="(item, index) in items" :key="index" :href="`#${item.anchor}`"> + <span + :style="{ 'padding-left': `${item.spacing}px` }" + class="gl-display-block" + data-testid="tableContentsLink" + > + {{ item.text }} + </span> + </gl-dropdown-item> + </gl-dropdown> +</template> diff --git a/app/assets/javascripts/grafana_integration/components/grafana_integration.vue b/app/assets/javascripts/grafana_integration/components/grafana_integration.vue index e941318dce0..3911201457f 100644 --- a/app/assets/javascripts/grafana_integration/components/grafana_integration.vue +++ b/app/assets/javascripts/grafana_integration/components/grafana_integration.vue @@ -1,5 +1,13 @@ <script> -import { GlButton, GlFormGroup, GlFormInput, GlFormCheckbox, GlIcon, GlLink } from '@gitlab/ui'; +import { + GlButton, + GlFormGroup, + GlFormInput, + GlFormCheckbox, + GlIcon, + GlLink, + GlSprintf, +} from '@gitlab/ui'; import { mapState, mapActions } from 'vuex'; import { helpPagePath } from '~/helpers/help_page_helper'; @@ -11,6 +19,7 @@ export default { GlFormInput, GlIcon, GlLink, + GlSprintf, }, data() { return { @@ -78,13 +87,11 @@ export default { </div> <div class="settings-content"> <form> - <gl-form-checkbox - id="grafana-integration-enabled" - v-model="integrationEnabled" - class="mb-4" - > - {{ s__('GrafanaIntegration|Active') }} - </gl-form-checkbox> + <gl-form-group :label="__('Enable authentication')" label-for="grafana-integration-enabled"> + <gl-form-checkbox id="grafana-integration-enabled" v-model="integrationEnabled"> + {{ s__('GrafanaIntegration|Active') }} + </gl-form-checkbox> + </gl-form-group> <gl-form-group :label="s__('GrafanaIntegration|Grafana URL')" label-for="grafana-url" @@ -95,18 +102,27 @@ export default { <gl-form-group :label="s__('GrafanaIntegration|API token')" label-for="grafana-token"> <gl-form-input id="grafana-token" v-model="localGrafanaToken" /> <p class="form-text text-muted"> - {{ s__('GrafanaIntegration|Enter the Grafana API token.') }} - <a - href="https://grafana.com/docs/http_api/auth/#create-api-token" - target="_blank" - rel="noopener noreferrer" + <gl-sprintf + :message=" + s__('GrafanaIntegration|Enter the %{docLinkStart}Grafana API token%{docLinkEnd}.') + " > - {{ __('More information.') }} - <gl-icon name="external-link" class="vertical-align-middle" /> - </a> + <template #docLink="{ content }"> + <gl-link + href="https://grafana.com/docs/http_api/auth/#create-api-token" + target="_blank" + >{{ content }} <gl-icon name="external-link" class="gl-vertical-align-middle" + /></gl-link> + </template> + </gl-sprintf> </p> </gl-form-group> - <gl-button variant="success" category="primary" @click="updateGrafanaIntegration"> + <gl-button + variant="confirm" + category="primary" + data-testid="save-grafana-settings-button" + @click="updateGrafanaIntegration" + > {{ __('Save changes') }} </gl-button> </form> diff --git a/app/assets/javascripts/incidents_settings/components/alerts_form.vue b/app/assets/javascripts/incidents_settings/components/alerts_form.vue index e8daad8811e..b22ec6add96 100644 --- a/app/assets/javascripts/incidents_settings/components/alerts_form.vue +++ b/app/assets/javascripts/incidents_settings/components/alerts_form.vue @@ -134,7 +134,7 @@ export default { ref="submitBtn" data-qa-selector="save_changes_button" :disabled="loading" - variant="success" + variant="confirm" type="submit" class="js-no-auto-disable" > diff --git a/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue b/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue index b56dd66342a..866d2ff399e 100644 --- a/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue +++ b/app/assets/javascripts/incidents_settings/components/pagerduty_form.vue @@ -11,7 +11,6 @@ import { GlModal, GlModalDirective, } from '@gitlab/ui'; -import { isEqual } from 'lodash'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { I18N_PAGERDUTY_SETTINGS_FORM, CONFIGURE_PAGERDUTY_WEBHOOK_DOCS_LINK } from '../constants'; @@ -50,14 +49,8 @@ export default { pagerduty_active: this.active, }; }, - isFormUpdated() { - return isEqual(this.pagerDutySettings, { - active: this.active, - webhookUrl: this.webhookUrl, - }); - }, isSaveDisabled() { - return this.isFormUpdated || this.loading || this.resettingWebhook; + return this.loading || this.resettingWebhook; }, webhookUpdateAlertMsg() { return this.webhookUpdateFailed @@ -123,13 +116,15 @@ export default { </template> </gl-sprintf> </p> - <form ref="settingsForm" @submit.prevent="updatePagerDutyIntegrationSettings"> + <form ref="settingsForm"> <gl-form-group class="col-8 col-md-9 gl-p-0"> <gl-toggle id="active" v-model="active" + :disabled="isSaveDisabled" :is-loading="loading" :label="$options.i18n.activeToggle.label" + @change="updatePagerDutyIntegrationSettings" /> </gl-form-group> @@ -166,15 +161,6 @@ export default { {{ $options.i18n.webhookUrl.restKeyInfo }} </gl-modal> </gl-form-group> - <gl-button - ref="submitBtn" - :disabled="isSaveDisabled" - variant="success" - type="submit" - class="js-no-auto-disable" - > - {{ $options.i18n.saveBtnLabel }} - </gl-button> </form> </div> </template> diff --git a/app/assets/javascripts/incidents_settings/constants.js b/app/assets/javascripts/incidents_settings/constants.js index d479838b491..d72e2aedee9 100644 --- a/app/assets/javascripts/incidents_settings/constants.js +++ b/app/assets/javascripts/incidents_settings/constants.js @@ -23,7 +23,7 @@ export const I18N_INTEGRATION_TABS = { headerText: s__('IncidentSettings|Incidents'), expandBtnLabel: __('Expand'), subHeaderText: s__( - 'IncidentSettings|Set up integrations with external tools to help better manage incidents.', + 'IncidentSettings|Fine-tune incident settings and set up integrations with external tools to help better manage incidents.', ), }; diff --git a/app/assets/javascripts/nav/components/top_nav_menu_item.vue b/app/assets/javascripts/nav/components/top_nav_menu_item.vue index a0d92811a6f..067180abd08 100644 --- a/app/assets/javascripts/nav/components/top_nav_menu_item.vue +++ b/app/assets/javascripts/nav/components/top_nav_menu_item.vue @@ -1,5 +1,8 @@ <script> import { GlButton, GlIcon } from '@gitlab/ui'; +import { kebabCase, mapKeys } from 'lodash'; + +const getDataKey = (key) => `data-${kebabCase(key)}`; export default { components: { @@ -12,6 +15,11 @@ export default { required: true, }, }, + computed: { + dataAttrs() { + return mapKeys(this.menuItem.data || {}, (value, key) => getDataKey(key)); + }, + }, }; </script> @@ -20,6 +28,8 @@ export default { category="tertiary" :href="menuItem.href" class="top-nav-menu-item gl-display-block" + :class="menuItem.css_class" + v-bind="dataAttrs" v-on="$listeners" > <span class="gl-display-flex"> diff --git a/app/assets/javascripts/operation_settings/components/metrics_settings.vue b/app/assets/javascripts/operation_settings/components/metrics_settings.vue index a24612e4680..959fffa2629 100644 --- a/app/assets/javascripts/operation_settings/components/metrics_settings.vue +++ b/app/assets/javascripts/operation_settings/components/metrics_settings.vue @@ -34,19 +34,19 @@ export default { <h4 class="js-section-header settings-title js-settings-toggle js-settings-toggle-trigger-only" > - {{ s__('MetricsSettings|Metrics dashboard') }} + {{ s__('MetricsSettings|Metrics') }} </h4> <gl-button class="js-settings-toggle">{{ __('Expand') }}</gl-button> <p class="js-section-sub-header"> - {{ s__('MetricsSettings|Manage Metrics Dashboard settings.') }} - <gl-link :href="helpPage">{{ __('Learn more') }}</gl-link> + {{ s__('MetricsSettings|Manage metrics dashboard settings.') }} + <gl-link :href="helpPage">{{ __('Learn more.') }}</gl-link> </p> </div> <div class="settings-content"> <form> <dashboard-timezone /> <external-dashboard /> - <gl-button variant="success" category="primary" @click="saveChanges"> + <gl-button variant="confirm" category="primary" @click="saveChanges"> {{ __('Save Changes') }} </gl-button> </form> diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js index 8a8ce70e998..6179586e56c 100644 --- a/app/assets/javascripts/pages/projects/blob/show/index.js +++ b/app/assets/javascripts/pages/projects/blob/show/index.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; +import TableOfContents from '~/blob/components/table_contents.vue'; import PipelineTourSuccessModal from '~/blob/pipeline_tour_success_modal.vue'; import BlobViewer from '~/blob/viewer/index'; import GpgBadges from '~/gpg_badges'; @@ -92,3 +93,15 @@ if (successPipelineEl) { }, }); } + +const tableContentsEl = document.querySelector('.js-table-contents'); + +if (tableContentsEl) { + // eslint-disable-next-line no-new + new Vue({ + el: tableContentsEl, + render(h) { + return h(TableOfContents); + }, + }); +} diff --git a/app/finders/concerns/packages/finder_helper.rb b/app/finders/concerns/packages/finder_helper.rb index f0ad998cadb..d2784a1d270 100644 --- a/app/finders/concerns/packages/finder_helper.rb +++ b/app/finders/concerns/packages/finder_helper.rb @@ -29,7 +29,7 @@ module Packages end def projects_visible_to_reporters(user, within_group:) - if user.is_a?(DeployToken) && Feature.enabled?(:packages_finder_helper_deploy_token, default_enabled: :yaml) + if user.is_a?(DeployToken) user.accessible_projects else within_group.all_projects diff --git a/app/graphql/mutations/concerns/mutations/resolves_subscription.rb b/app/graphql/mutations/concerns/mutations/resolves_subscription.rb index e26ae7d228c..ed9fb5fceb0 100644 --- a/app/graphql/mutations/concerns/mutations/resolves_subscription.rb +++ b/app/graphql/mutations/concerns/mutations/resolves_subscription.rb @@ -3,6 +3,7 @@ module Mutations module ResolvesSubscription extend ActiveSupport::Concern + included do argument :subscribed_state, GraphQL::BOOLEAN_TYPE, diff --git a/app/graphql/mutations/issues/set_subscription.rb b/app/graphql/mutations/issues/set_subscription.rb index a04c8f5ba2d..55c9049b7cf 100644 --- a/app/graphql/mutations/issues/set_subscription.rb +++ b/app/graphql/mutations/issues/set_subscription.rb @@ -2,10 +2,32 @@ module Mutations module Issues - class SetSubscription < Base + class SetSubscription < BaseMutation graphql_name 'IssueSetSubscription' include ResolvesSubscription + include Mutations::ResolvesIssuable + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: "The project the issue to mutate is in." + + argument :iid, GraphQL::STRING_TYPE, + required: true, + description: "The IID of the issue to mutate." + + field :issue, + Types::IssueType, + null: true, + description: "The issue after mutation." + + authorize :update_subscription + + private + + def find_object(project_path:, iid:) + resolve_issuable(type: :issue, parent_path: project_path, iid: iid) + end end end end diff --git a/app/graphql/mutations/merge_requests/set_subscription.rb b/app/graphql/mutations/merge_requests/set_subscription.rb index 7d3c40185c9..981daa81c28 100644 --- a/app/graphql/mutations/merge_requests/set_subscription.rb +++ b/app/graphql/mutations/merge_requests/set_subscription.rb @@ -2,10 +2,32 @@ module Mutations module MergeRequests - class SetSubscription < Base + class SetSubscription < BaseMutation graphql_name 'MergeRequestSetSubscription' include ResolvesSubscription + include Mutations::ResolvesIssuable + + argument :project_path, GraphQL::ID_TYPE, + required: true, + description: "The project the merge request to mutate is in." + + argument :iid, GraphQL::STRING_TYPE, + required: true, + description: "The IID of the merge request to mutate." + + field :merge_request, + Types::MergeRequestType, + null: true, + description: "The merge request after mutation." + + authorize :update_subscription + + private + + def find_object(project_path:, iid:) + resolve_issuable(type: :merge_request, parent_path: project_path, iid: iid) + end end end end diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb index c7939b29aa8..16eaab4c7c8 100644 --- a/app/helpers/nav/top_nav_helper.rb +++ b/app/helpers/nav/top_nav_helper.rb @@ -132,7 +132,7 @@ module Nav active: active_nav_link?(controller: 'admin/sessions'), icon: 'lock-open', href: destroy_admin_session_path, - method: :post + data: { method: 'post' } ) elsif current_user.admin? builder.add_secondary_menu_item( diff --git a/app/models/member.rb b/app/models/member.rb index 00255f97f85..0c786cb0f94 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -336,7 +336,7 @@ class Member < ApplicationRecord return User.find_by(id: user) if user.is_a?(Integer) - User.find_by(email: user) || user + User.find_by_any_email(user) || user end def retrieve_member(source, user, existing_members) diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb index 6eec03d6d75..6176faec06a 100644 --- a/app/policies/issue_policy.rb +++ b/app/policies/issue_policy.rb @@ -38,6 +38,7 @@ class IssuePolicy < IssuablePolicy rule { ~anonymous & can?(:read_issue) }.policy do enable :create_todo + enable :update_subscription end end diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb index e53a916f3ca..29a7a174759 100644 --- a/app/policies/merge_request_policy.rb +++ b/app/policies/merge_request_policy.rb @@ -20,6 +20,7 @@ class MergeRequestPolicy < IssuablePolicy rule { ~anonymous & can?(:read_merge_request) }.policy do enable :create_todo + enable :update_subscription end condition(:can_merge) { @subject.can_be_merged_by?(@user) } diff --git a/app/views/projects/blob/_header_content.html.haml b/app/views/projects/blob/_header_content.html.haml index b310939c5a3..95a5d63e07f 100644 --- a/app/views/projects/blob/_header_content.html.haml +++ b/app/views/projects/blob/_header_content.html.haml @@ -1,4 +1,6 @@ .file-header-content + - if Gitlab::MarkupHelper.gitlab_markdown?(blob.path) + .js-table-contents = blob_icon blob.mode, blob.name %strong.file-title-name.gl-word-break-all{ data: { qa_selector: 'file_name_content' } } diff --git a/app/views/projects/settings/operations/_alert_management.html.haml b/app/views/projects/settings/operations/_alert_management.html.haml index 0418d7df42d..34255af9cc6 100644 --- a/app/views/projects/settings/operations/_alert_management.html.haml +++ b/app/views/projects/settings/operations/_alert_management.html.haml @@ -6,7 +6,7 @@ %section.settings.no-animate#js-alert-management-settings{ class: ('expanded' if expanded) } .settings-header %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = _('Alert integrations') + = _('Alerts') %button.gl-button.btn.btn-default.js-settings-toggle{ type: 'button' } = _('Expand') %p diff --git a/app/views/projects/settings/operations/_tracing.html.haml b/app/views/projects/settings/operations/_tracing.html.haml index a591fa33096..343fd22c051 100644 --- a/app/views/projects/settings/operations/_tracing.html.haml +++ b/app/views/projects/settings/operations/_tracing.html.haml @@ -1,23 +1,13 @@ - setting = tracing_setting -- has_jaeger_url = setting.external_url.present? %section.settings.border-0.no-animate - .settings-header{ :class => "border-top" } + .settings-header{ :class => 'border-top' } %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = _("Jaeger tracing") + = _('Tracing') %button.btn.btn-default.gl-button.js-settings-toggle{ type: 'button' } = _('Expand') %p - - if has_jaeger_url - - tracing_link = link_to sanitize(setting.external_url, scrubber: Rails::Html::TextOnlyScrubber.new), target: "_blank", rel: 'noopener noreferrer' do - %span - = _('Tracing') - = sprite_icon('external-link', css_class: 'ml-1 vertical-align-middle') - - else - - tracing_link = link_to project_tracing_path(@project) do - %span - = _('Tracing') - = _("To open Jaeger from GitLab to view tracing from the %{link} page, add a URL to your Jaeger server.").html_safe % { link: tracing_link } + = _('Embed an image of your existing Jaeger server in GitLab.') = link_to _('Learn more.'), help_page_path('operations/tracing'), target: '_blank', rel: 'noopener noreferrer' .settings-content = form_for @project, url: project_settings_operations_path(@project), method: :patch do |f| @@ -27,8 +17,8 @@ = form.label :external_url, _('Jaeger URL'), class: 'label-bold' = form.url_field :external_url, class: 'form-control gl-form-input', placeholder: 'https://jaeger.example.com' %p.form-text.text-muted - - jaeger_help_url = "https://www.jaegertracing.io/docs/getting-started/" + - jaeger_help_url = 'https://www.jaegertracing.io/docs/getting-started/' - link_start_tag = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: jaeger_help_url } - - link_end_tag = "#{sprite_icon('external-link', css_class: 'ml-1 vertical-align-middle')}</a>".html_safe - = _("Learn more about %{link_start_tag}Jaeger configuration%{link_end_tag}.").html_safe % { link_start_tag: link_start_tag, link_end_tag: link_end_tag } + - link_end_tag = "#{sprite_icon('external-link', css_class: 'gl-ml-2 gl-vertical-align-middle')}</a>".html_safe + = _('Learn more about %{link_start_tag}Jaeger configuration%{link_end_tag}.').html_safe % { link_start_tag: link_start_tag, link_end_tag: link_end_tag } = f.submit _('Save changes'), class: 'gl-button btn btn-confirm' diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml index af183046e1e..e2c1a00a587 100644 --- a/app/views/projects/settings/operations/show.html.haml +++ b/app/views/projects/settings/operations/show.html.haml @@ -3,11 +3,11 @@ - page_title title - breadcrumb_title title += render 'projects/settings/operations/metrics_dashboard' += render 'projects/settings/operations/tracing' += render 'projects/settings/operations/error_tracking' = render 'projects/settings/operations/alert_management' = render 'projects/settings/operations/incidents' -= render 'projects/settings/operations/error_tracking' -= render 'projects/settings/operations/prometheus', service: prometheus_service if Feature.enabled?(:settings_operations_prometheus_service) -= render 'projects/settings/operations/metrics_dashboard' = render 'projects/settings/operations/grafana_integration' -= render 'projects/settings/operations/tracing' = render_if_exists 'projects/settings/operations/status_page' += render 'projects/settings/operations/prometheus', service: prometheus_service if Feature.enabled?(:settings_operations_prometheus_service) diff --git a/changelogs/unreleased/326808-cleanup-feature-flag.yml b/changelogs/unreleased/326808-cleanup-feature-flag.yml new file mode 100644 index 00000000000..d16b1e5e231 --- /dev/null +++ b/changelogs/unreleased/326808-cleanup-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Removed packages_finder_helper_deploy_token feature flag +merge_request: 62189 +author: +type: other diff --git a/changelogs/unreleased/329319-observe-secondary-email-addresses-when-adding-a-member.yml b/changelogs/unreleased/329319-observe-secondary-email-addresses-when-adding-a-member.yml new file mode 100644 index 00000000000..522d05fae7a --- /dev/null +++ b/changelogs/unreleased/329319-observe-secondary-email-addresses-when-adding-a-member.yml @@ -0,0 +1,5 @@ +--- +title: Observe secondary email addresses when adding a member +merge_request: 62024 +author: +type: changed diff --git a/changelogs/unreleased/330271-remove-support-for-wip-quick-action.yml b/changelogs/unreleased/330271-remove-support-for-wip-quick-action.yml new file mode 100644 index 00000000000..7fbbd4f022c --- /dev/null +++ b/changelogs/unreleased/330271-remove-support-for-wip-quick-action.yml @@ -0,0 +1,5 @@ +--- +title: Remove support for /wip quick action +merge_request: 61199 +author: +type: removed diff --git a/changelogs/unreleased/fix_set_subscription_permission.yml b/changelogs/unreleased/fix_set_subscription_permission.yml new file mode 100644 index 00000000000..847586f2528 --- /dev/null +++ b/changelogs/unreleased/fix_set_subscription_permission.yml @@ -0,0 +1,6 @@ +--- +title: Fix permission check when setting issue/merge request subscription in GraphQL + API. +merge_request: 61980 +author: +type: fixed diff --git a/config/feature_flags/development/packages_finder_helper_deploy_token.yml b/config/feature_flags/development/dast_runner_site_validation.yml index b847942706a..f8ad90062f6 100644 --- a/config/feature_flags/development/packages_finder_helper_deploy_token.yml +++ b/config/feature_flags/development/dast_runner_site_validation.yml @@ -1,8 +1,8 @@ --- -name: packages_finder_helper_deploy_token -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58497 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326808 -milestone: '13.11' +name: dast_runner_site_validation +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/61649 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331082 +milestone: '14.0' type: development -group: group::package -default_enabled: true +group: group::dynamic analysis +default_enabled: false diff --git a/config/metrics/counts_28d/20210216184303_o_pipeline_authoring_unique_users_committing_ciconfigfile_monthly.yml b/config/metrics/counts_28d/20210216184303_o_pipeline_authoring_unique_users_committing_ciconfigfile_monthly.yml index b649192d659..8e8f8b9b65b 100644 --- a/config/metrics/counts_28d/20210216184303_o_pipeline_authoring_unique_users_committing_ciconfigfile_monthly.yml +++ b/config/metrics/counts_28d/20210216184303_o_pipeline_authoring_unique_users_committing_ciconfigfile_monthly.yml @@ -1,16 +1,18 @@ --- key_path: redis_hll_counters.pipeline_authoring.o_pipeline_authoring_unique_users_committing_ciconfigfile_monthly -description: '' -product_section: '' -product_stage: '' -product_group: '' -product_category: '' +description: Monthly unique user count doing commits which contains the CI config file +product_section: ops +product_stage: verify +product_group: group::pipeline authoring +product_category: pipeline_authoring value_type: number status: data_available time_frame: 28d data_source: redis_hll distribution: +- ee - ce tier: - free -skip_validation: true +- premium +- ultimate diff --git a/config/metrics/counts_7d/20210216184301_o_pipeline_authoring_unique_users_committing_ciconfigfile_weekly.yml b/config/metrics/counts_7d/20210216184301_o_pipeline_authoring_unique_users_committing_ciconfigfile_weekly.yml new file mode 100644 index 00000000000..68c75dd579b --- /dev/null +++ b/config/metrics/counts_7d/20210216184301_o_pipeline_authoring_unique_users_committing_ciconfigfile_weekly.yml @@ -0,0 +1,18 @@ +--- +key_path: redis_hll_counters.pipeline_authoring.o_pipeline_authoring_unique_users_committing_ciconfigfile_weekly +description: Weekly unique user count doing commits which contains the CI config file +product_section: ops +product_stage: verify +product_group: group::pipeline authoring +product_category: pipeline_authoring +value_type: number +status: data_available +time_frame: 7d +data_source: redis_hll +distribution: +- ee +- ce +tier: +- free +- premium +- ultimate diff --git a/doc/development/fe_guide/editor_lite.md b/doc/development/fe_guide/editor_lite.md index 5ad0c753ced..f28588c23e9 100644 --- a/doc/development/fe_guide/editor_lite.md +++ b/doc/development/fe_guide/editor_lite.md @@ -15,6 +15,7 @@ GitLab features use it, including: - [CI Linter](../../ci/lint.md) - [Snippets](../../user/snippets.md) - [Web Editor](../../user/project/repository/web_editor.md) +- [Security Policies](../../user/application_security/threat_monitoring/index.md) ## How to use Editor Lite diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md index c69b9e76103..5ad907d029e 100644 --- a/doc/development/usage_ping/dictionary.md +++ b/doc/development/usage_ping/dictionary.md @@ -12828,21 +12828,21 @@ Tiers: `free`, `premium`, `ultimate` ### `redis_hll_counters.pipeline_authoring.o_pipeline_authoring_unique_users_committing_ciconfigfile_monthly` -Missing description +Monthly unique user count doing commits which contains the CI config file [YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210216184303_o_pipeline_authoring_unique_users_committing_ciconfigfile_monthly.yml) -Group: `` +Group: `group::pipeline authoring` Status: `data_available` -Tiers: `free` +Tiers: `free`, `premium`, `ultimate` ### `redis_hll_counters.pipeline_authoring.o_pipeline_authoring_unique_users_committing_ciconfigfile_weekly` -Monthly unique user count doing commits which contains the CI config file +Weekly unique user count doing commits which contains the CI config file -[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210216184301_o_pipeline_authoring_unique_users_committing_ciconfigfile_weekly.yml) +[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_7d/20210216184301_o_pipeline_authoring_unique_users_committing_ciconfigfile_weekly.yml) Group: `group::pipeline authoring` diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md index 07ffb92a000..92fde29624c 100644 --- a/doc/operations/incident_management/integrations.md +++ b/doc/operations/incident_management/integrations.md @@ -20,7 +20,7 @@ to use this endpoint. With Maintainer or higher [permissions](../../user/permissions.md), you can view the list of configured alerts integrations by navigating to **Settings > Operations** -in your project's sidebar menu, and expanding the **Alert integrations** section. The list displays +in your project's sidebar menu, and expanding the **Alerts** section. The list displays the integration name, type, and status (enabled or disabled): ![Current Integrations](img/integrations_list_v13_5.png) @@ -39,7 +39,7 @@ receive alert payloads in JSON format. You can always 1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md) for a project. 1. Navigate to **Settings > Operations** in your project. -1. Expand the **Alert integrations** section, and in the **Select integration type** dropdown menu, +1. Expand the **Alerts** section, and in the **Select integration type** dropdown menu, select **HTTP Endpoint**. 1. Toggle the **Active** alert setting. The URL and Authorization Key for the webhook configuration are available in the **View credentials** tab after you save the integration. You must also input @@ -56,7 +56,7 @@ and you can [customize the payload](#customize-the-alert-payload-outside-of-gitl 1. Sign in to GitLab as a user with maintainer [permissions](../../user/permissions.md) for a project. 1. Navigate to **Settings > Operations** in your project. -1. Expand the **Alert integrations** section. +1. Expand the **Alerts** section. 1. For each endpoint you want to create: 1. Click the **Add new integration** button. diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index e1815785fb5..45cb5e74d6c 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -111,7 +111,6 @@ threads. Some quick actions might not be available to all subscription tiers. | `/unlock` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Unlock the discussions. | | `/unsubscribe` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Unsubscribe from notifications. | | `/weight <value>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set weight. Valid options for `<value>` include `0`, `1`, `2`, and so on. | -| `/wip` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Toggle the draft status. | | `/zoom <Zoom URL>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add Zoom meeting to this issue ([introduced in GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/16609)). | ## Commit messages diff --git a/lib/api/jobs.rb b/lib/api/jobs.rb index cf65bfdfd0e..723a5b0fa3a 100644 --- a/lib/api/jobs.rb +++ b/lib/api/jobs.rb @@ -3,7 +3,6 @@ module API class Jobs < ::API::Base include PaginationParams - before { authenticate! } resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do diff --git a/lib/gitlab/instrumentation/redis.rb b/lib/gitlab/instrumentation/redis.rb index d1ac6a55fb7..9a9d3a866b1 100644 --- a/lib/gitlab/instrumentation/redis.rb +++ b/lib/gitlab/instrumentation/redis.rb @@ -21,10 +21,6 @@ module Gitlab nil end - def known_payload_keys - super + STORAGES.flat_map(&:known_payload_keys) - end - def payload super.merge(*STORAGES.flat_map(&:payload)) end diff --git a/lib/gitlab/instrumentation/redis_payload.rb b/lib/gitlab/instrumentation/redis_payload.rb index 69aafffd124..86a6525c8d0 100644 --- a/lib/gitlab/instrumentation/redis_payload.rb +++ b/lib/gitlab/instrumentation/redis_payload.rb @@ -5,12 +5,6 @@ module Gitlab module RedisPayload include ::Gitlab::Utils::StrongMemoize - # Fetches payload keys from the lazy payload (this avoids - # unnecessary processing of the values). - def known_payload_keys - to_lazy_payload.keys - end - def payload to_lazy_payload.transform_values do |value| result = value.call diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb index 3db3317e833..24006d75c4d 100644 --- a/lib/gitlab/metrics/subscribers/active_record.rb +++ b/lib/gitlab/metrics/subscribers/active_record.rb @@ -51,10 +51,6 @@ module Gitlab payload end - def self.known_payload_keys - DB_COUNTERS - end - private def ignored_query?(payload) diff --git a/lib/gitlab/metrics/subscribers/external_http.rb b/lib/gitlab/metrics/subscribers/external_http.rb index 0df64f2897e..60a1b084345 100644 --- a/lib/gitlab/metrics/subscribers/external_http.rb +++ b/lib/gitlab/metrics/subscribers/external_http.rb @@ -14,8 +14,6 @@ module Gitlab COUNTER = :external_http_count DURATION = :external_http_duration_s - KNOWN_PAYLOAD_KEYS = [COUNTER, DURATION].freeze - def self.detail_store ::Gitlab::SafeRequestStore[DETAIL_STORE] ||= [] end diff --git a/lib/gitlab/nav/top_nav_menu_item.rb b/lib/gitlab/nav/top_nav_menu_item.rb index ee11f1f4560..a5f835136fc 100644 --- a/lib/gitlab/nav/top_nav_menu_item.rb +++ b/lib/gitlab/nav/top_nav_menu_item.rb @@ -8,14 +8,13 @@ module Gitlab # this is already :/. We could also take a hash and manually check every # entry, but it's much more maintainable to do rely on native Ruby. # rubocop: disable Metrics/ParameterLists - def self.build(id:, title:, active: false, icon: '', href: '', method: nil, view: '', css_class: '', data: {}) + def self.build(id:, title:, active: false, icon: '', href: '', view: '', css_class: '', data: {}) { id: id, title: title, active: active, icon: icon, href: href, - method: method, view: view.to_s, css_class: css_class, data: data diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb index f3c6315cd6a..47c76e98e5c 100644 --- a/lib/gitlab/quick_actions/merge_request_actions.rb +++ b/lib/gitlab/quick_actions/merge_request_actions.rb @@ -99,7 +99,7 @@ module Gitlab # Allow it to mark as WIP on MR creation page _or_ through MR notes. (quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target)) end - command :draft, :wip do + command :draft do @updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip' end diff --git a/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb b/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb index b542aa4fe4c..1f0c63c5fff 100644 --- a/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb +++ b/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb @@ -3,24 +3,6 @@ module Gitlab module SidekiqMiddleware class InstrumentationLogger - def self.keys - @keys ||= [ - :cpu_s, - :gitaly_calls, - :gitaly_duration_s, - :rugged_calls, - :rugged_duration_s, - :elasticsearch_calls, - :elasticsearch_duration_s, - :elasticsearch_timed_out_count, - *::Gitlab::Memory::Instrumentation::KEY_MAPPING.values, - *::Gitlab::Instrumentation::Redis.known_payload_keys, - *::Gitlab::Metrics::Subscribers::ActiveRecord.known_payload_keys, - *::Gitlab::Metrics::Subscribers::ExternalHttp::KNOWN_PAYLOAD_KEYS, - *::Gitlab::Metrics::Subscribers::RackAttack::PAYLOAD_KEYS - ] - end - def call(worker, job, queue) ::Gitlab::InstrumentationHelper.init_instrumentation_data @@ -37,7 +19,6 @@ module Gitlab # https://github.com/mperham/sidekiq/blob/53bd529a0c3f901879925b8390353129c465b1f2/lib/sidekiq/processor.rb#L115-L118 job[:instrumentation] = {}.tap do |instrumentation_values| ::Gitlab::InstrumentationHelper.add_instrumentation_data(instrumentation_values) - instrumentation_values.slice!(*self.class.keys) end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ec15cb103e8..34372eb6c90 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2859,9 +2859,6 @@ msgid_plural "Alerts" msgstr[0] "" msgstr[1] "" -msgid "Alert integrations" -msgstr "" - msgid "AlertManagement|Acknowledged" msgstr "" @@ -8424,6 +8421,9 @@ msgstr "" msgid "Configuration" msgstr "" +msgid "Configuration help" +msgstr "" + msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}" msgstr "" @@ -12130,6 +12130,9 @@ msgstr "" msgid "Embed" msgstr "" +msgid "Embed an image of your existing Jaeger server in GitLab." +msgstr "" + msgid "Empty file" msgstr "" @@ -12214,6 +12217,9 @@ msgstr "" msgid "Enable authenticated API request rate limit" msgstr "" +msgid "Enable authentication" +msgstr "" + msgid "Enable automatic repository housekeeping (git repack, git gc)" msgstr "" @@ -15509,7 +15515,7 @@ msgstr "" msgid "GrafanaIntegration|Active" msgstr "" -msgid "GrafanaIntegration|Enter the Grafana API token." +msgid "GrafanaIntegration|Enter the %{docLinkStart}Grafana API token%{docLinkEnd}." msgstr "" msgid "GrafanaIntegration|Enter the base URL of the Grafana instance." @@ -17382,6 +17388,9 @@ msgstr "" msgid "IncidentSettings|Alert integration" msgstr "" +msgid "IncidentSettings|Fine-tune incident settings and set up integrations with external tools to help better manage incidents." +msgstr "" + msgid "IncidentSettings|Grafana integration" msgstr "" @@ -17397,9 +17406,6 @@ msgstr "" msgid "IncidentSettings|PagerDuty integration" msgstr "" -msgid "IncidentSettings|Set up integrations with external tools to help better manage incidents." -msgstr "" - msgid "IncidentSettings|Time limit" msgstr "" @@ -18560,9 +18566,6 @@ msgstr "" msgid "Jaeger URL" msgstr "" -msgid "Jaeger tracing" -msgstr "" - msgid "Jan" msgstr "" @@ -20907,10 +20910,10 @@ msgstr "" msgid "MetricsSettings|External dashboard URL" msgstr "" -msgid "MetricsSettings|Manage Metrics Dashboard settings." +msgid "MetricsSettings|Manage metrics dashboard settings." msgstr "" -msgid "MetricsSettings|Metrics dashboard" +msgid "MetricsSettings|Metrics" msgstr "" msgid "MetricsSettings|UTC (Coordinated Universal Time)" @@ -31126,9 +31129,6 @@ msgstr "" msgid "StatusPage|Status page URL" msgstr "" -msgid "StatusPage|Status page frontend documentation" -msgstr "" - msgid "StatusPage|To publish incidents to an external status page, GitLab stores a JSON file in your Amazon S3 account at a location that your external status page service can access. Make sure to also set up %{docsLink}" msgstr "" @@ -34049,9 +34049,6 @@ msgstr "" msgid "To only use CI/CD features for an external repository, choose %{strong_open}CI/CD for external repo%{strong_close}." msgstr "" -msgid "To open Jaeger from GitLab to view tracing from the %{link} page, add a URL to your Jaeger server." -msgstr "" - msgid "To pass variables to the triggered pipeline, add %{code_start}variables[VARIABLE]=VALUE%{code_end} to the API request." msgstr "" diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry_spec.rb index 7a71d1cfbaf..635313b9fb0 100644 --- a/qa/qa/specs/features/browser_ui/5_package/container_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/container_registry_spec.rb @@ -30,6 +30,8 @@ module QA DOCKER_TLS_CERTDIR: "/certs" DOCKER_TLS_VERIFY: 1 DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client" + before_script: + - until docker info; do sleep 1; done script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker build -t $IMAGE_TAG . diff --git a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb index 6675abd6b42..60f2f776595 100644 --- a/spec/features/alerts_settings/user_views_alerts_settings_spec.rb +++ b/spec/features/alerts_settings/user_views_alerts_settings_spec.rb @@ -25,7 +25,7 @@ RSpec.describe 'Alert integrations settings form', :js do it 'shows the alerts setting form title' do page.within('#js-alert-management-settings') do - expect(find('h4')).to have_content('Alert integrations') + expect(find('h4')).to have_content('Alerts') end end diff --git a/spec/features/projects/settings/user_searches_in_settings_spec.rb b/spec/features/projects/settings/user_searches_in_settings_spec.rb index 9b09958bae5..a60743f0e47 100644 --- a/spec/features/projects/settings/user_searches_in_settings_spec.rb +++ b/spec/features/projects/settings/user_searches_in_settings_spec.rb @@ -63,7 +63,7 @@ RSpec.describe 'User searches project settings', :js do visit project_settings_operations_path(project) end - it_behaves_like 'can search settings', 'Alert integrations', 'Error tracking' + it_behaves_like 'can search settings', 'Alerts', 'Error tracking' end context 'in Pages page' do diff --git a/spec/finders/concerns/packages/finder_helper_spec.rb b/spec/finders/concerns/packages/finder_helper_spec.rb index bad4c482bc6..e8648d131ff 100644 --- a/spec/finders/concerns/packages/finder_helper_spec.rb +++ b/spec/finders/concerns/packages/finder_helper_spec.rb @@ -113,41 +113,22 @@ RSpec.describe ::Packages::FinderHelper do let_it_be(:user) { create(:deploy_token, :group, read_package_registry: true) } let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) } - shared_examples 'handling all conditions' do - where(:group_visibility, :subgroup_visibility, :project2_visibility, :shared_example_name) do - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | 'returning both packages' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | 'returning both packages' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | 'returning both packages' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | 'returning both packages' - end - - with_them do - before do - project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) - subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) - project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) - group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) - end - - it_behaves_like params[:shared_example_name] - end - end - - context 'with packages_finder_helper_deploy_token enabled' do - before do - expect(group).not_to receive(:all_projects) - end - - it_behaves_like 'handling all conditions' + where(:group_visibility, :subgroup_visibility, :project2_visibility, :shared_example_name) do + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | 'returning both packages' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | 'returning both packages' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | 'returning both packages' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | 'returning both packages' end - context 'with packages_finder_helper_deploy_token disabled' do + with_them do before do - stub_feature_flags(packages_finder_helper_deploy_token: false) - expect(group).to receive(:all_projects).and_call_original + project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) + subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) + project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) end - it_behaves_like 'handling all conditions' + it_behaves_like params[:shared_example_name] end end end @@ -236,41 +217,22 @@ RSpec.describe ::Packages::FinderHelper do let_it_be(:user) { create(:deploy_token, :group, read_package_registry: true) } let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) } - shared_examples 'handling all conditions' do - where(:group_visibility, :subgroup_visibility, :project2_visibility, :shared_example_name) do - 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | 'returning both projects' - 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | 'returning both projects' - 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | 'returning both projects' - 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | 'returning both projects' - end - - with_them do - before do - project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) - subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) - project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) - group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) - end - - it_behaves_like params[:shared_example_name] - end - end - - context 'with packages_finder_helper_deploy_token enabled' do - before do - expect(group).not_to receive(:all_projects) - end - - it_behaves_like 'handling all conditions' + where(:group_visibility, :subgroup_visibility, :project2_visibility, :shared_example_name) do + 'PUBLIC' | 'PUBLIC' | 'PUBLIC' | 'returning both projects' + 'PUBLIC' | 'PUBLIC' | 'PRIVATE' | 'returning both projects' + 'PUBLIC' | 'PRIVATE' | 'PRIVATE' | 'returning both projects' + 'PRIVATE' | 'PRIVATE' | 'PRIVATE' | 'returning both projects' end - context 'with packages_finder_helper_deploy_token disabled' do + with_them do before do - stub_feature_flags(packages_finder_helper_deploy_token: false) - expect(group).to receive(:all_projects).and_call_original + project2.update!(visibility_level: Gitlab::VisibilityLevel.const_get(project2_visibility, false)) + subgroup.update!(visibility_level: Gitlab::VisibilityLevel.const_get(subgroup_visibility, false)) + project1.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) + group.update!(visibility_level: Gitlab::VisibilityLevel.const_get(group_visibility, false)) end - it_behaves_like 'handling all conditions' + it_behaves_like params[:shared_example_name] end end end diff --git a/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js b/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js index c43d78a1cf3..3ffbb7ab60a 100644 --- a/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js +++ b/spec/frontend/alerts_settings/components/alerts_integrations_list_spec.js @@ -80,7 +80,7 @@ describe('AlertIntegrationsList', () => { const cell = finsStatusCell().at(0); const activatedIcon = cell.find(GlIcon); expect(cell.text()).toBe(i18n.status.enabled.name); - expect(activatedIcon.attributes('name')).toBe('check-circle-filled'); + expect(activatedIcon.attributes('name')).toBe('check'); expect(activatedIcon.attributes('title')).toBe(i18n.status.enabled.tooltip); }); diff --git a/spec/frontend/blob/components/table_contents_spec.js b/spec/frontend/blob/components/table_contents_spec.js new file mode 100644 index 00000000000..09633dc5d5d --- /dev/null +++ b/spec/frontend/blob/components/table_contents_spec.js @@ -0,0 +1,67 @@ +import { GlDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import TableContents from '~/blob/components/table_contents.vue'; + +let wrapper; + +function createComponent() { + wrapper = shallowMount(TableContents); +} + +async function setLoaded(loaded) { + document.querySelector('.blob-viewer').setAttribute('data-loaded', loaded); + + await nextTick(); +} + +describe('Markdown table of contents component', () => { + beforeEach(() => { + setFixtures(` + <div class="blob-viewer" data-type="rich" data-loaded="false"> + <h1><a href="#1"></a>Hello</h1> + <h2><a href="#2"></a>World</h2> + <h3><a href="#3"></a>Testing</h3> + <h2><a href="#4"></a>GitLab</h2> + </div> + `); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('not loaded', () => { + it('does not populate dropdown', () => { + createComponent(); + + expect(wrapper.findComponent(GlDropdownItem).exists()).toBe(false); + }); + }); + + describe('loaded', () => { + it('populates dropdown', async () => { + createComponent(); + + await setLoaded(true); + + const dropdownItems = wrapper.findAllComponents(GlDropdownItem); + + expect(dropdownItems.exists()).toBe(true); + expect(dropdownItems.length).toBe(4); + }); + + it('sets padding for dropdown items', async () => { + createComponent(); + + await setLoaded(true); + + const dropdownLinks = wrapper.findAll('[data-testid="tableContentsLink"]'); + + expect(dropdownLinks.at(0).element.style.paddingLeft).toBe('0px'); + expect(dropdownLinks.at(1).element.style.paddingLeft).toBe('8px'); + expect(dropdownLinks.at(2).element.style.paddingLeft).toBe('16px'); + expect(dropdownLinks.at(3).element.style.paddingLeft).toBe('8px'); + }); + }); +}); diff --git a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap index 1d2a5d636bc..7082d652706 100644 --- a/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap +++ b/spec/frontend/grafana_integration/components/__snapshots__/grafana_integration_spec.js.snap @@ -43,14 +43,18 @@ exports[`grafana integration component default state to match the default snapsh class="settings-content" > <form> - <gl-form-checkbox-stub - class="mb-4" - id="grafana-integration-enabled" + <gl-form-group-stub + label="Enable authentication" + label-for="grafana-integration-enabled" > + <gl-form-checkbox-stub + id="grafana-integration-enabled" + > + + Active - Active - - </gl-form-checkbox-stub> + </gl-form-checkbox-stub> + </gl-form-group-stub> <gl-form-group-stub description="Enter the base URL of the Grafana instance." @@ -76,32 +80,19 @@ exports[`grafana integration component default state to match the default snapsh <p class="form-text text-muted" > - - Enter the Grafana API token. - - <a - href="https://grafana.com/docs/http_api/auth/#create-api-token" - rel="noopener noreferrer" - target="_blank" - > - - More information. - - <gl-icon-stub - class="vertical-align-middle" - name="external-link" - size="16" - /> - </a> + <gl-sprintf-stub + message="Enter the %{docLinkStart}Grafana API token%{docLinkEnd}." + /> </p> </gl-form-group-stub> <gl-button-stub buttontextclasses="" category="primary" + data-testid="save-grafana-settings-button" icon="" size="medium" - variant="success" + variant="confirm" > Save changes diff --git a/spec/frontend/grafana_integration/components/grafana_integration_spec.js b/spec/frontend/grafana_integration/components/grafana_integration_spec.js index f1a8e6fe2dc..d894cc7cc75 100644 --- a/spec/frontend/grafana_integration/components/grafana_integration_spec.js +++ b/spec/frontend/grafana_integration/components/grafana_integration_spec.js @@ -1,6 +1,7 @@ import { GlButton } from '@gitlab/ui'; -import { mount, shallowMount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import GrafanaIntegration from '~/grafana_integration/components/grafana_integration.vue'; import { createStore } from '~/grafana_integration/store'; @@ -51,8 +52,7 @@ describe('grafana integration component', () => { it('renders as an expand button by default', () => { wrapper = shallowMount(GrafanaIntegration, { store }); - const button = wrapper.find(GlButton); - + const button = wrapper.findComponent(GlButton); expect(button.text()).toBe('Expand'); }); }); @@ -70,6 +70,7 @@ describe('grafana integration component', () => { describe('form', () => { beforeEach(() => { jest.spyOn(axios, 'patch').mockImplementation(); + wrapper = mountExtended(GrafanaIntegration, { store }); }); afterEach(() => { @@ -77,7 +78,7 @@ describe('grafana integration component', () => { }); describe('submit button', () => { - const findSubmitButton = () => wrapper.find('.settings-content form').find(GlButton); + const findSubmitButton = () => wrapper.findByTestId('save-grafana-settings-button'); const endpointRequest = [ operationsSettingsEndpoint, @@ -93,9 +94,7 @@ describe('grafana integration component', () => { ]; it('submits form on click', () => { - wrapper = mount(GrafanaIntegration, { store }); axios.patch.mockResolvedValue(); - findSubmitButton(wrapper).trigger('click'); expect(axios.patch).toHaveBeenCalledWith(...endpointRequest); @@ -104,7 +103,6 @@ describe('grafana integration component', () => { it('creates flash banner on error', () => { const message = 'mockErrorMessage'; - wrapper = mount(GrafanaIntegration, { store }); axios.patch.mockRejectedValue({ response: { data: { message } } }); findSubmitButton().trigger('click'); diff --git a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap index 85d21f231b1..3d32585a211 100644 --- a/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap +++ b/spec/frontend/incidents_settings/components/__snapshots__/alerts_form_spec.js.snap @@ -103,7 +103,7 @@ exports[`Alert integration settings form default state should match the default icon="" size="medium" type="submit" - variant="success" + variant="confirm" > Save changes diff --git a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap index 07f90a12f0f..450b1236015 100644 --- a/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap +++ b/spec/frontend/incidents_settings/components/__snapshots__/incidents_settings_tabs_spec.js.snap @@ -30,7 +30,7 @@ exports[`IncidentsSettingTabs should render the component 1`] = ` <p> - Set up integrations with external tools to help better manage incidents. + Fine-tune incident settings and set up integrations with external tools to help better manage incidents. </p> </div> diff --git a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap index 79ad5ad1bb9..a374ac7e4f2 100644 --- a/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap +++ b/spec/frontend/incidents_settings/components/__snapshots__/pagerduty_form_spec.js.snap @@ -66,20 +66,6 @@ exports[`Alert integration settings form should match the default snapshot 1`] = </gl-modal-stub> </gl-form-group-stub> - - <gl-button-stub - buttontextclasses="" - category="primary" - class="js-no-auto-disable" - icon="" - size="medium" - type="submit" - variant="success" - > - - Save changes - - </gl-button-stub> </form> </div> `; diff --git a/spec/frontend/incidents_settings/components/pagerduty_form_spec.js b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js index 2ffd1292ddc..d2b591d427d 100644 --- a/spec/frontend/incidents_settings/components/pagerduty_form_spec.js +++ b/spec/frontend/incidents_settings/components/pagerduty_form_spec.js @@ -1,5 +1,5 @@ -import { GlAlert, GlModal } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlAlert, GlModal, GlToggle } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; import PagerDutySettingsForm from '~/incidents_settings/components/pagerduty_form.vue'; @@ -8,13 +8,13 @@ describe('Alert integration settings form', () => { const resetWebhookUrl = jest.fn(); const service = { updateSettings: jest.fn().mockResolvedValue(), resetWebhookUrl }; - const findForm = () => wrapper.find({ ref: 'settingsForm' }); - const findWebhookInput = () => wrapper.find('[data-testid="webhook-url"]'); - const findModal = () => wrapper.find(GlModal); - const findAlert = () => wrapper.find(GlAlert); + const findWebhookInput = () => wrapper.findByTestId('webhook-url'); + const findFormToggle = () => wrapper.findComponent(GlToggle); + const findModal = () => wrapper.findComponent(GlModal); + const findAlert = () => wrapper.findComponent(GlAlert); beforeEach(() => { - wrapper = shallowMount(PagerDutySettingsForm, { + wrapper = shallowMountExtended(PagerDutySettingsForm, { provide: { service, pagerDutySettings: { @@ -27,18 +27,15 @@ describe('Alert integration settings form', () => { }); afterEach(() => { - if (wrapper) { - wrapper.destroy(); - wrapper = null; - } + wrapper.destroy(); }); it('should match the default snapshot', () => { expect(wrapper.element).toMatchSnapshot(); }); - it('should call service `updateSettings` on form submit', () => { - findForm().trigger('submit'); + it('should call service `updateSettings` on toggle change', () => { + findFormToggle().vm.$emit('change', true); expect(service.updateSettings).toHaveBeenCalledWith( expect.objectContaining({ pagerduty_active: wrapper.vm.active }), ); diff --git a/spec/frontend/nav/components/top_nav_menu_item_spec.js b/spec/frontend/nav/components/top_nav_menu_item_spec.js index 579af13d08a..e2cacba1a68 100644 --- a/spec/frontend/nav/components/top_nav_menu_item_spec.js +++ b/spec/frontend/nav/components/top_nav_menu_item_spec.js @@ -7,6 +7,8 @@ const TEST_MENU_ITEM = { icon: 'search', href: '/pretty/good/burger', view: 'burger-view', + css_class: 'test-super-crazy test-class', + data: { qa_selector: 'not-a-real-selector', method: 'post', testFoo: 'test' }, }; describe('~/nav/components/top_nav_menu_item.vue', () => { @@ -47,6 +49,22 @@ describe('~/nav/components/top_nav_menu_item.vue', () => { expect(button.text()).toBe(TEST_MENU_ITEM.title); }); + it('renders button classes', () => { + const button = findButton(); + + expect(button.classes()).toEqual(expect.arrayContaining(TEST_MENU_ITEM.css_class.split(' '))); + }); + + it('renders button data attributes', () => { + const button = findButton(); + + expect(button.attributes()).toMatchObject({ + 'data-qa-selector': TEST_MENU_ITEM.data.qa_selector, + 'data-method': TEST_MENU_ITEM.data.method, + 'data-test-foo': TEST_MENU_ITEM.data.testFoo, + }); + }); + it('passes listeners to button', () => { expect(listener).not.toHaveBeenCalled(); diff --git a/spec/frontend/operation_settings/components/metrics_settings_spec.js b/spec/frontend/operation_settings/components/metrics_settings_spec.js index 272e9b71f67..9d2530ded1e 100644 --- a/spec/frontend/operation_settings/components/metrics_settings_spec.js +++ b/spec/frontend/operation_settings/components/metrics_settings_spec.js @@ -56,7 +56,7 @@ describe('operation settings external dashboard component', () => { it('renders header text', () => { mountComponent(); - expect(wrapper.find('.js-section-header').text()).toBe('Metrics dashboard'); + expect(wrapper.find('.js-section-header').text()).toBe('Metrics'); }); describe('expand/collapse button', () => { @@ -77,13 +77,13 @@ describe('operation settings external dashboard component', () => { }); it('renders descriptive text', () => { - expect(subHeader.text()).toContain('Manage Metrics Dashboard settings.'); + expect(subHeader.text()).toContain('Manage metrics dashboard settings.'); }); it('renders help page link', () => { const link = subHeader.find(GlLink); - expect(link.text()).toBe('Learn more'); + expect(link.text()).toBe('Learn more.'); expect(link.attributes().href).toBe(helpPage); }); }); diff --git a/spec/graphql/mutations/issues/set_subscription_spec.rb b/spec/graphql/mutations/issues/set_subscription_spec.rb index 9e05a136c0b..7e2c3d93c51 100644 --- a/spec/graphql/mutations/issues/set_subscription_spec.rb +++ b/spec/graphql/mutations/issues/set_subscription_spec.rb @@ -3,8 +3,38 @@ require 'spec_helper' RSpec.describe Mutations::Issues::SetSubscription do - it_behaves_like 'a subscribeable graphql resource' do - let_it_be(:resource) { create(:issue) } - let(:permission_name) { :update_issue } + let_it_be_with_reload(:project) { create(:project) } + let_it_be_with_reload(:resource) { create(:issue, project: project) } + let_it_be(:user) { create(:user) } + + specify { expect(described_class).to require_graphql_authorizations(:update_subscription) } + + context 'when user does not have access to the project' do + it_behaves_like 'a subscribeable not accessible graphql resource' + end + + context 'when user is developer member of the project' do + before do + project.add_developer(user) + end + + it_behaves_like 'a subscribeable graphql resource' + end + + context 'when the project is public' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it_behaves_like 'a subscribeable graphql resource' + end + + context 'when the project is public but the issue is confidential' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + resource.update!(confidential: true) + end + + it_behaves_like 'a subscribeable not accessible graphql resource' end end diff --git a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb index 600053637c9..377042f068c 100644 --- a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb @@ -3,8 +3,30 @@ require 'spec_helper' RSpec.describe Mutations::MergeRequests::SetSubscription do - it_behaves_like 'a subscribeable graphql resource' do - let_it_be(:resource) { create(:merge_request) } - let(:permission_name) { :update_merge_request } + let_it_be_with_reload(:project) { create(:project) } + let_it_be(:user) { create(:user) } + + let(:resource) { create(:merge_request, source_project: project, target_project: project) } + + specify { expect(described_class).to require_graphql_authorizations(:update_subscription) } + + context 'when user does not have access to the project' do + it_behaves_like 'a subscribeable not accessible graphql resource' + end + + context 'when user is developer member of the project' do + before do + project.add_developer(user) + end + + it_behaves_like 'a subscribeable graphql resource' + end + + context 'when the project is public' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it_behaves_like 'a subscribeable graphql resource' end end diff --git a/spec/helpers/nav/top_nav_helper_spec.rb b/spec/helpers/nav/top_nav_helper_spec.rb index b73d77a4ea9..b9912171a73 100644 --- a/spec/helpers/nav/top_nav_helper_spec.rb +++ b/spec/helpers/nav/top_nav_helper_spec.rb @@ -424,7 +424,7 @@ RSpec.describe Nav::TopNavHelper do title: 'Leave Admin Mode', icon: 'lock-open', href: '/admin/session/destroy', - method: :post + data: { method: 'post' } ) expect(subject[:secondary].last).to eq(expected_leave_admin_mode_item) end diff --git a/spec/lib/gitlab/instrumentation/redis_base_spec.rb b/spec/lib/gitlab/instrumentation/redis_base_spec.rb index 07be0ccf6e9..a7e08b5a9bd 100644 --- a/spec/lib/gitlab/instrumentation/redis_base_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_base_spec.rb @@ -18,24 +18,6 @@ RSpec.describe Gitlab::Instrumentation::RedisBase, :request_store do end end - describe '.known_payload_keys' do - it 'returns generated payload keys' do - expect(instrumentation_class_a.known_payload_keys).to eq([:redis_instance_a_calls, - :redis_instance_a_duration_s, - :redis_instance_a_read_bytes, - :redis_instance_a_write_bytes]) - end - - it 'does not call calculation methods' do - expect(instrumentation_class_a).not_to receive(:get_request_count) - expect(instrumentation_class_a).not_to receive(:query_time) - expect(instrumentation_class_a).not_to receive(:read_bytes) - expect(instrumentation_class_a).not_to receive(:write_bytes) - - instrumentation_class_a.known_payload_keys - end - end - describe '.payload' do it 'returns values that are higher than 0' do allow(instrumentation_class_a).to receive(:get_request_count) { 1 } diff --git a/spec/lib/gitlab/instrumentation/redis_spec.rb b/spec/lib/gitlab/instrumentation/redis_spec.rb index e927f39cae2..d712d09bdd8 100644 --- a/spec/lib/gitlab/instrumentation/redis_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_spec.rb @@ -26,46 +26,6 @@ RSpec.describe Gitlab::Instrumentation::Redis do it_behaves_like 'aggregation of redis storage data', :read_bytes it_behaves_like 'aggregation of redis storage data', :write_bytes - describe '.known_payload_keys' do - it 'returns all known payload keys' do - expected_keys = [ - :redis_calls, - :redis_duration_s, - :redis_read_bytes, - :redis_write_bytes, - :redis_action_cable_calls, - :redis_action_cable_duration_s, - :redis_action_cable_read_bytes, - :redis_action_cable_write_bytes, - :redis_cache_calls, - :redis_cache_duration_s, - :redis_cache_read_bytes, - :redis_cache_write_bytes, - :redis_queues_calls, - :redis_queues_duration_s, - :redis_queues_read_bytes, - :redis_queues_write_bytes, - :redis_shared_state_calls, - :redis_shared_state_duration_s, - :redis_shared_state_read_bytes, - :redis_shared_state_write_bytes - ] - - expect(described_class.known_payload_keys).to eq(expected_keys) - end - - it 'does not call storage calculation methods' do - described_class::STORAGES.each do |storage| - expect(storage).not_to receive(:get_request_count) - expect(storage).not_to receive(:query_time) - expect(storage).not_to receive(:read_bytes) - expect(storage).not_to receive(:write_bytes) - end - - described_class.known_payload_keys - end - end - describe '.payload', :request_store do before do Gitlab::Redis::Cache.with { |redis| redis.set('cache-test', 321) } diff --git a/spec/lib/gitlab/nav/top_nav_menu_item_spec.rb b/spec/lib/gitlab/nav/top_nav_menu_item_spec.rb index 26f9ea3a637..c96bed2284b 100644 --- a/spec/lib/gitlab/nav/top_nav_menu_item_spec.rb +++ b/spec/lib/gitlab/nav/top_nav_menu_item_spec.rb @@ -11,7 +11,6 @@ RSpec.describe ::Gitlab::Nav::TopNavMenuItem do active: true, icon: 'icon', href: 'href', - method: 'method', view: 'view', css_class: 'css_class', data: {} diff --git a/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb b/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb index eb9ba50cdcd..8cf65e1be5b 100644 --- a/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb +++ b/spec/lib/gitlab/sidekiq_middleware/instrumentation_logger_spec.rb @@ -24,58 +24,10 @@ RSpec.describe Gitlab::SidekiqMiddleware::InstrumentationLogger do stub_const('TestWorker', worker) end - describe '.keys' do - it 'returns all available payload keys' do - expected_keys = [ - :cpu_s, - :gitaly_calls, - :gitaly_duration_s, - :rugged_calls, - :rugged_duration_s, - :elasticsearch_calls, - :elasticsearch_duration_s, - :elasticsearch_timed_out_count, - :mem_objects, - :mem_bytes, - :mem_mallocs, - :redis_calls, - :redis_duration_s, - :redis_read_bytes, - :redis_write_bytes, - :redis_action_cable_calls, - :redis_action_cable_duration_s, - :redis_action_cable_read_bytes, - :redis_action_cable_write_bytes, - :redis_cache_calls, - :redis_cache_duration_s, - :redis_cache_read_bytes, - :redis_cache_write_bytes, - :redis_queues_calls, - :redis_queues_duration_s, - :redis_queues_read_bytes, - :redis_queues_write_bytes, - :redis_shared_state_calls, - :redis_shared_state_duration_s, - :redis_shared_state_read_bytes, - :redis_shared_state_write_bytes, - :db_count, - :db_write_count, - :db_cached_count, - :external_http_count, - :external_http_duration_s, - :rack_attack_redis_count, - :rack_attack_redis_duration_s - ] - - expect(described_class.keys).to include(*expected_keys) - end - end - describe '#call', :request_store do let(:instrumentation_values) do { cpu_s: 10, - unknown_attribute: 123, db_count: 0, db_cached_count: 0, db_write_count: 0, @@ -90,12 +42,10 @@ RSpec.describe Gitlab::SidekiqMiddleware::InstrumentationLogger do end end - it 'merges correct instrumentation data in the job' do + it 'merges all instrumentation data in the job' do expect { |b| subject.call(worker, job, queue, &b) }.to yield_control - expected_values = instrumentation_values.except(:unknown_attribute) - - expect(job[:instrumentation]).to eq(expected_values) + expect(job[:instrumentation]).to eq(instrumentation_values) end end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 72cd028b0b9..b0575b63c8a 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -594,6 +594,18 @@ RSpec.describe Member do end end + context 'when called with a known user secondary email' do + let(:secondary_email) { create(:email, email: 'secondary@example.com', user: user) } + + it 'adds the user as a member' do + expect(source.users).not_to include(user) + + described_class.add_user(source, secondary_email.email, :maintainer) + + expect(source.users.reload).to include(user) + end + end + context 'when called with an unknown user email' do it 'creates an invited member' do expect(source.users).not_to include(user) diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb index 76788ae2cb7..ed0050e8224 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -139,13 +139,14 @@ RSpec.describe IssuePolicy do create(:project_group_link, group: group, project: project) end - it 'does not allow guest to create todos' do + it 'does not allow anonymous user to create todos' do expect(permissions(nil, issue)).to be_allowed(:read_issue) expect(permissions(nil, issue)).to be_disallowed(:create_todo) + expect(permissions(nil, issue)).to be_disallowed(:update_subscription) end it 'allows guests to read issues' do - expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid, :create_todo) + expect(permissions(guest, issue)).to be_allowed(:read_issue, :read_issue_iid, :create_todo, :update_subscription) expect(permissions(guest, issue)).to be_disallowed(:update_issue, :admin_issue, :reopen_issue) expect(permissions(guest, issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid) @@ -205,12 +206,18 @@ RSpec.describe IssuePolicy do it 'forbids visitors from commenting' do expect(permissions(visitor, issue)).to be_disallowed(:create_note) end + it 'forbids visitors from subscribing' do + expect(permissions(visitor, issue)).to be_disallowed(:update_subscription) + end it 'allows guests to view' do expect(permissions(guest, issue)).to be_allowed(:read_issue) end it 'allows guests to comment' do expect(permissions(guest, issue)).to be_allowed(:create_note) end + it 'allows guests to subscribe' do + expect(permissions(guest, issue)).to be_allowed(:update_subscription) + end context 'when admin mode is enabled', :enable_admin_mode do it 'allows admins to view' do diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb index 744822f58d1..b94df4d4374 100644 --- a/spec/policies/merge_request_policy_spec.rb +++ b/spec/policies/merge_request_policy_spec.rb @@ -26,7 +26,8 @@ RSpec.describe MergeRequestPolicy do read_merge_request create_todo approve_merge_request - create_note].freeze + create_note + update_subscription].freeze shared_examples_for 'a denied user' do let(:perms) { permissions(subject, merge_request) } @@ -55,7 +56,7 @@ RSpec.describe MergeRequestPolicy do subject { permissions(nil, merge_request) } it do - is_expected.to be_disallowed(:create_todo) + is_expected.to be_disallowed(:create_todo, :update_subscription) end end end diff --git a/spec/services/members/invite_service_spec.rb b/spec/services/members/invite_service_spec.rb index d7fd7d5b2ca..7007d37ad8d 100644 --- a/spec/services/members/invite_service_spec.rb +++ b/spec/services/members/invite_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_shared_state, :sidekiq_inline do - let_it_be(:project) { create(:project) } + let_it_be(:project, reload: true) { create(:project) } let_it_be(:user) { project.owner } let_it_be(:project_user) { create(:user) } let_it_be(:namespace) { project.namespace } @@ -23,6 +23,18 @@ RSpec.describe Members::InviteService, :aggregate_failures, :clean_gitlab_redis_ it_behaves_like 'records an onboarding progress action', :user_added end + context 'when email belongs to an existing user as a secondary email' do + let(:secondary_email) { create(:email, email: 'secondary@example.com', user: project_user) } + let(:params) { { email: secondary_email.email } } + + it 'adds an existing user to members', :aggregate_failures do + expect_to_create_members(count: 1) + expect(result[:status]).to eq(:success) + expect(project.users).to include project_user + expect(project.members.last).not_to be_invite + end + end + context 'when email is not a valid email' do let(:params) { { email: '_bogus_' } } diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index b2351ab53bd..da547716e1e 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -82,7 +82,7 @@ RSpec.describe MergeRequests::CreateService, :clean_gitlab_redis_shared_state do let(:opts) do { title: 'Awesome merge_request', - description: "well this is not done yet\n/wip", + description: "well this is not done yet\n/draft", source_branch: 'feature', target_branch: 'master', assignees: [user2] diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 31263feb947..5b4d6188b66 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -307,7 +307,7 @@ RSpec.describe Notes::CreateService do ), # Set WIP status QuickAction.new( - action_text: "/wip", + action_text: "/draft", before_action: -> { issuable.reload.update!(title: "title") }, @@ -317,7 +317,7 @@ RSpec.describe Notes::CreateService do ), # Remove WIP status QuickAction.new( - action_text: "/wip", + action_text: "/draft", before_action: -> { issuable.reload.update!(title: "WIP: title") }, diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index f3ad69bae13..4af76bc65ab 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -1202,16 +1202,6 @@ RSpec.describe QuickActions::InterpretService do end it_behaves_like 'draft command' do - let(:content) { '/wip' } - let(:issuable) { merge_request } - end - - it_behaves_like 'undraft command' do - let(:content) { '/wip' } - let(:issuable) { merge_request } - end - - it_behaves_like 'draft command' do let(:content) { '/draft' } let(:issuable) { merge_request } end diff --git a/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb b/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb index ebba312e895..678bb908343 100644 --- a/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb +++ b/spec/support/shared_examples/graphql/mutations/resolves_subscription_shared_examples.rb @@ -2,44 +2,37 @@ require 'spec_helper' -RSpec.shared_examples 'a subscribeable graphql resource' do - let(:project) { resource.project } - let_it_be(:user) { create(:user) } - - subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } +RSpec.shared_examples 'a subscribeable not accessible graphql resource' do + let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } - specify { expect(described_class).to require_graphql_authorizations(permission_name) } + subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: true) } - describe '#resolve' do - let(:subscribe) { true } - let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] } - - subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: subscribe) } + it 'raises an error if the resource is not accessible to the user' do + expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) + end +end - it 'raises an error if the resource is not accessible to the user' do - expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) - end +RSpec.shared_examples 'a subscribeable graphql resource' do + let(:mutated_resource) { subject[resource.class.name.underscore.to_sym] } + let(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) } + let(:subscribe) { true } - context 'when the user can update the resource' do - before do - resource.project.add_developer(user) - end + subject { mutation.resolve(project_path: resource.project.full_path, iid: resource.iid, subscribed_state: subscribe) } - it 'subscribes to the resource' do - expect(mutated_resource).to eq(resource) - expect(mutated_resource.subscribed?(user, project)).to eq(true) - expect(subject[:errors]).to be_empty - end + it 'subscribes to the resource' do + expect(mutated_resource).to eq(resource) + expect(mutated_resource.subscribed?(user, project)).to eq(true) + expect(subject[:errors]).to be_empty + end - context 'when passing subscribe as false' do - let(:subscribe) { false } + context 'when passing subscribe as false' do + let(:subscribe) { false } - it 'unsubscribes from the discussion' do - resource.subscribe(user, project) + it 'unsubscribes from the discussion' do + resource.subscribe(user, project) - expect(mutated_resource.subscribed?(user, project)).to eq(false) - end - end + expect(mutated_resource.subscribed?(user, project)).to eq(false) + expect(subject[:errors]).to be_empty end end end diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb index ab868eb78b8..5cf3e6d849c 100644 --- a/spec/views/projects/settings/operations/show.html.haml_spec.rb +++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb @@ -36,7 +36,7 @@ RSpec.describe 'projects/settings/operations/show' do it 'renders the Operations Settings page' do render - expect(rendered).to have_content _('Alert integrations') + expect(rendered).to have_content _('Alerts') expect(rendered).to have_content _('Display alerts from all configured monitoring tools.') end end @@ -77,41 +77,11 @@ RSpec.describe 'projects/settings/operations/show' do end describe 'Operations > Tracing' do - context 'with project.tracing_external_url' do - it 'links to project.tracing_external_url' do - render - - expect(rendered).to have_link('Tracing', href: tracing_setting.external_url) - end - - context 'with malicious external_url' do - let(:malicious_tracing_url) { "https://replaceme.com/'><script>alert(document.cookie)</script>" } - let(:cleaned_url) { "https://replaceme.com/'>" } - - before do - tracing_setting.update_column(:external_url, malicious_tracing_url) - end - - it 'sanitizes external_url' do - render - - expect(tracing_setting.external_url).to eq(malicious_tracing_url) - expect(rendered).to have_link('Tracing', href: cleaned_url) - end - end - end - - context 'without project.tracing_external_url' do - let(:tracing_setting) { build(:project_tracing_setting, project: project) } - - before do - tracing_setting.external_url = nil - end - - it 'links to Tracing page' do + context 'Settings page ' do + it 'renders the Tracing Settings page' do render - expect(rendered).to have_link('Tracing', href: project_tracing_path(project)) + expect(rendered).to have_content _('Embed an image of your existing Jaeger server in GitLab.') end end end |