Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
221b529789
commit
b7c735c8ac
71 changed files with 799 additions and 141 deletions
|
@ -0,0 +1,53 @@
|
|||
<script>
|
||||
import eventHub from '../event_hub';
|
||||
import { GlToggle } from '@gitlab/ui';
|
||||
|
||||
export default {
|
||||
name: 'ActiveToggle',
|
||||
components: {
|
||||
GlToggle,
|
||||
},
|
||||
props: {
|
||||
initialActivated: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activated: this.initialActivated,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// Initialize view
|
||||
this.$nextTick(() => {
|
||||
this.onToggle(this.activated);
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
onToggle(e) {
|
||||
eventHub.$emit('toggle', e);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="form-group row" role="group">
|
||||
<label for="service[active]" class="col-form-label col-sm-2">{{ __('Active') }}</label>
|
||||
<div class="col-sm-10 pt-1">
|
||||
<gl-toggle
|
||||
v-model="activated"
|
||||
:disabled="disabled"
|
||||
name="service[active]"
|
||||
@change="onToggle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
3
app/assets/javascripts/integrations/edit/event_hub.js
Normal file
3
app/assets/javascripts/integrations/edit/event_hub.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
export default new Vue();
|
30
app/assets/javascripts/integrations/edit/index.js
Normal file
30
app/assets/javascripts/integrations/edit/index.js
Normal file
|
@ -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,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
|||
<template v-if="actionText">{{ actionText }}</template>
|
||||
</span>
|
||||
<a
|
||||
ref="noteTimestamp"
|
||||
v-if="noteTimestampLink"
|
||||
ref="noteTimestampLink"
|
||||
:href="noteTimestampLink"
|
||||
class="note-timestamp system-note-separator"
|
||||
@click="updateTargetNoteHash"
|
||||
>
|
||||
<time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
|
||||
</a>
|
||||
<time-ago-tooltip v-else ref="noteTimestamp" :time="createdAt" tooltip-placement="bottom" />
|
||||
</template>
|
||||
<slot name="extra-controls"></slot>
|
||||
<i
|
||||
v-if="showSpinner"
|
||||
ref="spinner"
|
||||
class="fa fa-spinner fa-spin editing-spinner"
|
||||
:aria-label="__('Comment is being updated')"
|
||||
aria-hidden="true"
|
||||
|
|
|
@ -9,7 +9,7 @@ class Projects::ReleasesController < Projects::ApplicationController
|
|||
push_frontend_feature_flag(:release_issue_summary, project, default_enabled: true)
|
||||
push_frontend_feature_flag(:release_evidence_collection, project, default_enabled: true)
|
||||
push_frontend_feature_flag(:release_show_page, project, default_enabled: true)
|
||||
push_frontend_feature_flag(:release_asset_link_editing, project)
|
||||
push_frontend_feature_flag(:release_asset_link_editing, project, default_enabled: true)
|
||||
end
|
||||
before_action :authorize_update_release!, only: %i[edit update]
|
||||
|
||||
|
|
|
@ -160,4 +160,14 @@ module SnippetsHelper
|
|||
title: 'Download',
|
||||
rel: 'noopener noreferrer')
|
||||
end
|
||||
|
||||
def snippet_file_name(snippet)
|
||||
blob = if Feature.enabled?(:version_snippets, current_user) && !snippet.repository.empty?
|
||||
snippet.blobs.first
|
||||
else
|
||||
snippet.blob
|
||||
end
|
||||
|
||||
blob.name
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,9 +28,20 @@ class DiffNotePosition < ApplicationRecord
|
|||
end
|
||||
|
||||
def position=(position)
|
||||
assign_attributes(self.class.position_to_attrs(position))
|
||||
end
|
||||
|
||||
def self.create_or_update_for(note, params)
|
||||
attrs = position_to_attrs(params[:position])
|
||||
attrs.merge!(params.slice(:diff_type, :line_code))
|
||||
attrs[:note_id] = note.id
|
||||
|
||||
upsert(attrs, unique_by: [:note_id, :diff_type])
|
||||
end
|
||||
|
||||
def self.position_to_attrs(position)
|
||||
position_attrs = position.to_h
|
||||
position_attrs[:diff_content_type] = position_attrs.delete(:position_type)
|
||||
|
||||
assign_attributes(position_attrs)
|
||||
position_attrs
|
||||
end
|
||||
end
|
||||
|
|
|
@ -83,6 +83,7 @@ class Note < ApplicationRecord
|
|||
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_one :system_note_metadata
|
||||
has_one :note_diff_file, inverse_of: :diff_note, foreign_key: :diff_note_id
|
||||
has_many :diff_note_positions
|
||||
|
||||
delegate :gfm_reference, :local_reference, to: :noteable
|
||||
delegate :name, to: :project, prefix: true
|
||||
|
|
|
@ -4,7 +4,7 @@ module PerformanceMonitoring
|
|||
class PrometheusPanel
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_accessor :type, :title, :y_label, :weight, :metrics, :y_axis
|
||||
attr_accessor :type, :title, :y_label, :weight, :metrics, :y_axis, :max_value
|
||||
|
||||
validates :title, presence: true
|
||||
validates :metrics, presence: true
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Discussions
|
||||
class CaptureDiffNotePositionService
|
||||
def initialize(merge_request, paths)
|
||||
@project = merge_request.project
|
||||
@tracer = build_tracer(merge_request, paths)
|
||||
end
|
||||
|
||||
def execute(discussion)
|
||||
# The service has been implemented for text only
|
||||
# The impact of image notes on this service is being investigated in
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/213989
|
||||
return unless discussion.on_text?
|
||||
|
||||
result = tracer&.trace(discussion.position)
|
||||
return unless result
|
||||
|
||||
position = result[:position]
|
||||
|
||||
# Currently position data is copied across all notes of a discussion
|
||||
# It makes sense to store a position only for the first note instead
|
||||
# Within the newly introduced table we can start doing just that
|
||||
DiffNotePosition.create_or_update_for(discussion.notes.first,
|
||||
diff_type: :head,
|
||||
position: position,
|
||||
line_code: position.line_code(project.repository))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :tracer, :project
|
||||
|
||||
def build_tracer(merge_request, paths)
|
||||
return if paths.blank?
|
||||
|
||||
old_diff_refs, new_diff_refs = build_diff_refs(merge_request)
|
||||
|
||||
return unless old_diff_refs && new_diff_refs
|
||||
|
||||
Gitlab::Diff::PositionTracer.new(
|
||||
project: project,
|
||||
old_diff_refs: old_diff_refs,
|
||||
new_diff_refs: new_diff_refs,
|
||||
paths: paths.uniq)
|
||||
end
|
||||
|
||||
def build_diff_refs(merge_request)
|
||||
merge_ref_head = merge_request.merge_ref_head
|
||||
return unless merge_ref_head
|
||||
|
||||
start_sha, base_sha = merge_ref_head.parent_ids
|
||||
new_diff_refs = Gitlab::Diff::DiffRefs.new(
|
||||
base_sha: base_sha,
|
||||
start_sha: start_sha,
|
||||
head_sha: merge_ref_head.id)
|
||||
|
||||
old_diff_refs = merge_request.diff_refs
|
||||
|
||||
return if new_diff_refs == old_diff_refs
|
||||
|
||||
[old_diff_refs, new_diff_refs]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Discussions
|
||||
class CaptureDiffNotePositionsService
|
||||
def initialize(merge_request)
|
||||
@merge_request = merge_request
|
||||
end
|
||||
|
||||
def execute
|
||||
return unless merge_request.has_complete_diff_refs?
|
||||
|
||||
discussions, paths = build_discussions
|
||||
|
||||
service = Discussions::CaptureDiffNotePositionService.new(merge_request, paths)
|
||||
|
||||
discussions.each do |discussion|
|
||||
service.execute(discussion)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :merge_request
|
||||
|
||||
def build_discussions
|
||||
active_diff_discussions = merge_request.notes.new_diff_notes.discussions.select do |discussion|
|
||||
discussion.active?(merge_request.diff_refs)
|
||||
end
|
||||
paths = active_diff_discussions.flat_map { |n| n.diff_file.paths }
|
||||
|
||||
[active_diff_discussions, paths]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -118,11 +118,18 @@ module MergeRequests
|
|||
|
||||
if can_git_merge? && merge_to_ref
|
||||
merge_request.mark_as_mergeable
|
||||
update_diff_discussion_positions!
|
||||
else
|
||||
merge_request.mark_as_unmergeable
|
||||
end
|
||||
end
|
||||
|
||||
def update_diff_discussion_positions!
|
||||
return if Feature.disabled?(:merge_ref_head_comments, merge_request.target_project)
|
||||
|
||||
Discussions::CaptureDiffNotePositionsService.new(merge_request).execute
|
||||
end
|
||||
|
||||
def recheck!
|
||||
if !merge_request.recheck_merge_status? && outdated_merge_ref?
|
||||
merge_request.mark_as_unchecked
|
||||
|
|
|
@ -65,6 +65,10 @@ module Notes
|
|||
if Feature.enabled?(:notes_create_service_tracking, project)
|
||||
Gitlab::Tracking.event('Notes::CreateService', 'execute', tracking_data_for(note))
|
||||
end
|
||||
|
||||
if Feature.enabled?(:merge_ref_head_comments, project) && note.for_merge_request? && note.diff_note? && note.start_of_discussion?
|
||||
Discussions::CaptureDiffNotePositionService.new(note.noteable, note.diff_file&.paths).execute(note.discussion)
|
||||
end
|
||||
end
|
||||
|
||||
def do_commands(note, update_params, message, only_commands)
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
= render 'groups/settings/project_creation_level', f: f, group: @group
|
||||
= render 'groups/settings/subgroup_creation_level', f: f, group: @group
|
||||
= render 'groups/settings/two_factor_auth', f: f
|
||||
= render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group
|
||||
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
|
||||
|
||||
= f.submit _('Save changes'), class: 'btn btn-success prepend-top-default js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
= markdown @service.help
|
||||
|
||||
.service-settings
|
||||
- if @service.show_active_box?
|
||||
.form-group.row
|
||||
= form.label :active, "Active", class: "col-form-label col-sm-2"
|
||||
.col-sm-10
|
||||
= form.check_box :active, checked: @service.active || @service.new_record?, disabled: disable_fields_service?(@service)
|
||||
.js-vue-integration-settings{ data: { show_active: @service.show_active_box?.to_s, activated: (@service.active || @service.new_record?).to_s, disabled: disable_fields_service?(@service).to_s } }
|
||||
|
||||
- if @service.configurable_events.present?
|
||||
.form-group.row
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
- link_project = local_assigns.fetch(:link_project, false)
|
||||
- notes_count = @noteable_meta_data[snippet.id].user_notes_count
|
||||
- file_name = snippet_file_name(snippet)
|
||||
|
||||
%li.snippet-row.py-3
|
||||
= image_tag avatar_icon_for_user(snippet.author), class: "avatar s40 d-none d-sm-block", alt: ''
|
||||
|
@ -7,10 +8,10 @@
|
|||
.title
|
||||
= link_to gitlab_snippet_path(snippet) do
|
||||
= snippet.title
|
||||
- if snippet.file_name.present?
|
||||
- if file_name.present?
|
||||
%span.snippet-filename.d-none.d-sm-inline-block.ml-2
|
||||
= sprite_icon('doc-code', size: 16, css_class: 'file-icon align-text-bottom')
|
||||
= snippet.file_name
|
||||
= file_name
|
||||
|
||||
%ul.controls
|
||||
%li
|
||||
|
|
|
@ -944,7 +944,7 @@
|
|||
- :name: background_migration
|
||||
:feature_category: :database
|
||||
:has_external_dependencies:
|
||||
:urgency: :low
|
||||
:urgency: :throttled
|
||||
:resource_boundary: :unknown
|
||||
:weight: 1
|
||||
:idempotent:
|
||||
|
|
|
@ -4,6 +4,7 @@ class BackgroundMigrationWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
include ApplicationWorker
|
||||
|
||||
feature_category :database
|
||||
urgency :throttled
|
||||
|
||||
# The minimum amount of time between processing two jobs of the same migration
|
||||
# class.
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update Active checkbox component to use toggle
|
||||
merge_request: 27778
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Deprecate 'token' attribute from Runners API
|
||||
merge_request: 29481
|
||||
author:
|
||||
type: deprecated
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: Fix dashboard processing error which prevented dashboards with unknown attributes
|
||||
inside panels from being displayed
|
||||
merge_request: 29517
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Migrate legacy uploads out of deprecated paths
|
||||
merge_request: 29295
|
||||
author:
|
||||
type: fixed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow Release links to be edited on the Edit Release page
|
||||
merge_request: 28816
|
||||
author:
|
||||
type: added
|
30
db/post_migrate/20200409211607_migrate_legacy_attachments.rb
Normal file
30
db/post_migrate/20200409211607_migrate_legacy_attachments.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MigrateLegacyAttachments < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
MIGRATION = 'LegacyUploadsMigrator'
|
||||
BATCH_SIZE = 5000
|
||||
INTERVAL = 5.minutes.to_i
|
||||
|
||||
class Upload < ActiveRecord::Base
|
||||
self.table_name = 'uploads'
|
||||
|
||||
include ::EachBatch
|
||||
end
|
||||
|
||||
def up
|
||||
queue_background_migration_jobs_by_range_at_intervals(Upload.where(uploader: 'AttachmentUploader', model_type: 'Note'),
|
||||
MIGRATION,
|
||||
INTERVAL,
|
||||
batch_size: BATCH_SIZE)
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
end
|
||||
end
|
|
@ -13152,5 +13152,6 @@ COPY "schema_migrations" (version) FROM STDIN;
|
|||
20200408110856
|
||||
20200408153842
|
||||
20200408175424
|
||||
20200409211607
|
||||
\.
|
||||
|
||||
|
|
|
@ -111,24 +111,6 @@ sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeReque
|
|||
sudo -u git -H bundle exec rake "gitlab:uploads:migrate[DesignManagement::DesignV432x230Uploader, DesignManagement::Action]"
|
||||
```
|
||||
|
||||
## Migrate legacy uploads out of deprecated paths
|
||||
|
||||
> 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
|
||||
|
|
|
@ -162,6 +162,10 @@ GET /runners/:id
|
|||
curl --header "PRIVATE-TOKEN: <your_access_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: <your_access_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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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".
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
|
|
@ -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:**
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -151,4 +151,35 @@ describe SnippetsHelper do
|
|||
"<input type=\"text\" readonly=\"readonly\" class=\"js-snippet-url-area snippet-embed-input form-control\" data-url=\"#{url}\" value=\"<script src="#{url}.js"></script>\" autocomplete=\"off\"></input>"
|
||||
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
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
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 } }
|
||||
|
||||
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')
|
||||
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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue