+
diff --git a/app/assets/javascripts/integrations/edit/event_hub.js b/app/assets/javascripts/integrations/edit/event_hub.js
new file mode 100644
index 0000000000000000000000000000000000000000..0948c2e53524a736a55c060600868ce89ee7687a
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/event_hub.js
@@ -0,0 +1,3 @@
+import Vue from 'vue';
+
+export default new Vue();
diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..a2ba581d4293221ae8f43a4933a11d79e4ceba55
--- /dev/null
+++ b/app/assets/javascripts/integrations/edit/index.js
@@ -0,0 +1,30 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import ActiveToggle from './components/active_toggle.vue';
+
+export default el => {
+ if (!el) {
+ return null;
+ }
+
+ const { showActive: showActiveStr, activated: activatedStr, disabled: disabledStr } = el.dataset;
+ const showActive = parseBoolean(showActiveStr);
+ const activated = parseBoolean(activatedStr);
+ const disabled = parseBoolean(disabledStr);
+
+ if (!showActive) {
+ return null;
+ }
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(ActiveToggle, {
+ props: {
+ initialActivated: activated,
+ disabled,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index 1c9b94ade8ac902183db67f08aecfcb2579ac9a3..3067f4090b1854a47ec9f343cf08292a4394a3af 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -2,28 +2,33 @@ import $ from 'jquery';
import axios from '../lib/utils/axios_utils';
import flash from '../flash';
import { __ } from '~/locale';
+import initForm from './edit';
+import eventHub from './edit/event_hub';
export default class IntegrationSettingsForm {
constructor(formSelector) {
this.$form = $(formSelector);
+ this.formActive = false;
// Form Metadata
this.canTestService = this.$form.data('canTest');
this.testEndPoint = this.$form.data('testUrl');
// Form Child Elements
- this.$serviceToggle = this.$form.find('#service_active');
this.$submitBtn = this.$form.find('button[type="submit"]');
this.$submitBtnLoader = this.$submitBtn.find('.js-btn-spinner');
this.$submitBtnLabel = this.$submitBtn.find('.js-btn-label');
}
init() {
- // Initialize View
- this.toggleServiceState(this.$serviceToggle.is(':checked'));
+ // Init Vue component
+ initForm(document.querySelector('.js-vue-integration-settings'));
+ eventHub.$on('toggle', active => {
+ this.formActive = active;
+ this.handleServiceToggle();
+ });
// Bind Event Listeners
- this.$serviceToggle.on('change', e => this.handleServiceToggle(e));
this.$submitBtn.on('click', e => this.handleSettingsSave(e));
}
@@ -31,7 +36,7 @@ export default class IntegrationSettingsForm {
// Check if Service is marked active, as if not marked active,
// We can skip testing it and directly go ahead to allow form to
// be submitted
- if (!this.$serviceToggle.is(':checked')) {
+ if (!this.formActive) {
return;
}
@@ -47,16 +52,16 @@ export default class IntegrationSettingsForm {
}
}
- handleServiceToggle(e) {
- this.toggleServiceState($(e.currentTarget).is(':checked'));
+ handleServiceToggle() {
+ this.toggleServiceState();
}
/**
* Change Form's validation enforcement based on service status (active/inactive)
*/
- toggleServiceState(serviceActive) {
- this.toggleSubmitBtnLabel(serviceActive);
- if (serviceActive) {
+ toggleServiceState() {
+ this.toggleSubmitBtnLabel();
+ if (this.formActive) {
this.$form.removeAttr('novalidate');
} else if (!this.$form.attr('novalidate')) {
this.$form.attr('novalidate', 'novalidate');
@@ -66,10 +71,10 @@ export default class IntegrationSettingsForm {
/**
* Toggle Submit button label based on Integration status and ability to test service
*/
- toggleSubmitBtnLabel(serviceActive) {
+ toggleSubmitBtnLabel() {
let btnLabel = __('Save changes');
- if (serviceActive && this.canTestService) {
+ if (this.formActive && this.canTestService) {
btnLabel = __('Test settings and save changes');
}
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 9cb592ceedbe1f310f5cd312fdec323f879a5a8c..f82b3554cac6fe269e9ec03e0f0068f9440df56b 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -39,13 +39,18 @@ export default {
required: false,
default: true,
},
+ showSpinner: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
},
computed: {
toggleChevronClass() {
return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down';
},
noteTimestampLink() {
- return `#note_${this.noteId}`;
+ return this.noteId ? `#note_${this.noteId}` : undefined;
},
hasAuthor() {
return this.author && Object.keys(this.author).length;
@@ -60,7 +65,9 @@ export default {
this.$emit('toggleHandler');
},
updateTargetNoteHash() {
- this.setTargetNoteHash(this.noteTimestampLink);
+ if (this.$store) {
+ this.setTargetNoteHash(this.noteTimestampLink);
+ }
},
},
};
@@ -101,16 +108,20 @@ export default {
{{ actionText }}
+ Introduced in GitLab 12.3.
-
-To migrate all uploads created by legacy uploaders, run:
-
-**Omnibus Installation**
-
-```shell
-gitlab-rake gitlab:uploads:legacy:migrate
-```
-
-**Source Installation**
-
-```shell
-bundle exec rake gitlab:uploads:legacy:migrate
-```
-
## Migrate from object storage to local storage
If you need to disable Object Storage for any reason, first you need to migrate
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 523f0363ceed8ef39cbc8f14c60525db7050c11f..21d768a1605677f57a89b280a2d1585fcbf77c36 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -162,6 +162,10 @@ GET /runners/:id
curl --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/runners/6"
```
+CAUTION: **Deprecation**
+The `token` attribute in the response is deprecated [since GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320).
+It will be removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322).
+
Example response:
```json
@@ -221,6 +225,10 @@ PUT /runners/:id
curl --request PUT --header "PRIVATE-TOKEN: " "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2"
```
+CAUTION: **Deprecation**
+The `token` attribute in the response is deprecated [since GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/issues/214320).
+It will be removed in [GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/issues/214322).
+
Example response:
```json
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 2b4170d21af97d1352848bc11dd6da25e35cde7f..e78a894d98734022e19b0f5fcd70387e5fd946f2 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -112,6 +112,32 @@ To access the Credentials inventory of a group, navigate to **{shield}** **Secur
This feature is similar to the [Credentials inventory for self-managed instances](../../admin_area/credentials_inventory.md).
+##### Limiting lifetime of personal access tokens of users in Group-managed accounts **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118893) in GitLab 12.10.
+
+Users in a group managed account can optionally specify an expiration date for
+[personal access tokens](../../profile/personal_access_tokens.md).
+This expiration date is not a requirement, and can be set to any arbitrary date.
+
+Since personal access tokens are the only token needed for programmatic access to GitLab, organizations with security requirements may want to enforce more protection to require regular rotation of these tokens.
+
+###### Setting a limit
+
+Only a GitLab administrator or an owner of a Group-managed account can set a limit. Leaving it empty means that the [instance level restrictions](../../admin_area/settings/account_and_limit_settings.md#limiting-lifetime-of-personal-access-tokens-ultimate-only) on the lifetime of personal access tokens will apply.
+
+To set a limit on how long personal access tokens are valid for users in a group managed account:
+
+1. Navigate to the **{settings}** **Settings > General** page in your group's sidebar.
+1. Expand the **Permissions, LFS, 2FA** section.
+1. Fill in the **Maximum allowable lifetime for personal access tokens (days)** field.
+1. Click **Save changes**.
+
+Once a lifetime for personal access tokens is set, GitLab will:
+
+- Apply the lifetime for new personal access tokens, and require users managed by the group to set an expiration date that is no later than the allowed lifetime.
+- After three hours, revoke old tokens with no expiration date or with a lifetime longer than the allowed lifetime. Three hours is given to allow administrators/group owner to change the allowed lifetime, or remove it, before revocation takes place.
+
##### Outer forks restriction for Group-managed accounts
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/34648) in GitLab 12.9.
diff --git a/doc/user/project/integrations/bamboo.md b/doc/user/project/integrations/bamboo.md
index 726b5f657ead42263aa6327d176425fb681aefed..8c7e6edbf389c41ab4fa1c39bbbf767dc834d58b 100644
--- a/doc/user/project/integrations/bamboo.md
+++ b/doc/user/project/integrations/bamboo.md
@@ -39,7 +39,7 @@ service in GitLab.
1. Navigate to the project you want to configure to trigger builds.
1. Navigate to the [Integrations page](overview.md#accessing-integrations)
1. Click 'Atlassian Bamboo CI'
-1. Select the 'Active' checkbox.
+1. Ensure that the **Active** toggle is enabled.
1. Enter the base URL of your Bamboo server. `https://bamboo.example.com`
1. Enter the build key from your Bamboo build plan. Build keys are typically made
up from the Project Key and Plan Key that are set on project/plan creation and
diff --git a/doc/user/project/integrations/discord_notifications.md b/doc/user/project/integrations/discord_notifications.md
index 9252b7dd0f9a0a1bf861f8e15ce89917fab0ce1d..aa45cc38cb595467c4c0a462bfb33705185bef7e 100644
--- a/doc/user/project/integrations/discord_notifications.md
+++ b/doc/user/project/integrations/discord_notifications.md
@@ -21,7 +21,7 @@ With the webhook URL created in the Discord channel, you can set up the Discord
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings. That is, **Project > Settings > Integrations**.
1. Select the **Discord Notifications** integration to configure it.
-1. Check the **Active** checkbox to turn on the service.
+1. Ensure that the **Active** toggle is enabled.
1. Check the checkboxes corresponding to the GitLab events for which you want to send notifications to Discord.
1. Paste the webhook URL that you copied from the create Discord webhook step.
1. Configure the remaining options and click the **Save changes** button.
diff --git a/doc/user/project/integrations/generic_alerts.md b/doc/user/project/integrations/generic_alerts.md
index f5d0f5eb21bd2623ef00d868bab6eae913cc4566..1a000fd1c44ff2a7a28b9cce5d18e6eee0f018c3 100644
--- a/doc/user/project/integrations/generic_alerts.md
+++ b/doc/user/project/integrations/generic_alerts.md
@@ -18,7 +18,7 @@ To set up the generic alerts integration:
1. Navigate to **Settings > Integrations** in a project.
1. Click on **Alerts endpoint**.
-1. Toggle the **Active** alert setting. The `URL` and `Authorization Key` for the webhook configuration can be found there.
+1. Toggle the **Active** alert setting. The `URL` and `Authorization Key` for the webhook configuration can be found there.
## Customizing the payload
diff --git a/doc/user/project/integrations/github.md b/doc/user/project/integrations/github.md
index 2e3801f3a32deff061c431fe7d529ec7546312d9..42d8eadd35e53e4ffab806e762255f776ff010a4 100644
--- a/doc/user/project/integrations/github.md
+++ b/doc/user/project/integrations/github.md
@@ -27,7 +27,7 @@ with `repo:status` access granted:
1. Navigate to the project you want to configure.
1. Navigate to the [Integrations page](overview.md#accessing-integrations)
1. Click "GitHub".
-1. Select the "Active" checkbox.
+1. Ensure that the **Active** toggle is enabled.
1. Paste the token you've generated on GitHub
1. Enter the path to your project on GitHub, such as `https://github.com/username/repository`
1. Optionally uncheck **Static status check names** checkbox to disable static status check names.
diff --git a/doc/user/project/integrations/hangouts_chat.md b/doc/user/project/integrations/hangouts_chat.md
index bd32ef91e3cde660810369796054822fb81c0bb6..f33833a9c9347d00ca395965dec25783b86d3c25 100644
--- a/doc/user/project/integrations/hangouts_chat.md
+++ b/doc/user/project/integrations/hangouts_chat.md
@@ -19,7 +19,7 @@ When you have the **Webhook URL** for your Hangouts Chat room webhook, you can s
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**.
1. Select the **Hangouts Chat** integration to configure it.
-1. Check the **Active** checkbox to turn on the service.
+1. Ensure that the **Active** toggle is enabled.
1. Check the checkboxes corresponding to the GitLab events you want to receive.
1. Paste the **Webhook URL** that you copied from the Hangouts Chat configuration step.
1. Configure the remaining options and click `Save changes`.
diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md
index 1e04e28143a962d04c1c85a354544613bbb49789..2ed7f13db9ba051ab664326859cf4dd9918579f5 100644
--- a/doc/user/project/integrations/hipchat.md
+++ b/doc/user/project/integrations/hipchat.md
@@ -37,7 +37,7 @@ service in GitLab.
1. Navigate to the project you want to configure for notifications.
1. Navigate to the [Integrations page](overview.md#accessing-integrations)
1. Click "HipChat".
-1. Select the "Active" checkbox.
+1. Ensure that the **Active** toggle is enabled.
1. Insert the `token` field from the URL into the `Token` field on the Web page.
1. Insert the `room` field from the URL into the `Room` field on the Web page.
1. Save or optionally click "Test Settings".
diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md
index db5397ee7d56026cdd36b7920ffe68b956e14216..2d807d4302b3b240442fa18a02564e4d919224fc 100644
--- a/doc/user/project/integrations/irker.md
+++ b/doc/user/project/integrations/irker.md
@@ -28,7 +28,7 @@ need to follow the firsts steps of the next section.
1. Navigate to the project you want to configure for notifications.
1. Navigate to the [Integrations page](overview.md#accessing-integrations)
1. Click "Irker".
-1. Select the "Active" checkbox.
+1. Ensure that the **Active** toggle is enabled.
1. Enter the server host address where `irkerd` runs (defaults to `localhost`)
in the `Server host` field on the Web page
1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the
diff --git a/doc/user/project/integrations/mattermost_slash_commands.md b/doc/user/project/integrations/mattermost_slash_commands.md
index 6abd613a019a4a04a77e28e4ae16181a866123d2..a23d8d7306df45940cc9a6671bfab69734c9bfb4 100644
--- a/doc/user/project/integrations/mattermost_slash_commands.md
+++ b/doc/user/project/integrations/mattermost_slash_commands.md
@@ -103,7 +103,7 @@ in a new slash command.
### Step 4. Copy the Mattermost token into the Mattermost slash command service
1. In GitLab, paste the Mattermost token you copied in the previous step and
- check the **Active** checkbox.
+ ensure that the **Active** toggle is enabled.
![Mattermost copy token to GitLab](img/mattermost_gitlab_token.png)
diff --git a/doc/user/project/integrations/slack.md b/doc/user/project/integrations/slack.md
index 6ceb398fa17ab4779588fc9a6fa37ac0748fcf6c..ba2a8f55d84b6fac60e4487f48d7218333512818 100644
--- a/doc/user/project/integrations/slack.md
+++ b/doc/user/project/integrations/slack.md
@@ -14,7 +14,7 @@ The Slack Notifications Service allows your GitLab project to send events (e.g.
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**.
1. Select the **Slack notifications** integration to configure it.
-1. Check the **Active** checkbox to turn on the service.
+1. Ensure that the **Active** toggle is enabled.
1. Check the checkboxes corresponding to the GitLab events you want to send to Slack as a notification.
1. For each event, optionally enter the Slack channel names where you want to send the event, separated by a comma. If left empty, the event will be sent to the default channel that you configured in the Slack Configuration step. **Note:** Usernames and private channels are not supported. To send direct messages, use the Member ID found under user's Slack profile.
1. Paste the **Webhook URL** that you copied from the Slack Configuration step.
diff --git a/doc/user/project/integrations/slack_slash_commands.md b/doc/user/project/integrations/slack_slash_commands.md
index d39d5cde46a971f8de39cb5c8504a3e6164c493f..d25a367bd1f40fa1eae01a7ca2d3eab907063626 100644
--- a/doc/user/project/integrations/slack_slash_commands.md
+++ b/doc/user/project/integrations/slack_slash_commands.md
@@ -19,7 +19,7 @@ For GitLab.com, use the [Slack app](gitlab_slack_application.md) instead.
1. Enter a trigger term. We suggest you use the project name. Click **Add Slash Command Integration**.
1. Complete the rest of the fields in the Slack configuration page using information from the GitLab browser tab. In particular, the URL needs to be copied and pasted. Click **Save Integration** to complete the configuration in Slack.
1. While still on the Slack configuration page, copy the **token**. Go back to the GitLab browser tab and paste in the **token**.
-1. Check the **Active** checkbox and click **Save changes** to complete the configuration in GitLab.
+1. Ensure that the **Active** toggle is enabled and click **Save changes** to complete the configuration in GitLab.
![Slack setup instructions](img/slack_setup.png)
diff --git a/doc/user/project/integrations/unify_circuit.md b/doc/user/project/integrations/unify_circuit.md
index 51f524f95c3a1445a7c0f7a87779af5b8ea5daf4..98dc6f298d56b42be358e42d3a67e42bd5a87a00 100644
--- a/doc/user/project/integrations/unify_circuit.md
+++ b/doc/user/project/integrations/unify_circuit.md
@@ -17,7 +17,7 @@ When you have the **Webhook URL** for your Unify Circuit conversation webhook, y
1. Navigate to the [Integrations page](overview.md#accessing-integrations) in your project's settings, i.e. **Project > Settings > Integrations**.
1. Select the **Unify Circuit** integration to configure it.
-1. Check the **Active** checkbox to turn on the service.
+1. Ensure that the **Active** toggle is enabled.
1. Check the checkboxes corresponding to the GitLab events you want to receive in Unify Circuit.
1. Paste the **Webhook URL** that you copied from the Unify Circuit configuration step.
1. Configure the remaining options and click `Save changes`.
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
index 37da3d0bb4a8b5ef30018d9f4b6f54b35a956457..bae514153ac806c4b36f0948e165e9a96701c062 100644
--- a/doc/user/project/releases/index.md
+++ b/doc/user/project/releases/index.md
@@ -309,12 +309,12 @@ Here is an example of a Release Evidence object:
### Enabling Release Evidence display **(CORE ONLY)**
This feature comes with the `:release_evidence_collection` feature flag
-disabled by default in GitLab self-managed instances. To turn it on,
+enabled by default in GitLab self-managed instances. To turn it off,
ask a GitLab administrator with Rails console access to run the following
command:
```ruby
-Feature.enable(:release_evidence_collection)
+Feature.disable(:release_evidence_collection)
```
NOTE: **Note:**
diff --git a/lib/api/entities/runner_details.rb b/lib/api/entities/runner_details.rb
index 17202821e6e2cba1dfeade4406b7377412530aa6..2bb143253fe14f83d976795243641edc5ddf8bfb 100644
--- a/lib/api/entities/runner_details.rb
+++ b/lib/api/entities/runner_details.rb
@@ -10,7 +10,11 @@ module API
expose :access_level
expose :version, :revision, :platform, :architecture
expose :contacted_at
+
+ # @deprecated in 12.10 https://gitlab.com/gitlab-org/gitlab/-/issues/214320
+ # will be removed by 13.0 https://gitlab.com/gitlab-org/gitlab/-/issues/214322
expose :token, if: lambda { |runner, options| options[:current_user].admin? || !runner.instance_type? }
+
# rubocop: disable CodeReuse/ActiveRecord
expose :projects, with: Entities::BasicProjectDetails do |runner, options|
if options[:current_user].admin?
diff --git a/lib/gitlab/import_export/project/relation_factory.rb b/lib/gitlab/import_export/project/relation_factory.rb
index 2405176c518b6c4ce9a009eef81db5dc8d432c42..f7f1195f2f1d24d73ee19bf6dfbae0da8434f801 100644
--- a/lib/gitlab/import_export/project/relation_factory.rb
+++ b/lib/gitlab/import_export/project/relation_factory.rb
@@ -70,8 +70,7 @@ module Gitlab
# Do not create relation if it is:
# - An unknown service
# - A legacy trigger
- unknown_service? ||
- (!Feature.enabled?(:use_legacy_pipeline_triggers, @importable) && legacy_trigger?)
+ unknown_service? || legacy_trigger?
end
def setup_models
diff --git a/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb b/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb
index 301c54b9f23209c0f720f6e8e533b92afec69796..239b5161256bc31fd02da08749fcc49e45b6eaea 100644
--- a/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb
+++ b/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter.rb
@@ -15,6 +15,9 @@ module Gitlab
insert_panel_id(id, panel)
end
+ rescue ActiveModel::UnknownAttributeError => error
+ remove_panel_ids!
+ Gitlab::ErrorTracking.log_exception(error)
end
private
diff --git a/lib/tasks/gitlab/uploads/legacy.rake b/lib/tasks/gitlab/uploads/legacy.rake
deleted file mode 100644
index 74db0060b8d1af88c04eafba60326f9dd7aeae38..0000000000000000000000000000000000000000
--- a/lib/tasks/gitlab/uploads/legacy.rake
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-namespace :gitlab do
- namespace :uploads do
- namespace :legacy do
- desc "GitLab | Uploads | Migrate all legacy attachments"
- task migrate: :environment do
- class Upload < ApplicationRecord
- self.table_name = 'uploads'
-
- include ::EachBatch
- end
-
- migration = 'LegacyUploadsMigrator'
- batch_size = 5000
- delay_interval = 5.minutes.to_i
-
- Upload.where(uploader: 'AttachmentUploader', model_type: 'Note').each_batch(of: batch_size) do |relation, index|
- start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
- delay = index * delay_interval
-
- BackgroundMigrationWorker.perform_in(delay, migration, [start_id, end_id])
- end
- end
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3b1c8f0d706a1b7c9e060212d531c3a5e7022613..9965f8c7d4786a8e2d7495e12d061133e009ac45 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -385,6 +385,11 @@ msgstr ""
msgid "%{name}'s avatar"
msgstr ""
+msgid "%{no_of_days} day"
+msgid_plural "%{no_of_days} days"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr ""
@@ -10840,6 +10845,9 @@ msgstr ""
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "If blank, set allowable lifetime to %{instance_level_policy_in_words}, as defined by the instance admin. Once set, existing tokens for users in this group may be revoked."
+msgstr ""
+
msgid "If checked, group owners can manage LDAP group links and LDAP member overrides"
msgstr ""
@@ -23015,6 +23023,15 @@ msgstr ""
msgid "VulnerabilityManagement|Resolved %{timeago} by %{user}"
msgstr ""
+msgid "VulnerabilityManagement|Something went wrong while trying to delete the comment. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to retrieve the vulnerability history. Please try again later."
+msgstr ""
+
+msgid "VulnerabilityManagement|Something went wrong while trying to save the comment. Please try again later."
+msgstr ""
+
msgid "VulnerabilityManagement|Something went wrong, could not create an issue."
msgstr ""
@@ -25030,6 +25047,9 @@ msgstr ""
msgid "no contributions"
msgstr ""
+msgid "no expiration"
+msgstr ""
+
msgid "no one can merge"
msgstr ""
@@ -25315,6 +25335,9 @@ msgstr ""
msgid "view the blob"
msgstr ""
+msgid "vulnerability|Add a comment"
+msgstr ""
+
msgid "vulnerability|Add a comment or reason for dismissal"
msgstr ""
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index ed05ad60ff0139bad55c00b74dd2ccc7578a908c..8eb15bb6bf5bd91d2a9a36b698ba282688f602c5 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -489,7 +489,6 @@ describe 'Admin updates settings', :clean_gitlab_redis_shared_state, :do_not_moc
end
def check_all_events
- page.check('Active')
page.check('Push')
page.check('Issue')
page.check('Confidential issue')
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
index db5e56bdde03bca050a437f2a952742c9200680d..287af7e7b114d65a3af74a159683d80ab02f278f 100644
--- a/spec/features/dashboard/snippets_spec.rb
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -3,8 +3,10 @@
require 'spec_helper'
describe 'Dashboard snippets' do
+ let_it_be(:user) { create(:user) }
+
context 'when the project has snippets' do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, creator: user) }
let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) }
before do
@@ -22,7 +24,7 @@ describe 'Dashboard snippets' do
end
context 'when there are no project snippets', :js do
- let(:project) { create(:project, :public) }
+ let(:project) { create(:project, :public, creator: user) }
before do
sign_in(project.owner)
@@ -47,9 +49,49 @@ describe 'Dashboard snippets' do
end
end
+ context 'rendering file names' do
+ let_it_be(:snippet) { create(:personal_snippet, :public, author: user, file_name: 'foo.txt') }
+ let_it_be(:versioned_snippet) { create(:personal_snippet, :repository, :public, author: user, file_name: 'bar.txt') }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when feature flag :version_snippets is disabled' do
+ before do
+ stub_feature_flags(version_snippets: false)
+
+ visit dashboard_snippets_path
+ end
+
+ it 'contains the snippet file names from the DB' do
+ aggregate_failures do
+ expect(page).to have_content 'foo.txt'
+ expect(page).to have_content('bar.txt')
+ expect(page).not_to have_content('.gitattributes')
+ end
+ end
+ end
+
+ context 'when feature flag :version_snippets is enabled' do
+ before do
+ stub_feature_flags(version_snippets: true)
+
+ visit dashboard_snippets_path
+ end
+
+ it 'contains both the versioned and non-versioned filenames' do
+ aggregate_failures do
+ expect(page).to have_content 'foo.txt'
+ expect(page).to have_content('.gitattributes')
+ expect(page).not_to have_content('bar.txt')
+ end
+ end
+ end
+ end
+
context 'filtering by visibility' do
- let(:user) { create(:user) }
- let!(:snippets) do
+ let_it_be(:snippets) do
[
create(:personal_snippet, :public, author: user),
create(:personal_snippet, :internal, author: user),
@@ -99,7 +141,7 @@ describe 'Dashboard snippets' do
end
context 'as an external user' do
- let(:user) { create(:user, :external) }
+ let_it_be(:user) { create(:user, :external) }
before do
sign_in(user)
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index 0b0a33620437df311ecfd4d3191e95657ecb9c9a..4f3fb6ac3bfcb755e5f8f653d45640b1e794707e 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -9,7 +9,7 @@ describe 'User activates issue tracker', :js do
let(:url) { 'http://tracker.example.com' }
def fill_short_form(disabled: false)
- uncheck 'Active' if disabled
+ find('input[name="service[active]"] + button').click if disabled
fill_in 'service_project_url', with: url
fill_in 'service_issues_url', with: "#{url}/:id"
diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb
index 557615f88725b65ca3fde96d4622e7f3742ed585..fb9628032b23eec8ea55d9c36bb17414f4a09730 100644
--- a/spec/features/projects/services/user_activates_jira_spec.rb
+++ b/spec/features/projects/services/user_activates_jira_spec.rb
@@ -10,7 +10,7 @@ describe 'User activates Jira', :js do
let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' }
def fill_form(disabled: false)
- uncheck 'Active' if disabled
+ find('input[name="service[active]"] + button').click if disabled
fill_in 'service_url', with: url
fill_in 'service_username', with: 'username'
@@ -53,7 +53,6 @@ describe 'User activates Jira', :js do
it 'shows errors when some required fields are not filled in' do
click_link('Jira')
- check 'Active'
fill_in 'service_password', with: 'password'
click_button('Test settings and save changes')
diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
index 2eaa2d24c4b7ce0fe9b0b0721639af1e85762043..ac9cb00be84a8c560e8b4d3ba67545ec9e3a790d 100644
--- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
@@ -5,14 +5,13 @@ require 'spec_helper'
describe 'Set up Mattermost slash commands', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:service) { project.create_mattermost_slash_commands_service }
let(:mattermost_enabled) { true }
before do
stub_mattermost_setting(enabled: mattermost_enabled)
project.add_maintainer(user)
sign_in(user)
- visit edit_project_service_path(project, service)
+ visit edit_project_service_path(project, :mattermost_slash_commands)
end
describe 'user visits the mattermost slash command config page' do
@@ -30,6 +29,7 @@ describe 'Set up Mattermost slash commands', :js do
token = ('a'..'z').to_a.join
fill_in 'service_token', with: token
+ find('input[name="service[active]"] + button').click
click_on 'Save changes'
expect(current_path).to eq(project_settings_integrations_path(project))
@@ -40,7 +40,6 @@ describe 'Set up Mattermost slash commands', :js do
token = ('a'..'z').to_a.join
fill_in 'service_token', with: token
- check 'service_active'
click_on 'Save changes'
expect(current_path).to eq(project_settings_integrations_path(project))
diff --git a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
index 752ef8d592d1d392a8680b916dce1b2c69a26221..4ce1acd93774aa28af5f97b45ae31954a06969dd 100644
--- a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
@@ -5,12 +5,11 @@ require 'spec_helper'
describe 'Slack slash commands' do
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:service) { project.create_slack_slash_commands_service }
before do
project.add_maintainer(user)
sign_in(user)
- visit edit_project_service_path(project, service)
+ visit edit_project_service_path(project, :slack_slash_commands)
end
it 'shows a token placeholder' do
@@ -23,17 +22,17 @@ describe 'Slack slash commands' do
expect(page).to have_content('This service allows users to perform common')
end
- it 'redirects to the integrations page after saving but not activating' do
+ it 'redirects to the integrations page after saving but not activating', :js do
fill_in 'service_token', with: 'token'
+ find('input[name="service[active]"] + button').click
click_on 'Save'
expect(current_path).to eq(project_settings_integrations_path(project))
expect(page).to have_content('Slack slash commands settings saved, but not activated.')
end
- it 'redirects to the integrations page after activating' do
+ it 'redirects to the integrations page after activating', :js do
fill_in 'service_token', with: 'token'
- check 'service_active'
click_on 'Save'
expect(current_path).to eq(project_settings_integrations_path(project))
diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb
index 2f6aad1d736b4bd918ae3808aa52b30365d33bcf..26734766ff04bc7a1801e538cc13f6d170f5119b 100644
--- a/spec/features/projects/services/user_activates_youtrack_spec.rb
+++ b/spec/features/projects/services/user_activates_youtrack_spec.rb
@@ -9,7 +9,7 @@ describe 'User activates issue tracker', :js do
let(:url) { 'http://tracker.example.com' }
def fill_form(disabled: false)
- uncheck 'Active' if disabled
+ find('input[name="service[active]"] + button').click if disabled
fill_in 'service_project_url', with: url
fill_in 'service_issues_url', with: "#{url}/:id"
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
index 638ecbcc11fcbcfb93f7f3b38087ccb0f7f2c469..b460a031486ac6645741940a99a642556b1791f8 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml
@@ -8,6 +8,7 @@ panel_groups:
type: "area-chart"
y_label: "y_label"
weight: 1
+ max_value: 1
metrics:
- id: metric_a1
query_range: 'query'
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json
index fe2da16c9b798e904a42a38b36cdc90a0ecb119a..20595cc0d73d6fa241c3ee83482c140c665e04d4 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/panels.json
@@ -11,6 +11,7 @@
"type": { "type": "string" },
"y_label": { "type": "string" },
"y_axis": { "$ref": "axis.json" },
+ "max_value": { "type": "number" },
"weight": { "type": "number" },
"metrics": {
"type": "array",
diff --git a/spec/frontend/integrations/edit/components/active_toggle_spec.js b/spec/frontend/integrations/edit/components/active_toggle_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..8a11c200c152599e87a319e4af36bb3c11cb88f6
--- /dev/null
+++ b/spec/frontend/integrations/edit/components/active_toggle_spec.js
@@ -0,0 +1,65 @@
+import { mount } from '@vue/test-utils';
+import ActiveToggle from '~/integrations/edit/components/active_toggle.vue';
+import { GlToggle } from '@gitlab/ui';
+
+const GL_TOGGLE_ACTIVE_CLASS = 'is-checked';
+
+describe('ActiveToggle', () => {
+ let wrapper;
+
+ const defaultProps = {
+ initialActivated: true,
+ disabled: false,
+ };
+
+ const createComponent = props => {
+ wrapper = mount(ActiveToggle, {
+ propsData: Object.assign({}, defaultProps, props),
+ });
+ };
+
+ afterEach(() => {
+ if (wrapper) wrapper.destroy();
+ });
+
+ const findGlToggle = () => wrapper.find(GlToggle);
+ const findButtonInToggle = () => findGlToggle().find('button');
+ const findInputInToggle = () => findGlToggle().find('input');
+
+ describe('template', () => {
+ describe('initialActivated is false', () => {
+ it('renders GlToggle as inactive', () => {
+ createComponent({
+ initialActivated: false,
+ });
+
+ expect(findGlToggle().exists()).toBe(true);
+ expect(findButtonInToggle().classes()).not.toContain(GL_TOGGLE_ACTIVE_CLASS);
+ expect(findInputInToggle().attributes('value')).toBe('false');
+ });
+ });
+
+ describe('initialActivated is true', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders GlToggle as active', () => {
+ expect(findGlToggle().exists()).toBe(true);
+ expect(findButtonInToggle().classes()).toContain(GL_TOGGLE_ACTIVE_CLASS);
+ expect(findInputInToggle().attributes('value')).toBe('true');
+ });
+
+ describe('on toggle click', () => {
+ it('switches the form value', () => {
+ findButtonInToggle().trigger('click');
+
+ wrapper.vm.$nextTick(() => {
+ expect(findButtonInToggle().classes()).not.toContain(GL_TOGGLE_ACTIVE_CLASS);
+ expect(findInputInToggle().attributes('value')).toBe('false');
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/notes/components/note_header_spec.js b/spec/frontend/notes/components/note_header_spec.js
index 642ab5138dc21471f98134badd89a837c49c04ee..d477de69716af4fae31434040c6005406853905d 100644
--- a/spec/frontend/notes/components/note_header_spec.js
+++ b/spec/frontend/notes/components/note_header_spec.js
@@ -16,7 +16,9 @@ describe('NoteHeader component', () => {
const findActionsWrapper = () => wrapper.find({ ref: 'discussionActions' });
const findChevronIcon = () => wrapper.find({ ref: 'chevronIcon' });
const findActionText = () => wrapper.find({ ref: 'actionText' });
+ const findTimestampLink = () => wrapper.find({ ref: 'noteTimestampLink' });
const findTimestamp = () => wrapper.find({ ref: 'noteTimestamp' });
+ const findSpinner = () => wrapper.find({ ref: 'spinner' });
const author = {
avatar_url: null,
@@ -33,11 +35,7 @@ describe('NoteHeader component', () => {
store: new Vuex.Store({
actions,
}),
- propsData: {
- ...props,
- actionTextHtml: '',
- noteId: '1394',
- },
+ propsData: { ...props },
});
};
@@ -108,17 +106,18 @@ describe('NoteHeader component', () => {
createComponent();
expect(findActionText().exists()).toBe(false);
- expect(findTimestamp().exists()).toBe(false);
+ expect(findTimestampLink().exists()).toBe(false);
});
describe('when createdAt is passed as a prop', () => {
it('renders action text and a timestamp', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
+ noteId: 123,
});
expect(findActionText().exists()).toBe(true);
- expect(findTimestamp().exists()).toBe(true);
+ expect(findTimestampLink().exists()).toBe(true);
});
it('renders correct actionText if passed', () => {
@@ -133,8 +132,9 @@ describe('NoteHeader component', () => {
it('calls an action when timestamp is clicked', () => {
createComponent({
createdAt: '2017-08-02T10:51:58.559Z',
+ noteId: 123,
});
- findTimestamp().trigger('click');
+ findTimestampLink().trigger('click');
expect(actions.setTargetNoteHash).toHaveBeenCalled();
});
@@ -153,4 +153,30 @@ describe('NoteHeader component', () => {
expect(wrapper.find(GitlabTeamMemberBadge).exists()).toBe(expected);
},
);
+
+ describe('loading spinner', () => {
+ it('shows spinner when showSpinner is true', () => {
+ createComponent();
+ expect(findSpinner().exists()).toBe(true);
+ });
+
+ it('does not show spinner when showSpinner is false', () => {
+ createComponent({ showSpinner: false });
+ expect(findSpinner().exists()).toBe(false);
+ });
+ });
+
+ describe('timestamp', () => {
+ it('shows timestamp as a link if a noteId was provided', () => {
+ createComponent({ createdAt: new Date().toISOString(), noteId: 123 });
+ expect(findTimestampLink().exists()).toBe(true);
+ expect(findTimestamp().exists()).toBe(false);
+ });
+
+ it('shows timestamp as plain text if a noteId was not provided', () => {
+ createComponent({ createdAt: new Date().toISOString() });
+ expect(findTimestampLink().exists()).toBe(false);
+ expect(findTimestamp().exists()).toBe(true);
+ });
+ });
});
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
index 6fdf4f5cfb4dd47bdbc91d541efb2c5e81f5d1e0..b5b431b5818ec4e77102b1c5f22dc534c15798f0 100644
--- a/spec/helpers/snippets_helper_spec.rb
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -151,4 +151,35 @@ describe SnippetsHelper do
"\" autocomplete=\"off\">"
end
end
+
+ describe '#snippet_file_name' do
+ subject { helper.snippet_file_name(snippet) }
+
+ where(:snippet_type, :flag_enabled, :trait, :filename) do
+ [
+ [:personal_snippet, false, nil, 'foo.txt'],
+ [:personal_snippet, true, nil, 'foo.txt'],
+ [:personal_snippet, false, :repository, 'foo.txt'],
+ [:personal_snippet, true, :repository, '.gitattributes'],
+
+ [:project_snippet, false, nil, 'foo.txt'],
+ [:project_snippet, true, nil, 'foo.txt'],
+ [:project_snippet, false, :repository, 'foo.txt'],
+ [:project_snippet, true, :repository, '.gitattributes']
+ ]
+ end
+
+ with_them do
+ let(:snippet) { create(snippet_type, trait, file_name: 'foo.txt') }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(snippet.author)
+ stub_feature_flags(version_snippets: flag_enabled)
+ end
+
+ it 'returns the correct filename' do
+ expect(subject).to eq filename
+ end
+ end
+ end
end
diff --git a/spec/javascripts/integrations/integration_settings_form_spec.js b/spec/javascripts/integrations/integration_settings_form_spec.js
index 82d1f815ca8195a8514876cf813f1ee815079c55..72d04be822f7665c5b5c09813ff18d978029cad9 100644
--- a/spec/javascripts/integrations/integration_settings_form_spec.js
+++ b/spec/javascripts/integrations/integration_settings_form_spec.js
@@ -23,9 +23,9 @@ describe('IntegrationSettingsForm', () => {
// Form Reference
expect(integrationSettingsForm.$form).toBeDefined();
expect(integrationSettingsForm.$form.prop('nodeName')).toEqual('FORM');
+ expect(integrationSettingsForm.formActive).toBeDefined();
// Form Child Elements
- expect(integrationSettingsForm.$serviceToggle).toBeDefined();
expect(integrationSettingsForm.$submitBtn).toBeDefined();
expect(integrationSettingsForm.$submitBtnLoader).toBeDefined();
expect(integrationSettingsForm.$submitBtnLabel).toBeDefined();
@@ -45,13 +45,15 @@ describe('IntegrationSettingsForm', () => {
});
it('should remove `novalidate` attribute to form when called with `true`', () => {
- integrationSettingsForm.toggleServiceState(true);
+ integrationSettingsForm.formActive = true;
+ integrationSettingsForm.toggleServiceState();
expect(integrationSettingsForm.$form.attr('novalidate')).not.toBeDefined();
});
it('should set `novalidate` attribute to form when called with `false`', () => {
- integrationSettingsForm.toggleServiceState(false);
+ integrationSettingsForm.formActive = false;
+ integrationSettingsForm.toggleServiceState();
expect(integrationSettingsForm.$form.attr('novalidate')).toBeDefined();
});
@@ -66,8 +68,9 @@ describe('IntegrationSettingsForm', () => {
it('should set Save button label to "Test settings and save changes" when serviceActive & canTestService are `true`', () => {
integrationSettingsForm.canTestService = true;
+ integrationSettingsForm.formActive = true;
- integrationSettingsForm.toggleSubmitBtnLabel(true);
+ integrationSettingsForm.toggleSubmitBtnLabel();
expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual(
'Test settings and save changes',
@@ -76,18 +79,22 @@ describe('IntegrationSettingsForm', () => {
it('should set Save button label to "Save changes" when either serviceActive or canTestService (or both) is `false`', () => {
integrationSettingsForm.canTestService = false;
+ integrationSettingsForm.formActive = false;
- integrationSettingsForm.toggleSubmitBtnLabel(false);
+ integrationSettingsForm.toggleSubmitBtnLabel();
expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
- integrationSettingsForm.toggleSubmitBtnLabel(true);
+ integrationSettingsForm.formActive = true;
+
+ integrationSettingsForm.toggleSubmitBtnLabel();
expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
integrationSettingsForm.canTestService = true;
+ integrationSettingsForm.formActive = false;
- integrationSettingsForm.toggleSubmitBtnLabel(false);
+ integrationSettingsForm.toggleSubmitBtnLabel();
expect(integrationSettingsForm.$submitBtnLabel.text()).toEqual('Save changes');
});
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 515d72add92608b7623f7aa0a07d567958859ff0..5d5e2fe2a3336e4483a32ed848e4916139ceaa7d 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -58,6 +58,7 @@ notes:
- system_note_metadata
- note_diff_file
- suggestions
+- diff_note_positions
- review
label_links:
- target
@@ -134,6 +135,7 @@ merge_requests:
- pipelines_for_merge_request
- merge_request_assignees
- suggestions
+- diff_note_positions
- unresolved_notes
- assignees
- reviews
@@ -517,6 +519,8 @@ error_tracking_setting:
- project
suggestions:
- note
+diff_note_positions:
+- note
metrics_setting:
- project
protected_environments:
diff --git a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
index 1eac580bc5ea57bbda4382c388a9551d66a810c0..80ae9a082572c5859b345505b663945c75edb4b0 100644
--- a/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_restorer_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::ImportExport::Project::TreeRestorer, quarantine: { flaky: 'http
@project = create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project')
@shared = @project.import_export_shared
- allow(Feature).to receive(:enabled?).and_call_original
+ allow(Feature).to receive(:enabled?) { true }
stub_feature_flags(project_import_ndjson: ndjson_enabled)
setup_import_export_config('complex')
@@ -34,6 +34,7 @@ describe Gitlab::ImportExport::Project::TreeRestorer, quarantine: { flaky: 'http
allow_any_instance_of(Repository).to receive(:fetch_source_branch!).and_return(true)
allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
+ expect(@shared).not_to receive(:error)
expect_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch).with('feature', 'DCBA')
allow_any_instance_of(Gitlab::Git::Repository).to receive(:create_branch)
diff --git a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
index ded57b1d576635f480a0ae1617b600b76daaf054..8adc360026d94d14001724b756ce047ce4e4c73a 100644
--- a/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project/tree_saver_spec.rb
@@ -29,12 +29,11 @@ describe Gitlab::ImportExport::Project::TreeSaver do
before_all do
RSpec::Mocks.with_temporary_scope do
- allow(Feature).to receive(:enabled?).and_call_original
+ allow(Feature).to receive(:enabled?) { true }
stub_feature_flags(project_export_as_ndjson: ndjson_enabled)
project.add_maintainer(user)
- stub_feature_flags(project_export_as_ndjson: ndjson_enabled)
project_tree_saver = described_class.new(project: project, current_user: user, shared: shared)
project_tree_saver.save
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 3cb02a8bcb370ad36ac384b69482624acf913e31..b2fca0b595421a874194cf9d564afd864de12ae0 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -15,7 +15,8 @@ describe Gitlab::Metrics::Dashboard::Processor do
Gitlab::Metrics::Dashboard::Stages::CustomMetricsDetailsInserter,
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter,
- Gitlab::Metrics::Dashboard::Stages::AlertsInserter
+ Gitlab::Metrics::Dashboard::Stages::AlertsInserter,
+ Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter
]
end
@@ -28,6 +29,12 @@ describe Gitlab::Metrics::Dashboard::Processor do
end
end
+ it 'includes an id for each dashboard panel' do
+ expect(all_panels).to satisfy_all do |panel|
+ panel[:id].present?
+ end
+ end
+
it 'includes boolean to indicate if panel group has custom metrics' do
expect(dashboard[:panel_groups]).to all(include( { has_custom_metrics: boolean } ))
end
@@ -199,9 +206,11 @@ describe Gitlab::Metrics::Dashboard::Processor do
private
def all_metrics
- dashboard[:panel_groups].flat_map do |group|
- group[:panels].flat_map { |panel| panel[:metrics] }
- end
+ all_panels.flat_map { |panel| panel[:metrics] }
+ end
+
+ def all_panels
+ dashboard[:panel_groups].flat_map { |group| group[:panels] }
end
def get_metric_details(metric)
diff --git a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
index 426a54bea78f62638e13eb957a8a6a3ec182b979..6124f471e3980f537bd788b7cf100e4448995df2 100644
--- a/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/stages/panel_ids_inserter_spec.rb
@@ -63,5 +63,24 @@ describe Gitlab::Metrics::Dashboard::Stages::PanelIdsInserter do
)
end
end
+
+ context 'when dashboard panels has unknown schema attributes' do
+ before do
+ error = ActiveModel::UnknownAttributeError.new(double, 'unknown_panel_attribute')
+ allow(::PerformanceMonitoring::PrometheusPanel).to receive(:new).and_raise(error)
+ end
+
+ it 'no panel has assigned id' do
+ transform!
+
+ expect(fetch_panel_ids(dashboard)).to all be_nil
+ end
+
+ it 'logs the failure' do
+ expect(Gitlab::ErrorTracking).to receive(:log_exception)
+
+ transform!
+ end
+ end
end
end
diff --git a/spec/models/diff_note_position_spec.rb b/spec/models/diff_note_position_spec.rb
index a00ba35feef787dc65d795f16fa2fb845879ca08..dedb8a8da4d41f1ec710da4acee9d1beb6f30bdd 100644
--- a/spec/models/diff_note_position_spec.rb
+++ b/spec/models/diff_note_position_spec.rb
@@ -3,14 +3,35 @@
require 'spec_helper'
describe DiffNotePosition, type: :model do
- it 'has a position attribute' do
- diff_position = build(:diff_position)
- line_code = 'bd4b7bfff3a247ccf6e3371c41ec018a55230bcc_534_521'
- diff_note_position = build(:diff_note_position, line_code: line_code, position: diff_position)
-
- expect(diff_note_position.position).to eq(diff_position)
- expect(diff_note_position.line_code).to eq(line_code)
- expect(diff_note_position.diff_content_type).to eq('text')
+ describe '.create_or_update_by' do
+ context 'when a diff note' do
+ let(:note) { create(:diff_note_on_merge_request) }
+ let(:diff_position) { build(:diff_position) }
+ let(:line_code) { 'bd4b7bfff3a247ccf6e3371c41ec018a55230bcc_534_521' }
+ let(:diff_note_position) { note.diff_note_positions.first }
+ let(:params) { { diff_type: :head, line_code: line_code, position: diff_position } }
+
+ context 'does not have a diff note position' do
+ it 'creates a diff note position' do
+ described_class.create_or_update_for(note, params)
+
+ expect(diff_note_position.position).to eq(diff_position)
+ expect(diff_note_position.line_code).to eq(line_code)
+ expect(diff_note_position.diff_content_type).to eq('text')
+ end
+ end
+
+ context 'has a diff note position' do
+ it 'updates the existing diff note position' do
+ create(:diff_note_position, note: note)
+ described_class.create_or_update_for(note, params)
+
+ expect(note.diff_note_positions.size).to eq(1)
+ expect(diff_note_position.position).to eq(diff_position)
+ expect(diff_note_position.line_code).to eq(line_code)
+ end
+ end
+ end
end
it 'unique by note_id and diff type' do
diff --git a/spec/services/discussions/capture_diff_note_position_service_spec.rb b/spec/services/discussions/capture_diff_note_position_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fced2eb7fce6222ad7e89ac24bb970bcd3f9c418
--- /dev/null
+++ b/spec/services/discussions/capture_diff_note_position_service_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Discussions::CaptureDiffNotePositionService do
+ context 'image note on diff' do
+ let!(:note) { create(:image_diff_note_on_merge_request) }
+
+ subject { described_class.new(note.noteable, ['files/images/any_image.png']) }
+
+ it 'is note affected by the service' do
+ expect(Gitlab::Diff::PositionTracer).not_to receive(:new)
+
+ expect(subject.execute(note.discussion)).to eq(nil)
+ expect(note.diff_note_positions).to be_empty
+ end
+ end
+
+ context 'when empty paths are passed as a param' do
+ let!(:note) { create(:diff_note_on_merge_request) }
+
+ subject { described_class.new(note.noteable, []) }
+
+ it 'does not calculate positons' do
+ expect(Gitlab::Diff::PositionTracer).not_to receive(:new)
+
+ expect(subject.execute(note.discussion)).to eq(nil)
+ expect(note.diff_note_positions).to be_empty
+ end
+ end
+end
diff --git a/spec/services/discussions/capture_diff_note_positions_service_spec.rb b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7b1e207f3eb1ffabf860aea7cb8b7381841e8f2f
--- /dev/null
+++ b/spec/services/discussions/capture_diff_note_positions_service_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Discussions::CaptureDiffNotePositionsService do
+ context 'when merge request has a discussion' do
+ let(:source_branch) { 'compare-with-merge-head-source' }
+ let(:target_branch) { 'compare-with-merge-head-target' }
+ let(:merge_request) { create(:merge_request, source_branch: source_branch, target_branch: target_branch) }
+ let(:project) { merge_request.project }
+
+ let(:offset) { 30 }
+ let(:first_new_line) { 508 }
+ let(:second_new_line) { 521 }
+
+ let(:service) { described_class.new(merge_request) }
+
+ def build_position(new_line, diff_refs)
+ path = 'files/markdown/ruby-style-guide.md'
+ Gitlab::Diff::Position.new(old_path: path, new_path: path,
+ new_line: new_line, diff_refs: diff_refs)
+ end
+
+ def note_for(new_line)
+ position = build_position(new_line, merge_request.diff_refs)
+ create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request)
+ end
+
+ def verify_diff_note_position!(note, line)
+ id, old_line, new_line = note.line_code.split('_')
+
+ expect(new_line).to eq(line.to_s)
+ expect(note.diff_note_positions.size).to eq(1)
+
+ diff_position = note.diff_note_positions.last
+ diff_refs = Gitlab::Diff::DiffRefs.new(
+ base_sha: merge_request.target_branch_sha,
+ start_sha: merge_request.target_branch_sha,
+ head_sha: merge_request.merge_ref_head.sha)
+
+ expect(diff_position.line_code).to eq("#{id}_#{old_line.to_i - offset}_#{new_line}")
+ expect(diff_position.position).to eq(build_position(new_line.to_i, diff_refs))
+ end
+
+ let!(:first_discussion_note) { note_for(first_new_line) }
+ let!(:second_discussion_note) { note_for(second_new_line) }
+ let!(:second_discussion_another_note) do
+ create(:diff_note_on_merge_request,
+ project: project,
+ position: second_discussion_note.position,
+ discussion_id: second_discussion_note.discussion_id,
+ noteable: merge_request)
+ end
+
+ context 'and position of the discussion changed on target branch head' do
+ it 'diff positions are created for the first notes of the discussions' do
+ MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request)
+ service.execute
+
+ verify_diff_note_position!(first_discussion_note, first_new_line)
+ verify_diff_note_position!(second_discussion_note, second_new_line)
+
+ expect(second_discussion_another_note.diff_note_positions).to be_empty
+ end
+ end
+ end
+end
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index 8f17e8083e39681acbdd0499762749eb25844391..45519ddf3d38edca906d748624e5f4e9c00a6450 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -33,6 +33,24 @@ describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_sta
expect(merge_request.merge_status).to eq('can_be_merged')
end
+ it 'update diff discussion positions' do
+ expect_next_instance_of(Discussions::CaptureDiffNotePositionsService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ subject
+ end
+
+ context 'when merge_ref_head_comments is disabled' do
+ it 'does not update diff discussion positions' do
+ stub_feature_flags(merge_ref_head_comments: false)
+
+ expect(Discussions::CaptureDiffNotePositionsService).not_to receive(:new)
+
+ subject
+ end
+ end
+
it 'updates the merge ref' do
expect { subject }.to change(merge_request, :merge_ref_head).from(nil)
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index a03b78a9a7a16aa4168dcb01a1e05d8ddc8efe93..c461dd700ecbab103ea33beeb6ddfeb512e993a0 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -143,10 +143,21 @@ describe Notes::CreateService do
end
it 'note is associated with a note diff file' do
+ MergeRequests::MergeToRefService.new(merge_request.project, merge_request.author).execute(merge_request)
+
note = described_class.new(project_with_repo, user, new_opts).execute
expect(note).to be_persisted
expect(note.note_diff_file).to be_present
+ expect(note.diff_note_positions).to be_present
+ end
+
+ it 'does not create diff positions merge_ref_head_comments is disabled' do
+ stub_feature_flags(merge_ref_head_comments: false)
+
+ expect(Discussions::CaptureDiffNotePositionService).not_to receive(:new)
+
+ described_class.new(project_with_repo, user, new_opts).execute
end
end
@@ -160,6 +171,8 @@ describe Notes::CreateService do
end
it 'note is not associated with a note diff file' do
+ expect(Discussions::CaptureDiffNotePositionService).not_to receive(:new)
+
note = described_class.new(project_with_repo, user, new_opts).execute
expect(note).to be_persisted
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index fd41c5c8fe38db3fb6927e4a304708e467dc33a8..47d69ca1f6a6937a0e6f2dcbb1a97957beadd574 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -73,7 +73,9 @@ module TestEnv
'submodule_inside_folder' => 'b491b92',
'png-lfs' => 'fe42f41',
'sha-starting-with-large-number' => '8426165',
- 'invalid-utf8-diff-paths' => '99e4853'
+ 'invalid-utf8-diff-paths' => '99e4853',
+ 'compare-with-merge-head-source' => 'b5f4399',
+ 'compare-with-merge-head-target' => '2f1e176'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
index 4e287f36648558ae55dc93e3cb8b0946c3d05e2b..1a5668946c6e8f8ca0fe907179b3dbfc4f4691fe 100644
--- a/spec/support/import_export/common_util.rb
+++ b/spec/support/import_export/common_util.rb
@@ -38,15 +38,12 @@ module ImportExport
end
def setup_reader(reader)
- case reader
- when :legacy_reader
- allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(true)
- allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(false)
- when :ndjson_reader
+ if reader == :ndjson_reader && Feature.enabled?(:project_import_ndjson)
allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(false)
allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(true)
else
- raise "invalid reader #{reader}. Supported readers: :legacy_reader, :ndjson_reader"
+ allow_any_instance_of(Gitlab::ImportExport::JSON::LegacyReader::File).to receive(:exist?).and_return(true)
+ allow_any_instance_of(Gitlab::ImportExport::JSON::NdjsonReader).to receive(:exist?).and_return(false)
end
end