diff options
43 files changed, 585 insertions, 85 deletions
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 0ceea21fda3..cebe501139c 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -394,6 +394,7 @@ export default { > <dashboards-dropdown id="monitor-dashboards-dropdown" + data-qa-selector="dashboards_filter_dropdown" class="mb-0 d-flex" toggle-class="dropdown-menu-toggle" :default-branch="defaultBranch" @@ -458,6 +459,7 @@ export default { label-size="sm" label-for="monitor-time-window-dropdown" class="col-sm-auto col-md-auto col-lg-auto" + data-qa-selector="show_last_dropdown" > <date-time-picker ref="dateTimePicker" @@ -533,6 +535,7 @@ export default { v-if="selectedDashboard.can_edit" class="mt-1 js-edit-link" :href="selectedDashboard.project_blob_path" + data-qa-selector="edit_dashboard_button" >{{ __('Edit dashboard') }}</gl-button > diff --git a/app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue b/app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue index e678957c1e5..58eb8a9df8e 100644 --- a/app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue +++ b/app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue @@ -92,8 +92,7 @@ export default { <p class="text-muted"> {{ s__(`Metrics|You can save a copy of this dashboard to your repository - so it can be customized. Select a file name and branch to - save it.`) + so it can be customized. Select a file name and branch to save it.`) }} </p> <gl-form-group @@ -104,7 +103,13 @@ export default { label-size="sm" label-for="fileName" > - <gl-form-input id="fileName" ref="fileName" v-model="form.fileName" :required="true" /> + <gl-form-input + id="fileName" + ref="fileName" + v-model="form.fileName" + data-qa-selector="duplicate_dashboard_filename_field" + :required="true" + /> </gl-form-group> <gl-form-group :label="__('Branch')" label-size="sm" label-for="branch"> <gl-form-radio-group diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/services/edit/index.js index 2d77f2686f7..56016cb980c 100644 --- a/app/assets/javascripts/pages/projects/services/edit/index.js +++ b/app/assets/javascripts/pages/projects/services/edit/index.js @@ -1,5 +1,6 @@ import IntegrationSettingsForm from '~/integrations/integration_settings_form'; import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics'; +import PrometheusAlerts from '~/prometheus_alerts'; import initAlertsSettings from '~/alerts_service_settings'; document.addEventListener('DOMContentLoaded', () => { @@ -12,5 +13,6 @@ document.addEventListener('DOMContentLoaded', () => { prometheusMetrics.loadActiveMetrics(); } + PrometheusAlerts(); initAlertsSettings(document.querySelector('.js-alerts-service-settings')); }); diff --git a/app/assets/javascripts/prometheus_alerts/components/reset_key.vue b/app/assets/javascripts/prometheus_alerts/components/reset_key.vue new file mode 100644 index 00000000000..3a03cd409a6 --- /dev/null +++ b/app/assets/javascripts/prometheus_alerts/components/reset_key.vue @@ -0,0 +1,124 @@ +<script> +import { GlButton, GlFormGroup, GlFormInput, GlModal, GlModalDirective } from '@gitlab/ui'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import axios from '~/lib/utils/axios_utils'; +import { __, sprintf } from '~/locale'; +import createFlash from '~/flash'; + +export default { + copyToClipboard: __('Copy'), + components: { + GlButton, + GlFormGroup, + GlFormInput, + GlModal, + ClipboardButton, + }, + directives: { + 'gl-modal': GlModalDirective, + }, + props: { + initialAuthorizationKey: { + type: String, + required: false, + default: '', + }, + changeKeyUrl: { + type: String, + required: true, + }, + notifyUrl: { + type: String, + required: true, + }, + learnMoreUrl: { + type: String, + required: true, + }, + }, + data() { + return { + authorizationKey: this.initialAuthorizationKey, + sectionDescription: sprintf( + __( + 'To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab.', + ), + { + linkStart: `<a href="${this.learnMoreUrl}" target="_blank" rel="noopener noreferrer">`, + linkEnd: '</a>', + }, + false, + ), + }; + }, + methods: { + resetKey() { + axios + .post(this.changeKeyUrl) + .then(res => { + this.authorizationKey = res.data.token; + }) + .catch(() => { + createFlash(__('Failed to reset key. Please try again.')); + }); + }, + }, +}; +</script> + +<template> + <div class="row py-4 border-top js-prometheus-alerts"> + <div class="col-lg-3"> + <h4 class="mt-0"> + {{ __('Alerts') }} + </h4> + <p> + {{ __('Receive alerts from manually configured Prometheus servers.') }} + </p> + </div> + <div class="col-lg-9"> + <p v-html="sectionDescription"></p> + <gl-form-group :label="__('URL')" label-for="notify-url" label-class="label-bold"> + <div class="input-group"> + <gl-form-input id="notify-url" :readonly="true" :value="notifyUrl" /> + <span class="input-group-append"> + <clipboard-button :text="notifyUrl" :title="$options.copyToClipboard" /> + </span> + </div> + </gl-form-group> + <gl-form-group + :label="__('Authorization key')" + label-for="authorization-key" + label-class="label-bold" + > + <div class="input-group"> + <gl-form-input id="authorization-key" :readonly="true" :value="authorizationKey" /> + <span class="input-group-append"> + <clipboard-button :text="authorizationKey" :title="$options.copyToClipboard" /> + </span> + </div> + </gl-form-group> + <template v-if="authorizationKey.length > 0"> + <gl-modal + modal-id="authKeyModal" + :title="__('Reset authorization key?')" + :ok-title="__('Reset authorization key')" + ok-variant="danger" + @ok="resetKey" + > + {{ + __( + 'Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key.', + ) + }} + </gl-modal> + <gl-button v-gl-modal.authKeyModal class="js-reset-auth-key">{{ + __('Reset key') + }}</gl-button> + </template> + <gl-button v-else class="js-reset-auth-key" @click="resetKey">{{ + __('Generate key') + }}</gl-button> + </div> + </div> +</template> diff --git a/app/assets/javascripts/prometheus_alerts/index.js b/app/assets/javascripts/prometheus_alerts/index.js new file mode 100644 index 00000000000..a42f19e5245 --- /dev/null +++ b/app/assets/javascripts/prometheus_alerts/index.js @@ -0,0 +1,27 @@ +import Vue from 'vue'; +import ResetKey from './components/reset_key.vue'; + +export default () => { + const el = document.querySelector('#js-settings-prometheus-alerts'); + + if (!el) { + return; + } + + const { authorizationKey, changeKeyUrl, notifyUrl, learnMoreUrl } = el.dataset; + + // eslint-disable-next-line no-new + new Vue({ + el, + render(createElement) { + return createElement(ResetKey, { + props: { + initialAuthorizationKey: authorizationKey, + changeKeyUrl, + notifyUrl, + learnMoreUrl, + }, + }); + }, + }); +}; diff --git a/app/models/user.rb b/app/models/user.rb index 768d4c17235..e18d642a155 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1583,13 +1583,6 @@ class User < ApplicationRecord end def read_only_attribute?(attribute) - if Feature.enabled?(:ldap_readonly_attributes, default_enabled: true) - enabled = Gitlab::Auth::Ldap::Config.enabled? - read_only = attribute.to_sym.in?(UserSyncedAttributesMetadata::SYNCABLE_ATTRIBUTES) - - return true if enabled && read_only - end - user_synced_attributes_metadata&.read_only?(attribute) end diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb index f0e9f2b7656..b69720eefd6 100644 --- a/app/services/users/update_service.rb +++ b/app/services/users/update_service.rb @@ -60,11 +60,7 @@ module Users end def discard_read_only_attributes - if Feature.enabled?(:ldap_readonly_attributes, default_enabled: true) - params.reject! { |key, _| @user.read_only_attribute?(key.to_sym) } - else - discard_synced_attributes - end + discard_synced_attributes end def discard_synced_attributes diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 588ee63c56f..b3acf3320d3 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -162,4 +162,6 @@ = render_if_exists "groups/ee/settings_nav" + = render_if_exists "groups/ee/administration_nav" + = render 'shared/sidebar_toggle_button' diff --git a/app/views/projects/services/prometheus/_external_alerts.html.haml b/app/views/projects/services/prometheus/_external_alerts.html.haml new file mode 100644 index 00000000000..24ff0cc88a3 --- /dev/null +++ b/app/views/projects/services/prometheus/_external_alerts.html.haml @@ -0,0 +1,8 @@ +- return unless can?(current_user, :read_prometheus_alerts, @project) +- return unless @service.manual_configuration? + +- notify_url = notify_project_prometheus_alerts_url(@project, format: :json) +- authorization_key = @project.alerting_setting.try(:token) +- learn_more_url = help_page_path('user/project/integrations/prometheus', anchor: 'external-prometheus-instances') + +#js-settings-prometheus-alerts{ data: { notify_url: notify_url, authorization_key: authorization_key, change_key_url: reset_alerting_token_project_settings_operations_path(@project), learn_more_url: learn_more_url } } diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index c719661d8e8..926671845c7 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -6,4 +6,4 @@ .row.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring = render 'projects/services/prometheus/metrics', project: @project -= render_if_exists 'projects/services/prometheus/external_alerts', project: @project += render 'projects/services/prometheus/external_alerts', project: @project diff --git a/changelogs/unreleased/210492-revert-ldap-readonly-sync.yml b/changelogs/unreleased/210492-revert-ldap-readonly-sync.yml new file mode 100644 index 00000000000..def24208afa --- /dev/null +++ b/changelogs/unreleased/210492-revert-ldap-readonly-sync.yml @@ -0,0 +1,5 @@ +--- +title: Revert LDAP readonly attributes feature +merge_request: 28541 +author: +type: removed diff --git a/changelogs/unreleased/26391-move-alerting-feature-to-core.yml b/changelogs/unreleased/26391-move-alerting-feature-to-core.yml new file mode 100644 index 00000000000..83173fe7ace --- /dev/null +++ b/changelogs/unreleased/26391-move-alerting-feature-to-core.yml @@ -0,0 +1,5 @@ +--- +title: Move Alerting feature to Core +merge_request: 28196 +author: +type: changed diff --git a/db/post_migrate/20200226124757_remove_health_status_from_epics.rb b/db/post_migrate/20200226124757_remove_health_status_from_epics.rb new file mode 100644 index 00000000000..e59edbacd2b --- /dev/null +++ b/db/post_migrate/20200226124757_remove_health_status_from_epics.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class RemoveHealthStatusFromEpics < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + with_lock_retries do + remove_column :epics, :health_status + end + end + + def down + with_lock_retries do + add_column :epics, :health_status, :integer, limit: 2 + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 178cf4c63da..d5bd08b41d0 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2271,7 +2271,6 @@ CREATE TABLE public.epics ( state_id smallint DEFAULT 1 NOT NULL, start_date_sourcing_epic_id integer, due_date_sourcing_epic_id integer, - health_status smallint, external_key character varying(255) ); @@ -12819,6 +12818,7 @@ COPY "schema_migrations" (version) FROM STDIN; 20200226100614 20200226100624 20200226100634 +20200226124757 20200226162156 20200226162239 20200226162634 diff --git a/doc/administration/troubleshooting/group_saml_scim.md b/doc/administration/troubleshooting/group_saml_scim.md index 7c2733d9d63..e2ce72d5a16 100644 --- a/doc/administration/troubleshooting/group_saml_scim.md +++ b/doc/administration/troubleshooting/group_saml_scim.md @@ -38,6 +38,24 @@ SCIM mapping: ![Azure AD SCIM](img/AzureAD-scim_attribute_mapping.png) +## Okta + +Basic SAML app configuration: + +![Okta basic SAML](img/Okta-SAMLsetup.png) + +User claims and attributes: + +![Okta Attributes](img/Okta-attributes.png) + +Advanced SAML app settings (defaults): + +![Okta Advanced Settings](img/Okta-advancedsettings.png) + +IdP Links and Certificate: + +![Okta Links and Certificate](img/Okta-linkscert.png) + ## OneLogin Application details: diff --git a/doc/administration/troubleshooting/img/Okta-SAMLsetup.png b/doc/administration/troubleshooting/img/Okta-SAMLsetup.png Binary files differnew file mode 100644 index 00000000000..8171febb5bc --- /dev/null +++ b/doc/administration/troubleshooting/img/Okta-SAMLsetup.png diff --git a/doc/administration/troubleshooting/img/Okta-advancedsettings.png b/doc/administration/troubleshooting/img/Okta-advancedsettings.png Binary files differnew file mode 100644 index 00000000000..43eb546f238 --- /dev/null +++ b/doc/administration/troubleshooting/img/Okta-advancedsettings.png diff --git a/doc/administration/troubleshooting/img/Okta-attributes.png b/doc/administration/troubleshooting/img/Okta-attributes.png Binary files differnew file mode 100644 index 00000000000..e4a7b33fe55 --- /dev/null +++ b/doc/administration/troubleshooting/img/Okta-attributes.png diff --git a/doc/administration/troubleshooting/img/Okta-linkscert.png b/doc/administration/troubleshooting/img/Okta-linkscert.png Binary files differnew file mode 100644 index 00000000000..33e6b3cc53e --- /dev/null +++ b/doc/administration/troubleshooting/img/Okta-linkscert.png diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md index 1c8ecec139d..9cf58353991 100644 --- a/doc/subscriptions/index.md +++ b/doc/subscriptions/index.md @@ -173,7 +173,7 @@ To see the status of your GitLab.com subscription, log into GitLab.com and go to 1. Go to **User Avatar > Settings**. 1. Click **Billing**. - For groups: - 1. From the group page (*not* from a project within the group), go to **Settings > Billing**. + 1. From the group page (*not* from a project within the group), go to **Administration > Billing**. The following table describes details of your subscription for groups: @@ -427,7 +427,7 @@ CI pipeline minutes are the execution time for your [pipelines](../ci/pipelines/ Quotas apply to: -- Groups, where the minutes are shared across all members of the group, its subgroups, and nested projects. To view the group's usage, navigate to the group, then **{settings}** **Settings > Usage Quotas**. +- Groups, where the minutes are shared across all members of the group, its subgroups, and nested projects. To view the group's usage, navigate to the group, then **{settings}** **Administration > Usage Quotas**. - Your personal account, where the minutes are available for your personal projects. To view and buy personal minutes, click your avatar, then **{settings}** **Settings > Pipeline quota**. Only pipeline minutes for GitLab shared runners are restricted. If you have a specific runner set up for your projects, there is no limit to your build time on GitLab.com. @@ -448,10 +448,10 @@ main quota. Additional minutes: To purchase additional minutes for your group on GitLab.com: -1. From your group, go to **{settings}** **Settings > Usage Quotas**. +1. From your group, go to **{settings}** **Administration > Usage Quotas**. 1. Locate the subscription card that's linked to your group on GitLab.com, click **Buy more CI minutes**, and complete the details about the transaction. 1. Once we have processed your payment, the extra CI minutes will be synced to your group. -1. To confirm the available CI minutes, go to your group, then **{settings}** **Settings > Usage Quotas**. +1. To confirm the available CI minutes, go to your group, then **{settings}** **Administration > Usage Quotas**. The **Additional minutes** displayed now includes the purchased additional CI minutes, plus any minutes rolled over from last month. To purchase additional minutes for your personal namespace: diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index c47cbfa9aa8..8d8c735338a 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -255,6 +255,8 @@ configured to act as a remote proxy and add the `Gitlab-DAST-Permission` header. ### API scan +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10928) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10. + Using an API specification as a scan's target is a useful way to seed URLs for scanning an API. Vulnerability rules in an API scan are different than those in a normal website scan. diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md index cf8d63e1512..1243cf7c2f5 100644 --- a/doc/user/group/saml_sso/index.md +++ b/doc/user/group/saml_sso/index.md @@ -24,7 +24,7 @@ Note the following: ## Configuring your Identity Provider -1. Navigate to the group and click **Settings > SAML SSO**. +1. Navigate to the group and click **Administration > SAML SSO**. 1. Configure your SAML server using the **Assertion consumer service URL** and **Identifier**. Alternatively GitLab provides [metadata XML configuration](#metadata-configuration). See [your identity provider's documentation](#providers) for more details. 1. Configure the SAML response to include a NameID that uniquely identifies each user. 1. Configure required assertions using the [table below](#assertions). @@ -116,7 +116,7 @@ This feature is similar to the [Credentials inventory for self-managed instances > [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34648) in GitLab 12.9. Groups with group-managed accounts can disallow forking of projects to destinations outside the group. -To do so, enable the "Prohibit outer forks" option in **Settings > SAML SSO**. +To do so, enable the "Prohibit outer forks" option in **Administration > SAML SSO**. When enabled, projects within the group can only be forked to other destinations within the group (including its subgroups). ##### Other restrictions for Group-managed accounts @@ -146,7 +146,7 @@ assertions to be able to create a user. GitLab provides metadata XML that can be used to configure your Identity Provider. -1. Navigate to the group and click **Settings > SAML SSO**. +1. Navigate to the group and click **Administration > SAML SSO**. 1. Copy the provided **GitLab metadata URL**. 1. Follow your Identity Provider's documentation and paste the metadata URL when it is requested. @@ -154,7 +154,7 @@ GitLab provides metadata XML that can be used to configure your Identity Provide Once you've set up your identity provider to work with GitLab, you'll need to configure GitLab to use it for authentication: -1. Navigate to the group's **Settings > SAML SSO**. +1. Navigate to the group's **Administration > SAML SSO**. 1. Find the SSO URL from your Identity Provider and enter it the **Identity provider single sign on URL** field. 1. Find and enter the fingerprint for the SAML token signing certificate in the **Certificate** field. 1. Click the **Enable SAML authentication for this group** toggle switch. @@ -234,6 +234,13 @@ Set other user attributes and claims according to the [assertions table](#assert Under Okta's **Single sign on URL** field, check the option **Use this for Recipient URL and Destination URL**. +Please note that Okta's generic SAML app does not have a **Login URL** field, where the **Identity provider single sign on URL** would normally go. The **Identity provider single sign on URL** may be required the first time a user is logging in if they are having any difficulties. + +We recommend: + +- **Application username** (NameID) set to **Custom** `user.getInternalProperty("id")`. +- **Name ID Format** set to **Persistent**. + Set attribute statements according to the [assertions table](#assertions). ### OneLogin setup notes @@ -281,14 +288,14 @@ If the information information you need isn't listed above you may wish to check To link SAML to your existing GitLab.com account: 1. Sign in to your GitLab.com account. -1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on the group's **Settings > SAML SSO** page. +1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on the group's **Administration > SAML SSO** page. 1. Visit the SSO URL and click **Authorize**. 1. Enter your credentials on the Identity Provider if prompted. 1. You will be redirected back to GitLab.com and should now have access to the group. In the future, you can use SAML to sign in to GitLab.com. ## Signing in to GitLab.com with SAML -1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on a group's **Settings > SAML SSO** page. If configured, it might also be possible to sign in to GitLab starting from your Identity Provider. +1. Locate the SSO URL for the group you are signing in to. A group Admin can find this on a group's **Administration > SAML SSO** page. If configured, it might also be possible to sign in to GitLab starting from your Identity Provider. 1. Visit the SSO URL and click the **Sign in with Single Sign-On** button. 1. Enter your credentials on the Identity Provider if prompted. 1. You will be signed in to GitLab.com and redirected to the group. diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md index c8fef453cf4..aea7f1e93e7 100644 --- a/doc/user/group/saml_sso/scim_setup.md +++ b/doc/user/group/saml_sso/scim_setup.md @@ -30,7 +30,7 @@ The following identity providers are supported: Once [Single sign-on](index.md) has been configured, we can: -1. Navigate to the group and click **Settings > SAML SSO**. +1. Navigate to the group and click **Administration > SAML SSO**. 1. Click on the **Generate a SCIM token** button. 1. Save the token and URL so they can be used in the next step. diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 5a070db9439..425687d21b8 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -671,7 +671,8 @@ To remove the alert, click back on the alert icon for the desired metric, and cl #### External Prometheus instances -> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/9258) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.8. +>- [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/9258) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.8. +>- [Moved](https://gitlab.com/gitlab-org/gitlab/issues/42640) to [GitLab Core](https://about.gitlab.com/pricing/) in 12.10. For manually configured Prometheus servers, a notify endpoint is provided to use with Prometheus webhooks. If you have manual configuration enabled, an **Alerts** section is added to **Settings > Integrations > Prometheus**. This contains the *URL* and *Authorization Key*. The **Reset Key** button will invalidate the key and generate a new one. diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb index 733cafd9d6c..c5d2d8be022 100644 --- a/lib/gitlab/usage_data.rb +++ b/lib/gitlab/usage_data.rb @@ -99,6 +99,7 @@ module Gitlab projects_with_repositories_enabled: count(ProjectFeature.where('repository_access_level > ?', ProjectFeature::DISABLED)), projects_with_error_tracking_enabled: count(::ErrorTracking::ProjectErrorTrackingSetting.where(enabled: true)), projects_with_alerts_service_enabled: count(AlertsService.active), + projects_with_prometheus_alerts: distinct_count(PrometheusAlert, :project_id), protected_branches: count(ProtectedBranch), releases: count(Release), remote_mirrors: count(RemoteMirror), diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 75d4542e2e3..3e581dba398 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1584,6 +1584,9 @@ msgstr "" msgid "AdminUsers|You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered." msgstr "" +msgid "Administration" +msgstr "" + msgid "Advanced" msgstr "" @@ -7364,9 +7367,6 @@ msgstr "" msgid "Email display name" msgstr "" -msgid "Email domain is not editable in subgroups. Value inherited from top-level parent group." -msgstr "" - msgid "Email not verified. Please verify your email in Salesforce." msgstr "" @@ -8675,6 +8675,9 @@ msgstr "" msgid "FeatureFlags|Active" msgstr "" +msgid "FeatureFlags|Add strategy" +msgstr "" + msgid "FeatureFlags|All users" msgstr "" @@ -8699,6 +8702,9 @@ msgstr "" msgid "FeatureFlags|Edit Feature Flag" msgstr "" +msgid "FeatureFlags|Enable features for specific users and specific environments by defining feature flag strategies. By default, features are available to all users in all environments." +msgstr "" + msgid "FeatureFlags|Environment Spec" msgstr "" @@ -8711,6 +8717,9 @@ msgstr "" msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcard rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}." msgstr "" +msgid "FeatureFlags|Feature Flag has no strategies" +msgstr "" + msgid "FeatureFlags|Feature Flags" msgstr "" @@ -8780,6 +8789,9 @@ msgstr "" msgid "FeatureFlags|Status" msgstr "" +msgid "FeatureFlags|Strategies" +msgstr "" + msgid "FeatureFlags|Target environments" msgstr "" @@ -10683,9 +10695,6 @@ msgstr "" msgid "IP Address" msgstr "" -msgid "IP address restriction is not editable in subgroups. Value inherited from top-level parent group." -msgstr "" - msgid "IP subnet restriction only allowed for top-level groups" msgstr "" @@ -13499,9 +13508,6 @@ msgstr "" msgid "No thanks, don't show this again" msgstr "" -msgid "No value set by top-level parent group." -msgstr "" - msgid "No vulnerabilities found for this group" msgstr "" diff --git a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml b/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml index d8ca7b591ed..3e83c8f0f77 100644 --- a/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml +++ b/qa/qa/fixtures/monitored_auto_devops/.gitlab-ci.yml @@ -22,7 +22,7 @@ variables: stages: - production -# This job continuously deploys to production on every push to `master`. +# This job continuously deploys to staging/production on every push to `master`. production: stage: production diff --git a/qa/qa/page/project/operations/metrics/show.rb b/qa/qa/page/project/operations/metrics/show.rb index c94c1f6590f..020a3a1d5f8 100644 --- a/qa/qa/page/project/operations/metrics/show.rb +++ b/qa/qa/page/project/operations/metrics/show.rb @@ -11,6 +11,14 @@ module QA view 'app/assets/javascripts/monitoring/components/dashboard.vue' do element :prometheus_graphs + element :dashboards_filter_dropdown + element :environments_dropdown + element :edit_dashboard_button + element :show_last_dropdown + end + + view 'app/assets/javascripts/monitoring/components/duplicate_dashboard_form.vue' do + element :duplicate_dashboard_filename_field end view 'app/assets/javascripts/monitoring/components/panel_type.vue' do @@ -35,6 +43,35 @@ module QA end end + def has_edit_dashboard_enabled? + within_element :prometheus_graphs do + has_element? :edit_dashboard_button + end + end + + def duplicate_dashboard(save_as = 'test_duplication.yml', commit_option = 'Commit to master branch') + click_element :dashboards_filter_dropdown + click_on 'Duplicate dashboard' + fill_element :duplicate_dashboard_filename_field, save_as + choose commit_option + within('.modal-content') { click_button(class: 'btn-success') } + end + + def filter_environment(environment = 'production') + click_element :environments_dropdown + + within_element :environments_dropdown do + click_link_with_text environment + end + end + + def show_last(range = '8 hours') + click_element :show_last_dropdown + within_element :show_last_dropdown do + click_on range + end + end + private def wait_for_data diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index f9332e0a853..327eedeaf91 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -25,7 +25,7 @@ module QA::Page end def wait_for_latest_pipeline_status - wait_until(reload: false, max_duration: 300) do + wait_until(reload: false, max_duration: 360) do within_element_by_index(:pipeline_commit_status, 0) { yield } end end diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb index ec00091b3ce..e3d5b755317 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/protocol_v2_push_ssh_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Git protocol v2 is temporarily disabled - context 'Create', quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/issues/27828', type: :bug } do + context 'Create' do describe 'Push over SSH using Git protocol version 2', :requires_git_protocol_v2 do # Note: If you run this test against GDK make sure you've enabled sshd and # enabled setting the Git protocol by adding `AcceptEnv GIT_PROTOCOL` to diff --git a/qa/qa/specs/features/browser_ui/8_monitor/apm/dashboards_spec.rb b/qa/qa/specs/features/browser_ui/8_monitor/apm/dashboards_spec.rb new file mode 100644 index 00000000000..f7463c69db1 --- /dev/null +++ b/qa/qa/specs/features/browser_ui/8_monitor/apm/dashboards_spec.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +module QA + context 'Monitor' do + describe 'Dashboards', :orchestrated, :kubernetes, quarantine: { type: :new } do + before(:all) do + @cluster = Service::KubernetesCluster.new.create! + Flow::Login.sign_in + create_project_to_monitor + wait_for_deployment + end + + before do + Flow::Login.sign_in_unless_signed_in + @project.visit! + end + + after(:all) do + @cluster&.remove! + end + + it 'duplicates to create dashboard to custom' do + Page::Project::Menu.perform(&:go_to_operations_metrics) + + Page::Project::Operations::Metrics::Show.perform do |dashboard| + dashboard.duplicate_dashboard + + expect(dashboard).to have_metrics + expect(dashboard).to have_edit_dashboard_enabled + end + end + + it 'verifies data on filtered deployed environment' do + Page::Project::Menu.perform(&:go_to_operations_metrics) + + Page::Project::Operations::Metrics::Show.perform do |dashboard| + dashboard.filter_environment + + expect(dashboard).to have_metrics + end + end + + it 'filters using the quick range' do + Page::Project::Menu.perform(&:go_to_operations_metrics) + + Page::Project::Operations::Metrics::Show.perform do |dashboard| + dashboard.show_last('30 minutes') + expect(dashboard).to have_metrics + + dashboard.show_last('3 hours') + expect(dashboard).to have_metrics + + dashboard.show_last('1 day') + expect(dashboard).to have_metrics + end + end + + private + + def wait_for_deployment + Page::Project::Menu.perform(&:click_ci_cd_pipelines) + Page::Project::Pipeline::Index.perform(&:wait_for_latest_pipeline_success_or_retry) + Page::Project::Menu.perform(&:go_to_operations_metrics) + end + + def create_project_to_monitor + @project = Resource::Project.fabricate_via_api! do |project| + project.name = 'cluster-with-prometheus' + project.description = 'Cluster with Prometheus' + end + + @cluster_props = Resource::KubernetesCluster.fabricate_via_browser_ui! do |cluster_settings| + cluster_settings.project = @project + cluster_settings.cluster = @cluster + cluster_settings.install_helm_tiller = true + cluster_settings.install_ingress = true + cluster_settings.install_prometheus = true + end + + Resource::CiVariable.fabricate_via_api! do |ci_variable| + ci_variable.project = @project + ci_variable.key = 'AUTO_DEVOPS_DOMAIN' + ci_variable.value = @cluster_props.ingress_ip + ci_variable.masked = false + end + + Resource::Repository::ProjectPush.fabricate! do |push| + push.project = @project + push.directory = Pathname + .new(__dir__) + .join('../../../../../fixtures/monitored_auto_devops') + push.commit_message = 'Create AutoDevOps compatible Project for Monitoring' + end + end + end + end +end diff --git a/remove-health-status-epic.yml b/remove-health-status-epic.yml new file mode 100644 index 00000000000..c87d84fc5b3 --- /dev/null +++ b/remove-health-status-epic.yml @@ -0,0 +1,5 @@ +--- +title: Remove health_status column from epics +merge_request: 26302 +author: +type: other diff --git a/spec/factories/usage_data.rb b/spec/factories/usage_data.rb index 9560e076ae4..da69a0fb844 100644 --- a/spec/factories/usage_data.rb +++ b/spec/factories/usage_data.rb @@ -26,6 +26,9 @@ FactoryBot.define do create_list(:issue, 2, project: projects[0], author: User.alert_bot) create_list(:issue, 2, project: projects[1], author: User.alert_bot) create_list(:issue, 4, project: projects[0]) + create(:prometheus_alert, project: projects[0]) + create(:prometheus_alert, project: projects[0]) + create(:prometheus_alert, project: projects[1]) create(:zoom_meeting, project: projects[0], issue: projects[0].issues[0], issue_status: :added) create_list(:zoom_meeting, 2, project: projects[0], issue: projects[0].issues[1], issue_status: :removed) create(:zoom_meeting, project: projects[0], issue: projects[0].issues[2], issue_status: :added) diff --git a/spec/features/projects/services/prometheus_external_alerts_spec.rb b/spec/features/projects/services/prometheus_external_alerts_spec.rb new file mode 100644 index 00000000000..e33b2d9a75e --- /dev/null +++ b/spec/features/projects/services/prometheus_external_alerts_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Prometheus external alerts', :js do + let(:project) { create(:project) } + let(:user) { create(:user) } + + let(:alerts_section_selector) { '.js-prometheus-alerts' } + let(:alerts_section) { page.find(alerts_section_selector) } + + before do + sign_in(user) + project.add_maintainer(user) + + visit_edit_service + end + + context 'with manual configuration' do + before do + create(:prometheus_service, project: project, api_url: 'http://prometheus.example.com', manual_configuration: '1', active: true) + end + + it 'shows the Alerts section' do + visit_edit_service + + expect(alerts_section).to have_content('Alerts') + expect(alerts_section).to have_content('Receive alerts from manually configured Prometheus servers.') + expect(alerts_section).to have_content('URL') + expect(alerts_section).to have_content('Authorization key') + end + end + + context 'with no configuration' do + it 'does not show the Alerts section' do + wait_for_requests + + expect(page).not_to have_css(alerts_section_selector) + end + end + + private + + def visit_edit_service + visit(project_settings_integrations_path(project)) + click_link('Prometheus') + end +end diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap index b8bda533072..c61ac0fb175 100644 --- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap +++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap @@ -19,6 +19,7 @@ exports[`Dashboard template matches the default snapshot 1`] = ` > <dashboards-dropdown-stub class="mb-0 d-flex" + data-qa-selector="dashboards_filter_dropdown" defaultbranch="master" id="monitor-dashboards-dropdown" selecteddashboard="[object Object]" @@ -74,6 +75,7 @@ exports[`Dashboard template matches the default snapshot 1`] = ` <gl-form-group-stub class="col-sm-auto col-md-auto col-lg-auto" + data-qa-selector="show_last_dropdown" label="Show last" label-for="monitor-time-window-dropdown" label-size="sm" diff --git a/spec/frontend/prometheus_alerts/components/reset_key_spec.js b/spec/frontend/prometheus_alerts/components/reset_key_spec.js new file mode 100644 index 00000000000..df52baafa29 --- /dev/null +++ b/spec/frontend/prometheus_alerts/components/reset_key_spec.js @@ -0,0 +1,105 @@ +import { shallowMount } from '@vue/test-utils'; +import MockAdapter from 'axios-mock-adapter'; +import ResetKey from '~/prometheus_alerts/components/reset_key.vue'; +import { GlModal } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import axios from '~/lib/utils/axios_utils'; + +describe('ResetKey', () => { + let mock; + let vm; + + const propsData = { + initialAuthorizationKey: 'abcd1234', + changeKeyUrl: '/updateKeyUrl', + notifyUrl: '/root/autodevops-deploy/prometheus/alerts/notify.json', + learnMoreUrl: '/learnMore', + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + setFixtures('<div class="flash-container"></div><div id="reset-key"></div>'); + }); + + afterEach(() => { + mock.restore(); + vm.destroy(); + }); + + describe('authorization key exists', () => { + beforeEach(() => { + propsData.initialAuthorizationKey = 'abcd1234'; + vm = shallowMount(ResetKey, { + propsData, + }); + }); + + it('shows fields and buttons', () => { + expect(vm.find('#notify-url').attributes('value')).toEqual(propsData.notifyUrl); + expect(vm.find('#authorization-key').attributes('value')).toEqual( + propsData.initialAuthorizationKey, + ); + + expect(vm.findAll(ClipboardButton).length).toBe(2); + expect(vm.find('.js-reset-auth-key').text()).toEqual('Reset key'); + }); + + it('reset updates key', () => { + mock.onPost(propsData.changeKeyUrl).replyOnce(200, { token: 'newToken' }); + + vm.find(GlModal).vm.$emit('ok'); + + return vm.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(vm.vm.authorizationKey).toEqual('newToken'); + expect(vm.find('#authorization-key').attributes('value')).toEqual('newToken'); + }); + }); + + it('reset key failure shows error', () => { + mock.onPost(propsData.changeKeyUrl).replyOnce(500); + + vm.find(GlModal).vm.$emit('ok'); + + return vm.vm + .$nextTick() + .then(waitForPromises) + .then(() => { + expect(vm.find('#authorization-key').attributes('value')).toEqual( + propsData.initialAuthorizationKey, + ); + + expect(document.querySelector('.flash-container').innerText.trim()).toEqual( + 'Failed to reset key. Please try again.', + ); + }); + }); + }); + + describe('authorization key has not been set', () => { + beforeEach(() => { + propsData.initialAuthorizationKey = ''; + vm = shallowMount(ResetKey, { + propsData, + }); + }); + + it('shows Generate Key button', () => { + expect(vm.find('.js-reset-auth-key').text()).toEqual('Generate key'); + expect(vm.find('#authorization-key').attributes('value')).toEqual(''); + }); + + it('Generate key button triggers key change', () => { + mock.onPost(propsData.changeKeyUrl).replyOnce(200, { token: 'newToken' }); + + vm.find('.js-reset-auth-key').vm.$emit('click'); + + return waitForPromises().then(() => { + expect(vm.find('#authorization-key').attributes('value')).toEqual('newToken'); + }); + }); + }); +}); diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb index eca69d755cc..c29a4dd9e00 100644 --- a/spec/lib/gitlab/usage_data_spec.rb +++ b/spec/lib/gitlab/usage_data_spec.rb @@ -51,6 +51,7 @@ describe Gitlab::UsageData, :aggregate_failures do expect(count_data[:projects_with_repositories_enabled]).to eq(3) expect(count_data[:projects_with_error_tracking_enabled]).to eq(1) expect(count_data[:projects_with_alerts_service_enabled]).to eq(1) + expect(count_data[:projects_with_prometheus_alerts]).to eq(2) expect(count_data[:issues_created_from_gitlab_error_tracking_ui]).to eq(1) expect(count_data[:issues_with_associated_zoom_link]).to eq(2) expect(count_data[:issues_using_zoom_quick_actions]).to eq(3) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b21841ad034..61c871ead92 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4263,30 +4263,6 @@ describe User, :do_not_mock_admin_mode do end describe '#read_only_attribute?' do - context 'when LDAP server is enabled' do - before do - allow(Gitlab::Auth::Ldap::Config).to receive(:enabled?).and_return(true) - end - - %i[name email location].each do |attribute| - it "is true for #{attribute}" do - expect(subject.read_only_attribute?(attribute)).to be_truthy - end - end - - context 'and ldap_readonly_attributes feature is disabled' do - before do - stub_feature_flags(ldap_readonly_attributes: false) - end - - %i[name email location].each do |attribute| - it "is false" do - expect(subject.read_only_attribute?(attribute)).to be_falsey - end - end - end - end - context 'when synced attributes metadata is present' do it 'delegates to synced_attributes_metadata' do subject.build_user_synced_attributes_metadata @@ -4297,7 +4273,7 @@ describe User, :do_not_mock_admin_mode do end end - context 'when synced attributes metadata is present' do + context 'when synced attributes metadata is not present' do it 'is false for any attribute' do expect(subject.read_only_attribute?(:email)).to be_falsey end diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index a8f21f64a72..f43fa5b4185 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -3,11 +3,15 @@ require 'spec_helper' describe API::Pipelines do - let(:user) { create(:user) } - let(:non_member) { create(:user) } - let(:project) { create(:project, :repository, creator: user) } + let_it_be(:user) { create(:user) } + let_it_be(:non_member) { create(:user) } - let!(:pipeline) do + # We need to reload as the shared example 'pipelines visibility table' is changing project + let_it_be(:project, reload: true) do + create(:project, :repository, creator: user) + end + + let_it_be(:pipeline) do create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch, user: user) end @@ -26,7 +30,7 @@ describe API::Pipelines do expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.first['sha']).to match /\A\h{40}\z/ + expect(json_response.first['sha']).to match(/\A\h{40}\z/) expect(json_response.first['id']).to eq pipeline.id expect(json_response.first['web_url']).to be_present expect(json_response.first.keys).to contain_exactly(*%w[id sha ref status web_url created_at updated_at]) @@ -438,7 +442,7 @@ describe API::Pipelines do get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user) expect(response).to have_gitlab_http_status(:ok) - expect(json_response['sha']).to match /\A\h{40}\z/ + expect(json_response['sha']).to match(/\A\h{40}\z/) end it 'returns 404 when it does not exist' do diff --git a/spec/serializers/prometheus_alert_entity_spec.rb b/spec/serializers/prometheus_alert_entity_spec.rb index 5121c62a0e0..2b6d8b62c4d 100644 --- a/spec/serializers/prometheus_alert_entity_spec.rb +++ b/spec/serializers/prometheus_alert_entity_spec.rb @@ -13,7 +13,6 @@ describe PrometheusAlertEntity do context 'when user can read prometheus alerts' do before do prometheus_alert.project.add_maintainer(user) - stub_licensed_features(prometheus_alerts: true) end it 'exposes prometheus_alert attributes' do diff --git a/spec/services/users/update_service_spec.rb b/spec/services/users/update_service_spec.rb index bd54ca97431..8e13e7d9c0c 100644 --- a/spec/services/users/update_service_spec.rb +++ b/spec/services/users/update_service_spec.rb @@ -55,15 +55,6 @@ describe Users::UpdateService do expect(result[:message]).to eq("Emoji is not included in the list") end - it 'ignores read-only attributes' do - allow(user).to receive(:read_only_attribute?).with(:name).and_return(true) - - expect do - update_user(user, name: 'changed' + user.name) - user.reload - end.not_to change { user.name } - end - it 'updates user detail with provided attributes' do result = update_user(user, job_title: 'Backend Engineer') diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb index 0fa1c40bd67..4d63cf12575 100644 --- a/spec/support/helpers/usage_data_helpers.rb +++ b/spec/support/helpers/usage_data_helpers.rb @@ -92,6 +92,7 @@ module UsageDataHelpers projects_with_repositories_enabled projects_with_error_tracking_enabled projects_with_alerts_service_enabled + projects_with_prometheus_alerts pages_domains protected_branches releases diff --git a/spec/support/shared_contexts/navbar_structure_context.rb b/spec/support/shared_contexts/navbar_structure_context.rb index c7df54f9501..2a98855a83f 100644 --- a/spec/support/shared_contexts/navbar_structure_context.rb +++ b/spec/support/shared_contexts/navbar_structure_context.rb @@ -121,8 +121,16 @@ RSpec.shared_context 'group navbar structure' do _('Projects'), _('CI / CD'), _('Webhooks'), - _('Audit Events'), - _('Usage Quotas') + _('Audit Events') + ] + } + end + + let(:administration_nav_item) do + { + nav_item: _('Administration'), + nav_sub_items: [ + s_('UsageQuota|Usage Quotas') ] } end |