Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
a88c31d0ea
commit
7e064974b9
|
@ -138,6 +138,10 @@ class HastToProseMirrorConverterState {
|
|||
return this.stack[this.stack.length - 1];
|
||||
}
|
||||
|
||||
get topNode() {
|
||||
return this.findInStack((item) => item.type === 'node');
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if the node stack is empty
|
||||
*/
|
||||
|
@ -179,7 +183,7 @@ class HastToProseMirrorConverterState {
|
|||
*/
|
||||
addText(schema, text) {
|
||||
if (!text) return;
|
||||
const nodes = this.top.content;
|
||||
const nodes = this.topNode?.content;
|
||||
const last = nodes[nodes.length - 1];
|
||||
const node = schema.text(text, this.marks);
|
||||
const merged = maybeMerge(last, node);
|
||||
|
@ -189,57 +193,92 @@ class HastToProseMirrorConverterState {
|
|||
} else {
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
this.closeMarks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a mark to the set of marks stored temporarily
|
||||
* until addText is called.
|
||||
* @param {*} markType
|
||||
* @param {*} attrs
|
||||
* until an inline node is created.
|
||||
* @param {https://prosemirror.net/docs/ref/#model.MarkType} schemaType Mark schema type
|
||||
* @param {https://github.com/syntax-tree/hast#nodes} hastNode AST node that the mark is based on
|
||||
* @param {Object} attrs Mark attributes
|
||||
* @param {Object} factorySpec Specifications on how th mark should be created
|
||||
*/
|
||||
openMark(markType, attrs) {
|
||||
this.marks = markType.create(attrs).addToSet(this.marks);
|
||||
openMark(schemaType, hastNode, attrs, factorySpec) {
|
||||
const mark = schemaType.create(attrs);
|
||||
this.stack.push({
|
||||
type: 'mark',
|
||||
mark,
|
||||
attrs,
|
||||
hastNode,
|
||||
factorySpec,
|
||||
});
|
||||
|
||||
this.marks = mark.addToSet(this.marks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties the temporary Mark set.
|
||||
* Removes a mark from the list of active marks that
|
||||
* are applied to inline nodes.
|
||||
*/
|
||||
closeMarks() {
|
||||
this.marks = Mark.none;
|
||||
closeMark() {
|
||||
const { mark } = this.stack.pop();
|
||||
|
||||
this.marks = mark.removeFromSet(this.marks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a node to the stack data structure.
|
||||
*
|
||||
* @param {Schema.NodeType} type ProseMirror Schema for the node
|
||||
* @param {HastNode} hastNode Hast node from which the ProseMirror node will be created
|
||||
* @param {https://prosemirror.net/docs/ref/#model.NodeType} schemaType ProseMirror Schema for the node
|
||||
* @param {https://github.com/syntax-tree/hast#nodes} hastNode Hast node from which the ProseMirror node will be created
|
||||
* @param {*} attrs Node’s attributes
|
||||
* @param {*} factorySpec The factory spec used to create the node factory
|
||||
*/
|
||||
openNode(type, hastNode, attrs, factorySpec) {
|
||||
this.stack.push({ type, attrs, content: [], hastNode, factorySpec });
|
||||
openNode(schemaType, hastNode, attrs, factorySpec) {
|
||||
this.stack.push({
|
||||
type: 'node',
|
||||
schemaType,
|
||||
attrs,
|
||||
content: [],
|
||||
hastNode,
|
||||
factorySpec,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the top ProseMirror node from the
|
||||
* conversion stack and adds the node to the
|
||||
* previous element.
|
||||
* @returns
|
||||
*/
|
||||
closeNode() {
|
||||
const { type, attrs, content } = this.stack.pop();
|
||||
const node = type.createAndFill(attrs, content);
|
||||
const { schemaType, attrs, content, factorySpec } = this.stack.pop();
|
||||
const node =
|
||||
factorySpec.type === 'inline' && this.marks.length
|
||||
? schemaType.createAndFill(attrs, content, this.marks)
|
||||
: schemaType.createAndFill(attrs, content);
|
||||
|
||||
if (!node) return null;
|
||||
if (!node) {
|
||||
/*
|
||||
When the node returned by `createAndFill` is null is because the `content` passed as a parameter
|
||||
doesn’t conform with the document schema. We are handling the most likely scenario here that happens
|
||||
when a paragraph is inside another paragraph.
|
||||
|
||||
if (this.marks.length) {
|
||||
this.marks = Mark.none;
|
||||
This scenario happens when the converter encounters a mark wrapping one or more paragraphs.
|
||||
In this case, the converter will wrap the mark in a paragraph as well because ProseMirror does
|
||||
not allow marks wrapping block nodes or being direct children of certain nodes like the root nodes
|
||||
or list items.
|
||||
*/
|
||||
if (
|
||||
schemaType.name === 'paragraph' &&
|
||||
content.some((child) => child.type.name === 'paragraph')
|
||||
) {
|
||||
this.topNode.content.push(...content);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this.empty) {
|
||||
this.top.content.push(node);
|
||||
this.topNode.content.push(node);
|
||||
}
|
||||
|
||||
return node;
|
||||
|
@ -247,9 +286,27 @@ class HastToProseMirrorConverterState {
|
|||
|
||||
closeUntil(hastNode) {
|
||||
while (hastNode !== this.top?.hastNode) {
|
||||
this.closeNode();
|
||||
if (this.top.type === 'node') {
|
||||
this.closeNode();
|
||||
} else {
|
||||
this.closeMark();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildDoc() {
|
||||
let doc;
|
||||
|
||||
do {
|
||||
if (this.top.type === 'node') {
|
||||
doc = this.closeNode();
|
||||
} else {
|
||||
this.closeMark();
|
||||
}
|
||||
} while (!this.empty);
|
||||
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,7 +333,7 @@ const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, source)
|
|||
},
|
||||
text: {
|
||||
selector: 'text',
|
||||
handle: (state, hastNode) => {
|
||||
handle: (state, hastNode, parent) => {
|
||||
const found = state.findInStack((node) => isFunction(node.factorySpec.processText));
|
||||
const { value: text } = hastNode;
|
||||
|
||||
|
@ -284,6 +341,7 @@ const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, source)
|
|||
return;
|
||||
}
|
||||
|
||||
state.closeUntil(parent);
|
||||
state.addText(schema, found ? found.factorySpec.processText(text) : text);
|
||||
},
|
||||
},
|
||||
|
@ -320,11 +378,7 @@ const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, source)
|
|||
} else if (factory.type === 'mark') {
|
||||
const markType = schema.marks[proseMirrorName];
|
||||
factory.handle = (state, hastNode, parent) => {
|
||||
state.openMark(markType, getAttrs(factory, hastNode, parent, source));
|
||||
|
||||
if (factory.inlineContent) {
|
||||
state.addText(schema, hastNode.value);
|
||||
}
|
||||
state.openMark(markType, hastNode, getAttrs(factory, hastNode, parent, source), factory);
|
||||
};
|
||||
} else if (factory.type === 'ignore') {
|
||||
factory.handle = noop;
|
||||
|
@ -357,7 +411,7 @@ const findParent = (ancestors, parent) => {
|
|||
return ancestors[ancestors.length - 1];
|
||||
};
|
||||
|
||||
const calcTextNodePosition = (textNode) => {
|
||||
const resolveNodePosition = (textNode) => {
|
||||
const { position, value, type } = textNode;
|
||||
|
||||
if (type !== 'text' || (!position.start && !position.end) || (position.start && position.end)) {
|
||||
|
@ -418,7 +472,7 @@ const wrapInlineElements = (nodes, wrappableTags) =>
|
|||
const wrapper = {
|
||||
type: 'element',
|
||||
tagName: 'p',
|
||||
position: calcTextNodePosition(child),
|
||||
position: resolveNodePosition(child),
|
||||
children: [child],
|
||||
properties: { wrapper: true },
|
||||
};
|
||||
|
@ -580,11 +634,5 @@ export const createProseMirrorDocFromMdastTree = ({
|
|||
return factory.skipChildren === true ? SKIP : true;
|
||||
});
|
||||
|
||||
let doc;
|
||||
|
||||
do {
|
||||
doc = state.closeNode();
|
||||
} while (!state.empty);
|
||||
|
||||
return doc;
|
||||
return state.buildDoc();
|
||||
};
|
||||
|
|
|
@ -115,34 +115,6 @@ function deferredInitialisation() {
|
|||
);
|
||||
}
|
||||
|
||||
const searchInputBox = document.querySelector('#search');
|
||||
if (searchInputBox) {
|
||||
searchInputBox.addEventListener(
|
||||
'focus',
|
||||
() => {
|
||||
if (gon.features?.newHeaderSearch) {
|
||||
import(/* webpackChunkName: 'globalSearch' */ '~/header_search')
|
||||
.then(async ({ initHeaderSearchApp }) => {
|
||||
// In case the user started searching before we bootstrapped, let's pass the search along.
|
||||
const initialSearchValue = searchInputBox.value;
|
||||
await initHeaderSearchApp(initialSearchValue);
|
||||
// this is new #search input element. We need to re-find it.
|
||||
document.querySelector('#search').focus();
|
||||
})
|
||||
.catch(() => {});
|
||||
} else {
|
||||
import(/* webpackChunkName: 'globalSearch' */ './search_autocomplete')
|
||||
.then(({ default: initSearchAutocomplete }) => {
|
||||
const searchDropdown = initSearchAutocomplete();
|
||||
searchDropdown.onSearchInputFocus();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
|
||||
addSelectOnFocusBehaviour('.js-select-on-focus');
|
||||
|
||||
const glTooltipDelay = localStorage.getItem('gl-tooltip-delay');
|
||||
|
@ -169,6 +141,36 @@ function deferredInitialisation() {
|
|||
}
|
||||
}
|
||||
|
||||
// loading this inside requestIdleCallback is causing issues
|
||||
// see https://gitlab.com/gitlab-org/gitlab/-/issues/365746
|
||||
const searchInputBox = document.querySelector('#search');
|
||||
if (searchInputBox) {
|
||||
searchInputBox.addEventListener(
|
||||
'focus',
|
||||
() => {
|
||||
if (gon.features?.newHeaderSearch) {
|
||||
import(/* webpackChunkName: 'globalSearch' */ '~/header_search')
|
||||
.then(async ({ initHeaderSearchApp }) => {
|
||||
// In case the user started searching before we bootstrapped, let's pass the search along.
|
||||
const initialSearchValue = searchInputBox.value;
|
||||
await initHeaderSearchApp(initialSearchValue);
|
||||
// this is new #search input element. We need to re-find it.
|
||||
document.querySelector('#search').focus();
|
||||
})
|
||||
.catch(() => {});
|
||||
} else {
|
||||
import(/* webpackChunkName: 'globalSearch' */ './search_autocomplete')
|
||||
.then(({ default: initSearchAutocomplete }) => {
|
||||
const searchDropdown = initSearchAutocomplete();
|
||||
searchDropdown.onSearchInputFocus();
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
|
||||
const $body = $('body');
|
||||
const $document = $(document);
|
||||
const bootstrapBreakpoint = bp.getBreakpointSize();
|
||||
|
|
|
@ -115,7 +115,7 @@ $monokai-gh: #75715e;
|
|||
@include hljs-override('section', $monokai-gh);
|
||||
@include hljs-override('bullet', $monokai-n);
|
||||
@include hljs-override('subst', $monokai-p);
|
||||
@include hljs-override('symbol', $monokai-ni);
|
||||
@include hljs-override('symbol', $monokai-ss);
|
||||
|
||||
// Line numbers
|
||||
.file-line-num {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
@import '../white_base';
|
||||
|
||||
@include conflict-colors('white');
|
||||
@include hljs-override('symbol', $white-ss);
|
||||
}
|
||||
|
||||
:root {
|
||||
|
|
|
@ -27,6 +27,10 @@ module Packages
|
|||
# fixed cadence of 12 hours
|
||||
self.next_run_at = Time.zone.now + 12.hours
|
||||
end
|
||||
|
||||
def keep_n_duplicated_package_files_disabled?
|
||||
keep_n_duplicated_package_files == 'all'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,6 @@ module AlertManagement
|
|||
@alert = alert
|
||||
@param_errors = []
|
||||
@status = params.delete(:status)
|
||||
@status_change_reason = params.delete(:status_change_reason)
|
||||
|
||||
super(project: alert.project, current_user: current_user, params: params)
|
||||
end
|
||||
|
@ -37,7 +36,7 @@ module AlertManagement
|
|||
|
||||
private
|
||||
|
||||
attr_reader :alert, :param_errors, :status, :status_change_reason
|
||||
attr_reader :alert, :param_errors, :status
|
||||
|
||||
def allowed?
|
||||
current_user&.can?(:update_alert_management_alert, alert)
|
||||
|
@ -130,37 +129,16 @@ module AlertManagement
|
|||
def handle_status_change
|
||||
add_status_change_system_note
|
||||
resolve_todos if alert.resolved?
|
||||
sync_to_incident if should_sync_to_incident?
|
||||
end
|
||||
|
||||
def add_status_change_system_note
|
||||
SystemNoteService.change_alert_status(alert, current_user, status_change_reason)
|
||||
SystemNoteService.change_alert_status(alert, current_user)
|
||||
end
|
||||
|
||||
def resolve_todos
|
||||
todo_service.resolve_todos_for_target(alert, current_user)
|
||||
end
|
||||
|
||||
def sync_to_incident
|
||||
::Issues::UpdateService.new(
|
||||
project: project,
|
||||
current_user: current_user,
|
||||
params: {
|
||||
escalation_status: {
|
||||
status: status,
|
||||
status_change_reason: " by changing the status of #{alert.to_reference(project)}"
|
||||
}
|
||||
}
|
||||
).execute(alert.issue)
|
||||
end
|
||||
|
||||
def should_sync_to_incident?
|
||||
alert.issue &&
|
||||
alert.issue.supports_escalation? &&
|
||||
alert.issue.escalation_status &&
|
||||
alert.issue.escalation_status.status != alert.status
|
||||
end
|
||||
|
||||
def filter_duplicate
|
||||
# Only need to check if changing to a not-resolved status
|
||||
return if params[:status_event].blank? || params[:status_event] == :resolve
|
||||
|
|
|
@ -6,7 +6,6 @@ module IncidentManagement
|
|||
def initialize(issuable, current_user, **params)
|
||||
@issuable = issuable
|
||||
@escalation_status = issuable.escalation_status
|
||||
@alert = issuable.alert_management_alert
|
||||
|
||||
super(project: issuable.project, current_user: current_user, params: params)
|
||||
end
|
||||
|
@ -19,26 +18,13 @@ module IncidentManagement
|
|||
|
||||
private
|
||||
|
||||
attr_reader :issuable, :escalation_status, :alert
|
||||
attr_reader :issuable, :escalation_status
|
||||
|
||||
def after_update
|
||||
sync_status_to_alert
|
||||
add_status_system_note
|
||||
add_timeline_event
|
||||
end
|
||||
|
||||
def sync_status_to_alert
|
||||
return unless alert
|
||||
return if alert.status == escalation_status.status
|
||||
|
||||
::AlertManagement::Alerts::UpdateService.new(
|
||||
alert,
|
||||
current_user,
|
||||
status: escalation_status.status_name,
|
||||
status_change_reason: " by changing the incident status of #{issuable.to_reference(project)}"
|
||||
).execute
|
||||
end
|
||||
|
||||
def add_status_system_note
|
||||
return unless escalation_status.status_previously_changed?
|
||||
|
||||
|
|
|
@ -5,30 +5,17 @@ module IncidentManagement
|
|||
class BuildService < ::BaseProjectService
|
||||
def initialize(issue)
|
||||
@issue = issue
|
||||
@alert = issue.alert_management_alert
|
||||
|
||||
super(project: issue.project)
|
||||
end
|
||||
|
||||
def execute
|
||||
return issue.escalation_status if issue.escalation_status
|
||||
|
||||
issue.build_incident_management_issuable_escalation_status(alert_params)
|
||||
issue.escalation_status || issue.build_incident_management_issuable_escalation_status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :issue, :alert
|
||||
|
||||
def alert_params
|
||||
return {} unless alert
|
||||
|
||||
{
|
||||
status_event: alert.status_event_for(alert.status_name)
|
||||
}
|
||||
end
|
||||
attr_reader :issue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
IncidentManagement::IssuableEscalationStatuses::BuildService.prepend_mod
|
||||
|
|
|
@ -5,7 +5,7 @@ module IncidentManagement
|
|||
class PrepareUpdateService < ::BaseProjectService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
SUPPORTED_PARAMS = %i[status status_change_reason].freeze
|
||||
SUPPORTED_PARAMS = %i[status].freeze
|
||||
|
||||
def initialize(issuable, current_user, params)
|
||||
@issuable = issuable
|
||||
|
|
|
@ -162,8 +162,6 @@ class IssuableBaseService < ::BaseProjectService
|
|||
|
||||
return unless result.success? && result[:escalation_status].present?
|
||||
|
||||
@escalation_status_change_reason = result[:escalation_status].delete(:status_change_reason)
|
||||
|
||||
params[:incident_management_issuable_escalation_status_attributes] = result[:escalation_status]
|
||||
end
|
||||
|
||||
|
|
|
@ -199,8 +199,7 @@ module Issues
|
|||
|
||||
::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.new(
|
||||
issue,
|
||||
current_user,
|
||||
status_change_reason: @escalation_status_change_reason # Defined in IssuableBaseService before save
|
||||
current_user
|
||||
).execute
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Packages
|
||||
module Cleanup
|
||||
class ExecutePolicyService
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
MAX_EXECUTION_TIME = 250.seconds
|
||||
|
||||
DUPLICATED_FILES_BATCH_SIZE = 10_000
|
||||
MARK_PACKAGE_FILES_FOR_DESTRUCTION_SERVICE_BATCH_SIZE = 200
|
||||
|
||||
def initialize(policy)
|
||||
@policy = policy
|
||||
@counts = {
|
||||
marked_package_files_total_count: 0,
|
||||
unique_package_id_and_file_name_total_count: 0
|
||||
}
|
||||
end
|
||||
|
||||
def execute
|
||||
cleanup_duplicated_files
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cleanup_duplicated_files
|
||||
return if @policy.keep_n_duplicated_package_files_disabled?
|
||||
|
||||
result = installable_package_files.each_batch(of: DUPLICATED_FILES_BATCH_SIZE) do |package_files|
|
||||
break :timeout if cleanup_duplicated_files_on(package_files) == :timeout
|
||||
end
|
||||
|
||||
response_success(timeout: result == :timeout)
|
||||
end
|
||||
|
||||
def cleanup_duplicated_files_on(package_files)
|
||||
unique_package_id_and_file_name_from(package_files).each do |package_id, file_name|
|
||||
result = remove_duplicated_files_for(package_id: package_id, file_name: file_name)
|
||||
@counts[:marked_package_files_total_count] += result.payload[:marked_package_files_count]
|
||||
@counts[:unique_package_id_and_file_name_total_count] += 1
|
||||
|
||||
break :timeout unless result.success?
|
||||
end
|
||||
end
|
||||
|
||||
def unique_package_id_and_file_name_from(package_files)
|
||||
# This is a highly custom query for this service, that's why it's not in the model.
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
package_files.group(:package_id, :file_name)
|
||||
.having("COUNT(*) > #{@policy.keep_n_duplicated_package_files}")
|
||||
.pluck(:package_id, :file_name)
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
end
|
||||
|
||||
def remove_duplicated_files_for(package_id:, file_name:)
|
||||
base = ::Packages::PackageFile.for_package_ids(package_id)
|
||||
.installable
|
||||
.with_file_name(file_name)
|
||||
ids_to_keep = base.recent
|
||||
.limit(@policy.keep_n_duplicated_package_files)
|
||||
.pluck_primary_key
|
||||
|
||||
duplicated_package_files = base.id_not_in(ids_to_keep)
|
||||
::Packages::MarkPackageFilesForDestructionService.new(duplicated_package_files)
|
||||
.execute(batch_deadline: batch_deadline, batch_size: MARK_PACKAGE_FILES_FOR_DESTRUCTION_SERVICE_BATCH_SIZE)
|
||||
end
|
||||
|
||||
def project
|
||||
@policy.project
|
||||
end
|
||||
|
||||
def installable_package_files
|
||||
::Packages::PackageFile.installable
|
||||
.for_package_ids(
|
||||
::Packages::Package.installable
|
||||
.for_projects(project.id)
|
||||
)
|
||||
end
|
||||
|
||||
def batch_deadline
|
||||
strong_memoize(:batch_deadline) do
|
||||
MAX_EXECUTION_TIME.from_now
|
||||
end
|
||||
end
|
||||
|
||||
def response_success(timeout:)
|
||||
ServiceResponse.success(
|
||||
message: "Packages cleanup policy executed for project #{project.id}",
|
||||
payload: {
|
||||
timeout: timeout,
|
||||
counts: @counts
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -9,18 +9,41 @@ module Packages
|
|||
@package_files = package_files
|
||||
end
|
||||
|
||||
def execute
|
||||
@package_files.each_batch(of: BATCH_SIZE) do |batched_package_files|
|
||||
batched_package_files.update_all(status: :pending_destruction)
|
||||
def execute(batch_deadline: nil, batch_size: BATCH_SIZE)
|
||||
timeout = false
|
||||
updates_count = 0
|
||||
min_batch_size = [batch_size, BATCH_SIZE].min
|
||||
|
||||
@package_files.each_batch(of: min_batch_size) do |batched_package_files|
|
||||
if batch_deadline && Time.zone.now > batch_deadline
|
||||
timeout = true
|
||||
break
|
||||
end
|
||||
|
||||
updates_count += batched_package_files.update_all(status: :pending_destruction)
|
||||
end
|
||||
|
||||
service_response_success('Package files are now pending destruction')
|
||||
payload = { marked_package_files_count: updates_count }
|
||||
|
||||
return response_error(payload) if timeout
|
||||
|
||||
response_success(payload)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def service_response_success(message)
|
||||
ServiceResponse.success(message: message)
|
||||
def response_success(payload)
|
||||
ServiceResponse.success(
|
||||
message: 'Package files are now pending destruction',
|
||||
payload: payload
|
||||
)
|
||||
end
|
||||
|
||||
def response_error(payload)
|
||||
ServiceResponse.error(
|
||||
message: 'Timeout while marking package files as pending destruction',
|
||||
payload: payload
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/367025
|
|||
milestone: '15.2'
|
||||
type: development
|
||||
group: group::optimize
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCompositeIndexForProtectedEnvironments < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
# skips the `required_` part because index limit is 63 characters
|
||||
INDEX_NAME = 'index_protected_environments_on_approval_count_and_created_at'
|
||||
|
||||
def up
|
||||
add_concurrent_index :protected_environments, %i[required_approval_count created_at], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index :protected_environments, %i[required_approval_count created_at], name: INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCompositeIndexForProtectedEnvironmentApprovalRules < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
# uses `pe_` instead of `protected_environment_` because index limit is 63 characters
|
||||
INDEX_NAME = 'index_pe_approval_rules_on_required_approvals_and_created_at'
|
||||
|
||||
def up
|
||||
add_concurrent_index :protected_environment_approval_rules, %i[required_approvals created_at], name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index :protected_environment_approval_rules, %i[required_approvals created_at], name: INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexOnInstallablePackageFiles < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
INDEX_NAME = 'idx_pkgs_installable_package_files_on_package_id_id_file_name'
|
||||
# See https://gitlab.com/gitlab-org/gitlab/-/blob/e3ed2c1f65df2e137fc714485d7d42264a137968/app/models/packages/package_file.rb#L16
|
||||
DEFAULT_STATUS = 0
|
||||
|
||||
def up
|
||||
add_concurrent_index :packages_package_files,
|
||||
[:package_id, :id, :file_name],
|
||||
where: "(status = #{DEFAULT_STATUS})",
|
||||
name: INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :packages_package_files, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ReplacePackagesIndexOnProjectIdAndStatus < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
NEW_INDEX_NAME = 'index_packages_packages_on_project_id_and_status_and_id'
|
||||
OLD_INDEX_NAME = 'index_packages_packages_on_project_id_and_status'
|
||||
|
||||
def up
|
||||
add_concurrent_index :packages_packages,
|
||||
[:project_id, :status, :id],
|
||||
name: NEW_INDEX_NAME
|
||||
remove_concurrent_index_by_name :packages_packages, OLD_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :packages_packages,
|
||||
[:project_id, :status],
|
||||
name: OLD_INDEX_NAME
|
||||
remove_concurrent_index_by_name :packages_packages, NEW_INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UnsetEscalationPoliciesForAlertIncidents < Gitlab::Database::Migration[2.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
class EscalationStatus < MigrationRecord
|
||||
include EachBatch
|
||||
|
||||
self.table_name = 'incident_management_issuable_escalation_statuses'
|
||||
|
||||
scope :having_alert_policy, -> do
|
||||
joins(
|
||||
'INNER JOIN alert_management_alerts ' \
|
||||
'ON alert_management_alerts.issue_id ' \
|
||||
'= incident_management_issuable_escalation_statuses.issue_id'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def up
|
||||
EscalationStatus.each_batch do |escalation_statuses|
|
||||
escalation_statuses
|
||||
.where.not(policy_id: nil)
|
||||
.having_alert_policy
|
||||
.update_all(policy_id: nil, escalations_started_at: nil)
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
# no-op
|
||||
#
|
||||
# We cannot retrieve the exact nullified values. We could
|
||||
# approximately guess what the values are via the alert's
|
||||
# escalation policy. However, that may not be accurate
|
||||
# in all cases, as an alert's escalation policy is implictly
|
||||
# inferred from the project rather than explicit, like an incident.
|
||||
# So we won't backfill potentially incorrect data.
|
||||
#
|
||||
# This data is functionally safe to delete, as the relevant
|
||||
# fields are read-only, and exclusively informational.
|
||||
#
|
||||
# Re-running the migration will have no effect.
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
668404076e9cfc91817b8ae3ec995a69ec0db283153bbe497a81eb83c2188ceb
|
|
@ -0,0 +1 @@
|
|||
547fc0071177395133497cbcec9a9d9ed058fe74f632f5e84d9a6416047503f2
|
|
@ -0,0 +1 @@
|
|||
9414b08c3eacadffd8759739da163eb378776d3ecdb06dab7c66e259ff1bed29
|
|
@ -0,0 +1 @@
|
|||
b93ab540270a4b743c12fe5d1d6963cfeb29ee3b0a1e4e012cd4b3d1b3a08cde
|
|
@ -0,0 +1 @@
|
|||
7929540cf382f282f75f2f9c9dd6196d426ed1edb1f6744da1f0a627e7fb0cfc
|
|
@ -27116,6 +27116,8 @@ CREATE INDEX idx_pkgs_debian_project_distribution_keys_on_distribution_id ON pac
|
|||
|
||||
CREATE UNIQUE INDEX idx_pkgs_dep_links_on_pkg_id_dependency_id_dependency_type ON packages_dependency_links USING btree (package_id, dependency_id, dependency_type);
|
||||
|
||||
CREATE INDEX idx_pkgs_installable_package_files_on_package_id_id_file_name ON packages_package_files USING btree (package_id, id, file_name) WHERE (status = 0);
|
||||
|
||||
CREATE INDEX idx_proj_feat_usg_on_jira_dvcs_cloud_last_sync_at_and_proj_id ON project_feature_usages USING btree (jira_dvcs_cloud_last_sync_at, project_id) WHERE (jira_dvcs_cloud_last_sync_at IS NOT NULL);
|
||||
|
||||
CREATE INDEX idx_proj_feat_usg_on_jira_dvcs_server_last_sync_at_and_proj_id ON project_feature_usages USING btree (jira_dvcs_server_last_sync_at, project_id) WHERE (jira_dvcs_server_last_sync_at IS NOT NULL);
|
||||
|
@ -29096,7 +29098,7 @@ CREATE INDEX index_packages_packages_on_project_id_and_created_at ON packages_pa
|
|||
|
||||
CREATE INDEX index_packages_packages_on_project_id_and_package_type ON packages_packages USING btree (project_id, package_type);
|
||||
|
||||
CREATE INDEX index_packages_packages_on_project_id_and_status ON packages_packages USING btree (project_id, status);
|
||||
CREATE INDEX index_packages_packages_on_project_id_and_status_and_id ON packages_packages USING btree (project_id, status, id);
|
||||
|
||||
CREATE INDEX index_packages_packages_on_project_id_and_version ON packages_packages USING btree (project_id, version);
|
||||
|
||||
|
@ -29162,6 +29164,8 @@ CREATE INDEX index_path_locks_on_project_id ON path_locks USING btree (project_i
|
|||
|
||||
CREATE INDEX index_path_locks_on_user_id ON path_locks USING btree (user_id);
|
||||
|
||||
CREATE INDEX index_pe_approval_rules_on_required_approvals_and_created_at ON protected_environment_approval_rules USING btree (required_approvals, created_at);
|
||||
|
||||
CREATE UNIQUE INDEX index_personal_access_tokens_on_token_digest ON personal_access_tokens USING btree (token_digest);
|
||||
|
||||
CREATE INDEX index_personal_access_tokens_on_user_id ON personal_access_tokens USING btree (user_id);
|
||||
|
@ -29426,6 +29430,8 @@ CREATE INDEX index_protected_environment_deploy_access_levels_on_group_id ON pro
|
|||
|
||||
CREATE INDEX index_protected_environment_deploy_access_levels_on_user_id ON protected_environment_deploy_access_levels USING btree (user_id);
|
||||
|
||||
CREATE INDEX index_protected_environments_on_approval_count_and_created_at ON protected_environments USING btree (required_approval_count, created_at);
|
||||
|
||||
CREATE UNIQUE INDEX index_protected_environments_on_group_id_and_name ON protected_environments USING btree (group_id, name) WHERE (group_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_protected_environments_on_project_id ON protected_environments USING btree (project_id);
|
||||
|
|
|
@ -4,9 +4,9 @@ group: Runner
|
|||
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||
---
|
||||
|
||||
# SaaS runners on macOS (Limited Availability) **(PREMIUM SAAS)**
|
||||
# SaaS runners on macOS (Beta) **(PREMIUM SAAS)**
|
||||
|
||||
SaaS runners on macOS are in [Limited Availability](../../../policy/alpha-beta-support.md#limited-availability-la) for approved open source programs and customers in Premium and Ultimate plans.
|
||||
SaaS runners on macOS are in [Beta]](../../../policy/alpha-beta-support.md#beta-features) for approved open source programs and customers in Premium and Ultimate plans.
|
||||
|
||||
SaaS runners on macOS provide an on-demand macOS build environment integrated with
|
||||
GitLab SaaS [CI/CD](../../../ci/index.md).
|
||||
|
@ -22,11 +22,11 @@ Jobs handled by macOS shared runners on GitLab.com **time out after 2 hours**, r
|
|||
|
||||
## Access request process
|
||||
|
||||
While in limited availability, to run CI jobs on the macOS runners, GitLab SaaS customer namespaces must be explicitly added to the macOS `allow-list`. Customers who participated in the beta have already been added.
|
||||
While in beta, to run CI jobs on the macOS runners, GitLab SaaS customer namespaces must be explicitly added to the macOS `allow-list`.
|
||||
|
||||
After you have been added, you can use the macOS runners for any projects in your namespace.
|
||||
|
||||
To request access, open a [limited availability access request](https://gitlab.com/gitlab-com/runner-saas-macos-limited-availability/-/issues/new).
|
||||
To request access, open an [access request](https://gitlab.com/gitlab-com/runner-saas-macos-limited-availability/-/issues/new).
|
||||
The expected turnaround for activation is two business days.
|
||||
|
||||
## Quickstart
|
||||
|
|
|
@ -62,6 +62,7 @@ are very appreciative of the work done by translators and proofreaders!
|
|||
- Michael Hahnle - [GitLab](https://gitlab.com/mhah), [Crowdin](https://crowdin.com/profile/mhah)
|
||||
- Katrin Leinweber - [GitLab](https://gitlab.com/katrinleinweber), [Crowdin](https://crowdin.com/profile/katrinleinweber)
|
||||
- Justman10000 - [GitLab](https://gitlab.com/Justman10000), [Crowdin](https://crowdin.com/profile/Justman10000)
|
||||
- Vladislav Wanner - [GitLab](https://gitlab.com/RumBugen), [Crowdin](https://crowdin.com/profile/RumBugen)
|
||||
- Greek
|
||||
- Proofreaders needed.
|
||||
- Hebrew
|
||||
|
|
|
@ -110,12 +110,12 @@ sequenceDiagram
|
|||
DebianProjectPackages->>+FindOrCreateIncomingService: Create "incoming" package
|
||||
DebianProjectPackages->>+CreatePackageFileService: Create "unknown" file
|
||||
Note over DebianProjectPackages: If `.changes` file
|
||||
DebianProjectPackages->>+ProcessChangesWorker:
|
||||
DebianProjectPackages->>+ProcessChangesWorker: Schedule worker to process the file
|
||||
DebianProjectPackages->>+Client: 202 Created
|
||||
ProcessChangesWorker->>+ProcessChangesService:
|
||||
ProcessChangesService->>+ExtractChangesMetadataService:
|
||||
ExtractChangesMetadataService->>+ExtractMetadataService:
|
||||
ExtractMetadataService->>+ParseDebian822Service:
|
||||
ProcessChangesWorker->>+ProcessChangesService: Start service
|
||||
ProcessChangesService->>+ExtractChangesMetadataService: Extract changesmetadata
|
||||
ExtractChangesMetadataService->>+ExtractMetadataService: Extract file metadata
|
||||
ExtractMetadataService->>+ParseDebian822Service: run `dpkg --field` to get control file
|
||||
ExtractMetadataService->>+ExtractDebMetadataService: If .deb or .udeb
|
||||
ExtractDebMetadataService->>+ParseDebian822Service: run `dpkg --field` to get control file
|
||||
ParseDebian822Service-->>-ExtractDebMetadataService: Parse String as Debian RFC822 control data format
|
||||
|
@ -125,8 +125,8 @@ sequenceDiagram
|
|||
ExtractMetadataService-->>-ExtractChangesMetadataService: Parse Metadata file
|
||||
ExtractChangesMetadataService-->>-ProcessChangesService: Return list of files and hashes from the .changes file
|
||||
loop process files listed in .changes
|
||||
ProcessChangesService->>+ExtractMetadataService:
|
||||
ExtractMetadataService->>+ParseDebian822Service:
|
||||
ProcessChangesService->>+ExtractMetadataService: Process file
|
||||
ExtractMetadataService->>+ParseDebian822Service: run `dpkg --field` to get control file
|
||||
ExtractMetadataService->>+ExtractDebMetadataService: If .deb or .udeb
|
||||
ExtractDebMetadataService->>+ParseDebian822Service: run `dpkg --field` to get control file
|
||||
ParseDebian822Service-->>-ExtractDebMetadataService: Parse String as Debian RFC822 control data format
|
||||
|
@ -135,8 +135,8 @@ sequenceDiagram
|
|||
ParseDebian822Service-->>-ExtractMetadataService: Parse String as Debian RFC822 control data format
|
||||
ExtractMetadataService-->>-ProcessChangesService: Use parsed metadata to update "unknown" (or known) file
|
||||
end
|
||||
ProcessChangesService->>+GenerateDistributionWorker:
|
||||
GenerateDistributionWorker->>+GenerateDistributionService:
|
||||
ProcessChangesService->>+GenerateDistributionWorker: Find distribution and start service
|
||||
GenerateDistributionWorker->>+GenerateDistributionService: Generate distribution
|
||||
GenerateDistributionService->>+GenerateDistributionService: generate component files based on new archs and updates from .changes
|
||||
GenerateDistributionService->>+GenerateDistributionKeyService: generate GPG key for distribution
|
||||
GenerateDistributionKeyService-->>-GenerateDistributionService: GPG key
|
||||
|
|
|
@ -168,8 +168,9 @@ by changing the status. Setting the status to:
|
|||
- **Acknowledged** limits on-call pages based on the project's [escalation policy](escalation_policies.md).
|
||||
- **Triggered** from **Resolved** restarts the alert escalating from the beginning.
|
||||
|
||||
For [alerts with an associated incident](alerts.md#create-an-incident-from-an-alert),
|
||||
updating the alert status also updates the incident status.
|
||||
In GitLab 15.1 and earlier, updating the status of an [alert with an associated incident](alerts.md#create-an-incident-from-an-alert)
|
||||
also updates the incident status. In [GitLab 15.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/356057),
|
||||
the incident status is independent and does not update when the alert status changes.
|
||||
|
||||
### Create an incident from an alert
|
||||
|
||||
|
|
|
@ -278,8 +278,9 @@ by changing the status. Setting the status to:
|
|||
- **Acknowledged** limits on-call pages based on the selected [escalation policy](#change-escalation-policy).
|
||||
- **Triggered** from **Resolved** restarts the incident escalating from the beginning.
|
||||
|
||||
For [incidents created from alerts](alerts.md#create-an-incident-from-an-alert),
|
||||
updating the incident status also updates the alert status.
|
||||
In GitLab 15.1 and earlier, updating the status of an [incident created from an alert](alerts.md#create-an-incident-from-an-alert)
|
||||
also updates the alert status. In [GitLab 15.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/356057),
|
||||
the alert status is independent and does not update when the incident status changes.
|
||||
|
||||
### Change escalation policy **(PREMIUM)**
|
||||
|
||||
|
@ -296,8 +297,9 @@ Selecting an escalation policy updates the incident status to **Triggered** and
|
|||
Deselecting an escalation policy halts escalation. Refer to the [incident status](#change-incident-status)
|
||||
to manage on-call paging once escalation has begun.
|
||||
|
||||
For [incidents created from alerts](alerts.md#create-an-incident-from-an-alert),
|
||||
the incident's escalation policy reflects the alert's escalation policy and cannot be changed.
|
||||
In GitLab 15.1 and earlier, the escalation policy for [incidents created from alerts](alerts.md#create-an-incident-from-an-alert)
|
||||
reflects the alert's escalation policy and cannot be changed. In [GitLab 15.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/356057),
|
||||
the incident escalation policy is independent and can be changed.
|
||||
|
||||
### Manage incidents from Slack
|
||||
|
||||
|
|
|
@ -62,4 +62,3 @@ the rule fires. You can respond to a page or stop incident escalations by
|
|||
[unsetting the incident's escalation policy](incidents.md#change-escalation-policy).
|
||||
|
||||
To avoid duplicate pages, [incidents created from alerts](alerts.md#create-an-incident-from-an-alert) do not support independent escalation.
|
||||
Instead, the status and escalation policy fields are synced between the alert and the incident.
|
||||
|
|
|
@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
|
||||
Exporting merge requests CSV enables you and your team to export all the data collected from merge requests into a comma-separated values (CSV) file, which stores tabular data in plain text.
|
||||
|
||||
To export merge requests to CSV, navigate to your **Merge requests** from the sidebar of a project and select **Export to CSV**.
|
||||
To export merge requests to CSV, navigate to your **Merge requests** from the sidebar of a project and select **Export as CSV**.
|
||||
|
||||
## CSV Output
|
||||
|
||||
|
|
|
@ -1351,8 +1351,8 @@
|
|||
<p data-sourcepos="1:1-2:3" dir="auto"><code> </code>
|
||||
aaa</p>
|
||||
wysiwyg: |-
|
||||
<p><code>
|
||||
aaa</code></p>
|
||||
<p>
|
||||
aaa</p>
|
||||
04_05__leaf_blocks__fenced_code_blocks__021:
|
||||
canonical: |
|
||||
<pre><code>aaa
|
||||
|
@ -1711,8 +1711,7 @@
|
|||
<p data-sourcepos="3:1-3:5"><em>foo</em></p>
|
||||
</del>
|
||||
wysiwyg: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
<p><em><s>foo</s></em></p>
|
||||
04_06__leaf_blocks__html_blocks__021:
|
||||
canonical: |
|
||||
<p><del><em>foo</em></del></p>
|
||||
|
@ -5381,7 +5380,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:11" dir="auto"><em>(<strong>foo</strong>)</em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>(</em><strong>foo</strong>)</p>
|
||||
<p><em>(</em><strong><em>foo</em></strong><em>)</em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__044:
|
||||
canonical: |
|
||||
<p><strong>Gomphocarpus (<em>Gomphocarpus physocarpus</em>, syn.
|
||||
|
@ -5390,15 +5389,15 @@
|
|||
<p data-sourcepos="1:1-2:25" dir="auto"><strong>Gomphocarpus (<em>Gomphocarpus physocarpus</em>, syn.
|
||||
<em>Asclepias physocarpa</em>)</strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>Gomphocarpus (</strong><em>Gomphocarpus physocarpus</em>, syn.
|
||||
<em>Asclepias physocarpa</em>)</p>
|
||||
<p><strong>Gomphocarpus (<em>Gomphocarpus physocarpus</em>, syn.
|
||||
<em>Asclepias physocarpa</em>)</strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__045:
|
||||
canonical: |
|
||||
<p><strong>foo "<em>bar</em>" foo</strong></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:19" dir="auto"><strong>foo "<em>bar</em>" foo</strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>foo "</strong><em>bar</em>" foo</p>
|
||||
<p><strong>foo "<em>bar</em>" foo</strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__046:
|
||||
canonical: |
|
||||
<p><strong>foo</strong>bar</p>
|
||||
|
@ -5426,7 +5425,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:11" dir="auto"><em>(<strong>foo</strong>)</em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>(</em><strong>foo</strong>)</p>
|
||||
<p><em>(</em><strong><em>foo</em></strong><em>)</em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__050:
|
||||
canonical: |
|
||||
<p>__foo__bar</p>
|
||||
|
@ -5461,7 +5460,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:17" dir="auto"><em>foo <a href="/url">bar</a></em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>foo </em><a target="_blank" rel="noopener noreferrer nofollow" href="/url">bar</a></p>
|
||||
<p><em>foo </em><a target="_blank" rel="noopener noreferrer nofollow" href="/url"><em>bar</em></a></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__055:
|
||||
canonical: |
|
||||
<p><em>foo
|
||||
|
@ -5478,7 +5477,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:17" dir="auto"><em>foo <strong>bar</strong> baz</em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>foo </em><strong>bar</strong> baz</p>
|
||||
<p><em>foo </em><strong><em>bar</em></strong><em> baz</em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__057:
|
||||
canonical: |
|
||||
<p><em>foo <em>bar</em> baz</em></p>
|
||||
|
@ -5506,14 +5505,14 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:17" dir="auto"><em>foo <strong>bar</strong> baz</em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>foo </em><strong>bar</strong> baz</p>
|
||||
<p><em>foo </em><strong><em>bar</em></strong><em> baz</em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__061:
|
||||
canonical: |
|
||||
<p><em>foo<strong>bar</strong>baz</em></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:15" dir="auto"><em>foo<strong>bar</strong>baz</em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>foo</em><strong>bar</strong>baz</p>
|
||||
<p><em>foo</em><strong><em>bar</em></strong><em>baz</em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__062:
|
||||
canonical: |
|
||||
<p><em>foo**bar</em></p>
|
||||
|
@ -5527,21 +5526,21 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:13" dir="auto"><em><strong>foo</strong> bar</em></p>
|
||||
wysiwyg: |-
|
||||
<p><strong><em>foo</em></strong> bar</p>
|
||||
<p><strong><em>foo</em></strong><em> bar</em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__064:
|
||||
canonical: |
|
||||
<p><em>foo <strong>bar</strong></em></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:13" dir="auto"><em>foo <strong>bar</strong></em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>foo </em><strong>bar</strong></p>
|
||||
<p><em>foo </em><strong><em>bar</em></strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__065:
|
||||
canonical: |
|
||||
<p><em>foo<strong>bar</strong></em></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:12" dir="auto"><em>foo<strong>bar</strong></em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>foo</em><strong>bar</strong></p>
|
||||
<p><em>foo</em><strong><em>bar</em></strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__066:
|
||||
canonical: |
|
||||
<p>foo<em><strong>bar</strong></em>baz</p>
|
||||
|
@ -5562,7 +5561,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:27" dir="auto"><em>foo <strong>bar <em>baz</em> bim</strong> bop</em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>foo </em><strong>bar </strong><em>baz</em> bim bop</p>
|
||||
<p><em>foo </em><strong><em>bar baz</em> bim</strong> bop</p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__069:
|
||||
canonical: |
|
||||
<p><em>foo <a href="/url"><em>bar</em></a></em></p>
|
||||
|
@ -5590,7 +5589,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:19" dir="auto"><strong>foo <a href="/url">bar</a></strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>foo </strong><a target="_blank" rel="noopener noreferrer nofollow" href="/url">bar</a></p>
|
||||
<p><strong>foo </strong><a target="_blank" rel="noopener noreferrer nofollow" href="/url"><strong>bar</strong></a></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__073:
|
||||
canonical: |
|
||||
<p><strong>foo
|
||||
|
@ -5607,7 +5606,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:17" dir="auto"><strong>foo <em>bar</em> baz</strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>foo </strong><em>bar</em> baz</p>
|
||||
<p><strong>foo <em>bar</em> baz</strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__075:
|
||||
canonical: |
|
||||
<p><strong>foo <strong>bar</strong> baz</strong></p>
|
||||
|
@ -5635,28 +5634,28 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:17" dir="auto"><strong>foo <em>bar</em> baz</strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>foo </strong><em>bar</em> baz</p>
|
||||
<p><strong>foo <em>bar</em> baz</strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__079:
|
||||
canonical: |
|
||||
<p><strong>foo<em>bar</em>baz</strong></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:15" dir="auto"><strong>foo<em>bar</em>baz</strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>foo</strong><em>bar</em>baz</p>
|
||||
<p><strong>foo<em>bar</em>baz</strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__080:
|
||||
canonical: |
|
||||
<p><strong><em>foo</em> bar</strong></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:13" dir="auto"><strong><em>foo</em> bar</strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong><em>foo</em></strong> bar</p>
|
||||
<p><strong><em>foo</em> bar</strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__081:
|
||||
canonical: |
|
||||
<p><strong>foo <em>bar</em></strong></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:13" dir="auto"><strong>foo <em>bar</em></strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>foo </strong><em>bar</em></p>
|
||||
<p><strong>foo <em>bar</em></strong></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__082:
|
||||
canonical: |
|
||||
<p><strong>foo <em>bar <strong>baz</strong>
|
||||
|
@ -5665,15 +5664,15 @@
|
|||
<p data-sourcepos="1:1-2:10" dir="auto"><strong>foo <em>bar <strong>baz</strong>
|
||||
bim</em> bop</strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>foo </strong><em>bar </em><strong>baz</strong>
|
||||
bim bop</p>
|
||||
<p><strong>foo <em>bar baz</em></strong><em>
|
||||
bim</em> bop</p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__083:
|
||||
canonical: |
|
||||
<p><strong>foo <a href="/url"><em>bar</em></a></strong></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:21" dir="auto"><strong>foo <a href="/url"><em>bar</em></a></strong></p>
|
||||
wysiwyg: |-
|
||||
<p><strong>foo </strong><a target="_blank" rel="noopener noreferrer nofollow" href="/url"><em>bar</em></a></p>
|
||||
<p><strong>foo </strong><a target="_blank" rel="noopener noreferrer nofollow" href="/url"><strong><em>bar</em></strong></a></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__084:
|
||||
canonical: |
|
||||
<p>__ is not an empty emphasis</p>
|
||||
|
@ -5932,7 +5931,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:26" dir="auto"><em>foo <strong>bar *baz bim</strong> bam</em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>foo </em><strong>bar *baz bim</strong> bam</p>
|
||||
<p><em>foo </em><strong><em>bar *baz bim</em></strong><em> bam</em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__121:
|
||||
canonical: |
|
||||
<p>**foo <strong>bar baz</strong></p>
|
||||
|
@ -5988,14 +5987,14 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:7" dir="auto"><em>a <code>*</code></em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>a </em><code>*</code></p>
|
||||
<p><em>a <code>*</code></em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__129:
|
||||
canonical: |
|
||||
<p><em>a <code>_</code></em></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:7" dir="auto"><em>a <code>_</code></em></p>
|
||||
wysiwyg: |-
|
||||
<p><em>a </em><code>_</code></p>
|
||||
<p><em>a <code>_</code></em></p>
|
||||
06_05__inlines__emphasis_and_strong_emphasis__130:
|
||||
canonical: |
|
||||
<p>**a<a href="http://foo.bar/?q=**">http://foo.bar/?q=**</a></p>
|
||||
|
@ -6267,15 +6266,14 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:30" dir="auto"><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link </a><em>foo </em><strong>bar</strong><code>#</code></p>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link <em>foo </em><strong><em>bar<code>#</code></em></strong></a></p>
|
||||
06_07__inlines__links__033:
|
||||
canonical: |
|
||||
<p><a href="/uri"><img src="moon.jpg" alt="moon" /></a></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:25" dir="auto"><a href="/uri"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="moon" decoding="async" class="lazy" data-src="moon.jpg"></a></p>
|
||||
wysiwyg: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri"><img src="moon.jpg" alt="moon"></a></p>
|
||||
06_07__inlines__links__034:
|
||||
canonical: |
|
||||
<p>[foo <a href="/uri">bar</a>](/uri)</p>
|
||||
|
@ -6289,7 +6287,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:37" dir="auto">[foo <em>[bar <a href="/uri">baz</a>](/uri)</em>](/uri)</p>
|
||||
wysiwyg: |-
|
||||
<p>[foo <em>[bar </em><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">baz</a>](/uri)](/uri)</p>
|
||||
<p>[foo <em>[bar </em><a target="_blank" rel="noopener noreferrer nofollow" href="/uri"><em>baz</em></a><em>](/uri)</em>](/uri)</p>
|
||||
06_07__inlines__links__036:
|
||||
canonical: |
|
||||
<p><img src="uri3" alt="[foo](uri2)" /></p>
|
||||
|
@ -6366,15 +6364,14 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:29" dir="auto"><a href="/uri">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link </a><em>foo </em><strong>bar</strong><code>#</code></p>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">link <em>foo </em><strong><em>bar<code>#</code></em></strong></a></p>
|
||||
06_07__inlines__links__047:
|
||||
canonical: |
|
||||
<p><a href="/uri"><img src="moon.jpg" alt="moon" /></a></p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:24" dir="auto"><a href="/uri"><img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" alt="moon" decoding="async" class="lazy" data-src="moon.jpg"></a></p>
|
||||
wysiwyg: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/uri"><img src="moon.jpg" alt="moon"></a></p>
|
||||
06_07__inlines__links__048:
|
||||
canonical: |
|
||||
<p>[foo <a href="/uri">bar</a>]<a href="/uri">ref</a></p>
|
||||
|
@ -6388,7 +6385,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:27" dir="auto">[foo <em>bar <a href="/uri">baz</a></em>]<a href="/uri">ref</a></p>
|
||||
wysiwyg: |-
|
||||
<p>[foo <em>bar </em><a target="_blank" rel="noopener noreferrer nofollow" href="/uri">baz</a>]<a target="_blank" rel="noopener noreferrer nofollow" href="/uri">ref</a></p>
|
||||
<p>[foo <em>bar </em><a target="_blank" rel="noopener noreferrer nofollow" href="/uri"><em>baz</em></a>]<a target="_blank" rel="noopener noreferrer nofollow" href="/uri">ref</a></p>
|
||||
06_07__inlines__links__050:
|
||||
canonical: |
|
||||
<p>*<a href="/uri">foo*</a></p>
|
||||
|
@ -6553,7 +6550,7 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:13" dir="auto"><a href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em></a> bar</p>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
06_07__inlines__links__071:
|
||||
canonical: |
|
||||
<p><a href="/url" title="title">Foo</a></p>
|
||||
|
@ -6584,14 +6581,14 @@
|
|||
static: |-
|
||||
<p data-sourcepos="1:1-1:11" dir="auto"><a href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
wysiwyg: |-
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em></a> bar</p>
|
||||
<p><a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a></p>
|
||||
06_07__inlines__links__075:
|
||||
canonical: |
|
||||
<p>[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:13" dir="auto">[<a href="/url" title="title"><em>foo</em> bar</a>]</p>
|
||||
wysiwyg: |-
|
||||
<p>[<a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em></a> bar]</p>
|
||||
<p>[<a target="_blank" rel="noopener noreferrer nofollow" href="/url" title="title"><em>foo</em> bar</a>]</p>
|
||||
06_07__inlines__links__076:
|
||||
canonical: |
|
||||
<p>[[bar <a href="/url">foo</a></p>
|
||||
|
@ -7301,8 +7298,8 @@
|
|||
<p data-sourcepos="1:1-2:4" dir="auto"><em>foo<br>
|
||||
bar</em></p>
|
||||
wysiwyg: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
<p><em>foo<br>
|
||||
bar</em></p>
|
||||
06_13__inlines__hard_line_breaks__007:
|
||||
canonical: |
|
||||
<p><em>foo<br />
|
||||
|
@ -7311,8 +7308,8 @@
|
|||
<p data-sourcepos="1:1-2:4" dir="auto"><em>foo<br>
|
||||
bar</em></p>
|
||||
wysiwyg: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
<p><em>foo<br>
|
||||
bar</em></p>
|
||||
06_13__inlines__hard_line_breaks__008:
|
||||
canonical: |
|
||||
<p><code>code span</code></p>
|
||||
|
@ -7415,11 +7412,11 @@
|
|||
07_01__gitlab_specific_markdown__footnotes__001:
|
||||
canonical: ""
|
||||
static: |-
|
||||
<p data-sourcepos="1:1-1:27" dir="auto">footnote reference tag <sup class="footnote-ref"><a href="#fn-1-794" id="fnref-1-794" data-footnote-ref>1</a></sup></p>
|
||||
<p data-sourcepos="1:1-1:27" dir="auto">footnote reference tag <sup class="footnote-ref"><a href="#fn-1-4719" id="fnref-1-4719" data-footnote-ref>1</a></sup></p>
|
||||
<section data-footnotes class="footnotes">
|
||||
<ol>
|
||||
<li id="fn-1-794">
|
||||
<p data-sourcepos="3:7-3:19">footnote text <a href="#fnref-1-794" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
|
||||
<li id="fn-1-4719">
|
||||
<p data-sourcepos="3:7-3:19">footnote text <a href="#fnref-1-4719" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
|
|
@ -2667,11 +2667,6 @@
|
|||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "code"
|
||||
}
|
||||
],
|
||||
"text": "\naaa"
|
||||
}
|
||||
]
|
||||
|
@ -3259,8 +3254,28 @@
|
|||
]
|
||||
}
|
||||
04_06__leaf_blocks__html_blocks__020: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
},
|
||||
{
|
||||
"type": "strike"
|
||||
}
|
||||
],
|
||||
"text": "foo"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
04_06__leaf_blocks__html_blocks__021: |-
|
||||
{
|
||||
"type": "doc",
|
||||
|
@ -11691,12 +11706,20 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "foo"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": ")"
|
||||
}
|
||||
]
|
||||
|
@ -11722,6 +11745,9 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -11730,11 +11756,19 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": ", syn.\n"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -11743,6 +11777,11 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": ")"
|
||||
}
|
||||
]
|
||||
|
@ -11768,6 +11807,9 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -11776,6 +11818,11 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "\" foo"
|
||||
}
|
||||
]
|
||||
|
@ -11857,12 +11904,20 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "foo"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": ")"
|
||||
}
|
||||
]
|
||||
|
@ -11971,6 +12026,9 @@
|
|||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
|
@ -12020,12 +12078,20 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": " baz"
|
||||
}
|
||||
]
|
||||
|
@ -12121,12 +12187,20 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": " baz"
|
||||
}
|
||||
]
|
||||
|
@ -12154,12 +12228,20 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "baz"
|
||||
}
|
||||
]
|
||||
|
@ -12207,6 +12289,11 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": " bar"
|
||||
}
|
||||
]
|
||||
|
@ -12234,6 +12321,9 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
|
@ -12263,6 +12353,9 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
|
@ -12351,22 +12444,25 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar "
|
||||
"text": "bar baz"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "baz"
|
||||
"text": " bim"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": " bim bop"
|
||||
"text": " bop"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -12469,6 +12565,9 @@
|
|||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
|
@ -12516,6 +12615,9 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -12524,6 +12626,11 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": " baz"
|
||||
}
|
||||
]
|
||||
|
@ -12617,6 +12724,9 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -12625,6 +12735,11 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": " baz"
|
||||
}
|
||||
]
|
||||
|
@ -12650,6 +12765,9 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -12658,6 +12776,11 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": "baz"
|
||||
}
|
||||
]
|
||||
|
@ -12685,6 +12808,11 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
}
|
||||
],
|
||||
"text": " bar"
|
||||
}
|
||||
]
|
||||
|
@ -12710,6 +12838,9 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -12739,24 +12870,27 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar "
|
||||
"text": "bar baz"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "baz"
|
||||
"text": "\nbim"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "\nbim bop"
|
||||
"text": " bop"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -12791,6 +12925,9 @@
|
|||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -13602,12 +13739,20 @@
|
|||
"marks": [
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar *baz bim"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": " bam"
|
||||
}
|
||||
]
|
||||
|
@ -13798,6 +13943,9 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
},
|
||||
{
|
||||
"type": "code"
|
||||
}
|
||||
|
@ -13827,6 +13975,9 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
},
|
||||
{
|
||||
"type": "code"
|
||||
}
|
||||
|
@ -14769,6 +14920,16 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/uri",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -14778,8 +14939,21 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/uri",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
|
@ -14787,6 +14961,22 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/uri",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
},
|
||||
{
|
||||
"type": "code"
|
||||
}
|
||||
|
@ -14798,8 +14988,38 @@
|
|||
]
|
||||
}
|
||||
06_07__inlines__links__033: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "image",
|
||||
"attrs": {
|
||||
"src": "moon.jpg",
|
||||
"alt": "moon",
|
||||
"title": null,
|
||||
"uploading": false,
|
||||
"canonicalSrc": null
|
||||
},
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/uri",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
06_07__inlines__links__034: |-
|
||||
{
|
||||
"type": "doc",
|
||||
|
@ -14867,13 +15087,25 @@
|
|||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "baz"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "](/uri)](/uri)"
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "](/uri)"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "](/uri)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -15159,6 +15391,16 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/uri",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
|
@ -15168,8 +15410,21 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/uri",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "bar"
|
||||
|
@ -15177,6 +15432,22 @@
|
|||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/uri",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "bold"
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
},
|
||||
{
|
||||
"type": "code"
|
||||
}
|
||||
|
@ -15188,8 +15459,38 @@
|
|||
]
|
||||
}
|
||||
06_07__inlines__links__047: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "image",
|
||||
"attrs": {
|
||||
"src": "moon.jpg",
|
||||
"alt": "moon",
|
||||
"title": null,
|
||||
"uploading": false,
|
||||
"canonicalSrc": null
|
||||
},
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/uri",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
06_07__inlines__links__048: |-
|
||||
{
|
||||
"type": "doc",
|
||||
|
@ -15273,6 +15574,9 @@
|
|||
"title": null,
|
||||
"canonicalSrc": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "baz"
|
||||
|
@ -15847,6 +16151,18 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/url",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": "title",
|
||||
"canonicalSrc": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"text": " bar"
|
||||
}
|
||||
]
|
||||
|
@ -15966,6 +16282,18 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/url",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": "title",
|
||||
"canonicalSrc": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"text": " bar"
|
||||
}
|
||||
]
|
||||
|
@ -16004,7 +16332,23 @@
|
|||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": " bar]"
|
||||
"marks": [
|
||||
{
|
||||
"type": "link",
|
||||
"attrs": {
|
||||
"href": "/url",
|
||||
"target": "_blank",
|
||||
"class": null,
|
||||
"title": "title",
|
||||
"canonicalSrc": null
|
||||
}
|
||||
}
|
||||
],
|
||||
"text": " bar"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": "]"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -18256,11 +18600,79 @@
|
|||
]
|
||||
}
|
||||
06_13__inlines__hard_line_breaks__006: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "foo"
|
||||
},
|
||||
{
|
||||
"type": "hardBreak",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "\nbar"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
06_13__inlines__hard_line_breaks__007: |-
|
||||
Error - check implementation:
|
||||
Cannot destructure property 'type' of 'this.stack.pop(...)' as it is undefined.
|
||||
{
|
||||
"type": "doc",
|
||||
"content": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "foo"
|
||||
},
|
||||
{
|
||||
"type": "hardBreak",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"marks": [
|
||||
{
|
||||
"type": "italic"
|
||||
}
|
||||
],
|
||||
"text": "\nbar"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
06_13__inlines__hard_line_breaks__008: |-
|
||||
{
|
||||
"type": "doc",
|
||||
|
|
|
@ -79,7 +79,7 @@ module QA
|
|||
Page::Project::Registry::Show.perform do |registry|
|
||||
expect(registry).to have_registry_repository(project.name)
|
||||
|
||||
registry.click_on_image(registry_repository.name)
|
||||
registry.click_on_image(project.name)
|
||||
expect(registry).to have_tag('master')
|
||||
|
||||
registry.click_delete
|
||||
|
|
|
@ -141,6 +141,8 @@ RSpec.describe 'Projects > Settings > Repository settings' do
|
|||
end
|
||||
|
||||
context 'remote mirror settings' do
|
||||
let(:ssh_url) { 'ssh://user@localhost/project.git' }
|
||||
|
||||
before do
|
||||
visit project_settings_repository_path(project)
|
||||
end
|
||||
|
@ -150,11 +152,12 @@ RSpec.describe 'Projects > Settings > Repository settings' do
|
|||
end
|
||||
|
||||
it 'creates a push mirror that mirrors all branches', :js do
|
||||
expect(find('.js-mirror-protected-hidden', visible: false).value).to eq('0')
|
||||
expect(page).to have_css('.js-mirror-protected-hidden[value="0"]', visible: false)
|
||||
|
||||
fill_in 'url', with: ssh_url
|
||||
expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false)
|
||||
|
||||
fill_in 'url', with: 'ssh://user@localhost/project.git'
|
||||
select 'SSH public key', from: 'Authentication method'
|
||||
|
||||
select_direction
|
||||
|
||||
Sidekiq::Testing.fake! do
|
||||
|
@ -167,13 +170,14 @@ RSpec.describe 'Projects > Settings > Repository settings' do
|
|||
expect(project.remote_mirrors.first.only_protected_branches).to eq(false)
|
||||
end
|
||||
|
||||
it 'creates a push mirror that only mirrors protected branches', :js,
|
||||
quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/337394' do
|
||||
it 'creates a push mirror that only mirrors protected branches', :js do
|
||||
find('#only_protected_branches').click
|
||||
|
||||
expect(find('.js-mirror-protected-hidden', visible: false).value).to eq('1')
|
||||
expect(page).to have_css('.js-mirror-protected-hidden[value="1"]', visible: false)
|
||||
|
||||
fill_in 'url', with: ssh_url
|
||||
expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false)
|
||||
|
||||
fill_in 'url', with: 'ssh://user@localhost/project.git'
|
||||
select 'SSH public key', from: 'Authentication method'
|
||||
select_direction
|
||||
|
||||
|
@ -190,7 +194,9 @@ RSpec.describe 'Projects > Settings > Repository settings' do
|
|||
it 'creates a push mirror that keeps divergent refs', :js do
|
||||
select_direction
|
||||
|
||||
fill_in 'url', with: 'ssh://user@localhost/project.git'
|
||||
fill_in 'url', with: ssh_url
|
||||
expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false)
|
||||
|
||||
fill_in 'Password', with: 'password'
|
||||
check 'Keep divergent refs'
|
||||
|
||||
|
|
|
@ -931,19 +931,101 @@ Paragraph
|
|||
`,
|
||||
expectedDoc: doc(div(source('<div>div</div>'), paragraph(source('div'), 'div'))),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
[![moon](moon.jpg)](/uri)
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
source('[![moon](moon.jpg)](/uri)'),
|
||||
link(
|
||||
{ ...source('[![moon](moon.jpg)](/uri)'), href: '/uri' },
|
||||
image({ ...source('![moon](moon.jpg)'), src: 'moon.jpg', alt: 'moon' }),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
<del>
|
||||
|
||||
*foo*
|
||||
|
||||
</del>
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
source('*foo*'),
|
||||
strike(source('<del>\n\n*foo*\n\n</del>'), italic(source('*foo*'), 'foo')),
|
||||
),
|
||||
),
|
||||
expectedMarkdown: '*foo*',
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
~[moon](moon.jpg) and [sun](sun.jpg)~
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
source('~[moon](moon.jpg) and [sun](sun.jpg)~'),
|
||||
strike(
|
||||
source('~[moon](moon.jpg) and [sun](sun.jpg)~'),
|
||||
link({ ...source('[moon](moon.jpg)'), href: 'moon.jpg' }, 'moon'),
|
||||
),
|
||||
strike(source('~[moon](moon.jpg) and [sun](sun.jpg)~'), ' and '),
|
||||
strike(
|
||||
source('~[moon](moon.jpg) and [sun](sun.jpg)~'),
|
||||
link({ ...source('[sun](sun.jpg)'), href: 'sun.jpg' }, 'sun'),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
markdown: `
|
||||
<del>
|
||||
|
||||
**Paragraph 1**
|
||||
|
||||
_Paragraph 2_
|
||||
|
||||
</del>
|
||||
`,
|
||||
expectedDoc: doc(
|
||||
paragraph(
|
||||
source('**Paragraph 1**'),
|
||||
strike(
|
||||
source('<del>\n\n**Paragraph 1**\n\n_Paragraph 2_\n\n</del>'),
|
||||
bold(source('**Paragraph 1**'), 'Paragraph 1'),
|
||||
),
|
||||
),
|
||||
paragraph(
|
||||
source('_Paragraph 2_'),
|
||||
strike(
|
||||
source('<del>\n\n**Paragraph 1**\n\n_Paragraph 2_\n\n</del>'),
|
||||
italic(source('_Paragraph 2_'), 'Paragraph 2'),
|
||||
),
|
||||
),
|
||||
),
|
||||
expectedMarkdown: `**Paragraph 1**
|
||||
|
||||
_Paragraph 2_`,
|
||||
},
|
||||
];
|
||||
|
||||
const runOnly = examples.find((example) => example.only === true);
|
||||
const runExamples = runOnly ? [runOnly] : examples;
|
||||
|
||||
it.each(runExamples)('processes %s correctly', async ({ markdown, expectedDoc }) => {
|
||||
const trimmed = markdown.trim();
|
||||
const document = await deserialize(trimmed);
|
||||
it.each(runExamples)(
|
||||
'processes %s correctly',
|
||||
async ({ markdown, expectedDoc, expectedMarkdown }) => {
|
||||
const trimmed = markdown.trim();
|
||||
const document = await deserialize(trimmed);
|
||||
|
||||
expect(expectedDoc).not.toBeFalsy();
|
||||
expect(document.toJSON()).toEqual(expectedDoc.toJSON());
|
||||
expect(serialize(document)).toEqual(trimmed);
|
||||
});
|
||||
expect(expectedDoc).not.toBeFalsy();
|
||||
expect(document.toJSON()).toEqual(expectedDoc.toJSON());
|
||||
expect(serialize(document)).toEqual(expectedMarkdown || trimmed);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* DISCLAIMER: THIS IS A SECURITY ORIENTED TEST THAT ENSURES
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe UnsetEscalationPoliciesForAlertIncidents do
|
||||
let(:namespaces) { table(:namespaces) }
|
||||
let(:projects) { table(:projects) }
|
||||
let(:issues) { table(:issues) }
|
||||
let(:alerts) { table(:alert_management_alerts) }
|
||||
let(:escalation_policies) { table(:incident_management_escalation_policies) }
|
||||
let(:escalation_statuses) { table(:incident_management_issuable_escalation_statuses) }
|
||||
let(:current_time) { Time.current.change(usec: 0) }
|
||||
|
||||
let!(:namespace) { namespaces.create!(name: 'namespace', path: 'namespace') }
|
||||
let!(:project_namespace) { namespaces.create!(name: 'project', path: 'project', type: 'project') }
|
||||
let!(:project) { projects.create!(namespace_id: namespace.id, project_namespace_id: project_namespace.id) }
|
||||
let!(:policy) { escalation_policies.create!(project_id: project.id, name: 'escalation policy') }
|
||||
|
||||
# Escalation status with policy from alert; Policy & escalation start time should be nullified
|
||||
let!(:issue_1) { create_issue }
|
||||
let!(:escalation_status_1) { create_status(issue_1, policy, current_time) }
|
||||
let!(:alert_1) { create_alert(1, issue_1) }
|
||||
|
||||
# Escalation status without policy, but with alert; Should be ignored
|
||||
let!(:issue_2) { create_issue }
|
||||
let!(:escalation_status_2) { create_status(issue_2, nil, current_time) }
|
||||
let!(:alert_2) { create_alert(2, issue_2) }
|
||||
|
||||
# Escalation status without alert, but with policy; Should be ignored
|
||||
let!(:issue_3) { create_issue }
|
||||
let!(:escalation_status_3) { create_status(issue_3, policy, current_time) }
|
||||
|
||||
# Alert without issue; Should be ignored
|
||||
let!(:alert_3) { create_alert(3) }
|
||||
|
||||
it 'removes the escalation policy if the incident corresponds to an alert' do
|
||||
expect { migrate! }
|
||||
.to change { escalation_status_1.reload.policy_id }.from(policy.id).to(nil)
|
||||
.and change { escalation_status_1.escalations_started_at }.from(current_time).to(nil)
|
||||
.and not_change { policy_attrs(escalation_status_2) }
|
||||
.and not_change { policy_attrs(escalation_status_3) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_issue
|
||||
issues.create!(project_id: project.id)
|
||||
end
|
||||
|
||||
def create_status(issue, policy = nil, escalations_started_at = nil)
|
||||
escalation_statuses.create!(
|
||||
issue_id: issue.id,
|
||||
policy_id: policy&.id,
|
||||
escalations_started_at: escalations_started_at
|
||||
)
|
||||
end
|
||||
|
||||
def create_alert(iid, issue = nil)
|
||||
alerts.create!(
|
||||
project_id: project.id,
|
||||
started_at: current_time,
|
||||
title: "alert #{iid}",
|
||||
iid: iid.to_s,
|
||||
issue_id: issue&.id
|
||||
)
|
||||
end
|
||||
|
||||
def policy_attrs(escalation_status)
|
||||
escalation_status.reload.slice(:policy_id, :escalations_started_at)
|
||||
end
|
||||
end
|
|
@ -25,4 +25,16 @@ RSpec.describe Packages::Cleanup::Policy, type: :model do
|
|||
|
||||
it { is_expected.to contain_exactly(active_policy) }
|
||||
end
|
||||
|
||||
describe '#keep_n_duplicated_package_files_disabled?' do
|
||||
subject { policy.keep_n_duplicated_package_files_disabled? }
|
||||
|
||||
%w[all 1].each do |value|
|
||||
context "with value set to #{value}" do
|
||||
let(:policy) { build(:packages_cleanup_policy, keep_n_duplicated_package_files: value) }
|
||||
|
||||
it { is_expected.to eq(value == 'all') }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -249,57 +249,6 @@ RSpec.describe AlertManagement::Alerts::UpdateService do
|
|||
|
||||
it_behaves_like 'adds a system note'
|
||||
end
|
||||
|
||||
context 'with an associated issue' do
|
||||
let_it_be(:issue, reload: true) { create(:issue, project: project) }
|
||||
|
||||
before do
|
||||
alert.update!(issue: issue)
|
||||
end
|
||||
|
||||
shared_examples 'does not sync with the incident status' do
|
||||
specify do
|
||||
expect(::Issues::UpdateService).not_to receive(:new)
|
||||
expect { response }.to change { alert.acknowledged? }.to(true)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'does not sync with the incident status'
|
||||
|
||||
context 'when the issue is an incident' do
|
||||
before do
|
||||
issue.update!(issue_type: Issue.issue_types[:incident])
|
||||
end
|
||||
|
||||
it_behaves_like 'does not sync with the incident status'
|
||||
|
||||
context 'when the incident has an escalation status' do
|
||||
let_it_be(:escalation_status, reload: true) { create(:incident_management_issuable_escalation_status, issue: issue) }
|
||||
|
||||
it 'updates the incident escalation status with the new alert status' do
|
||||
expect(::Issues::UpdateService).to receive(:new).once.and_call_original
|
||||
expect(described_class).to receive(:new).once.and_call_original
|
||||
|
||||
expect { response }.to change { escalation_status.reload.acknowledged? }.to(true)
|
||||
.and change { alert.reload.acknowledged? }.to(true)
|
||||
end
|
||||
|
||||
context 'when the statuses match' do
|
||||
before do
|
||||
escalation_status.update!(status_event: :acknowledge)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not sync with the incident status'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a status change reason is included' do
|
||||
let(:params) { { status: new_status, status_change_reason: ' by changing the incident status' } }
|
||||
|
||||
it_behaves_like 'adds a system note', /changed the status to \*\*Acknowledged\*\* by changing the incident status/
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,14 +7,11 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::AfterUpdateServic
|
|||
let_it_be(:escalation_status, reload: true) { create(:incident_management_issuable_escalation_status, :triggered) }
|
||||
let_it_be(:issue, reload: true) { escalation_status.issue }
|
||||
let_it_be(:project) { issue.project }
|
||||
let_it_be(:alert) { create(:alert_management_alert, issue: issue, project: project) }
|
||||
|
||||
let(:status_event) { :acknowledge }
|
||||
let(:update_params) { { incident_management_issuable_escalation_status_attributes: { status_event: status_event } } }
|
||||
let(:service) { IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.new(issue, current_user) }
|
||||
|
||||
subject(:result) do
|
||||
issue.update!(update_params)
|
||||
issue.update!(incident_management_issuable_escalation_status_attributes: update_params)
|
||||
service.execute
|
||||
end
|
||||
|
||||
|
@ -22,22 +19,15 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::AfterUpdateServic
|
|||
issue.project.add_developer(current_user)
|
||||
end
|
||||
|
||||
shared_examples 'does not attempt to update the alert' do
|
||||
specify do
|
||||
expect(::AlertManagement::Alerts::UpdateService).not_to receive(:new)
|
||||
context 'with status attributes' do
|
||||
let(:status_event) { :acknowledge }
|
||||
let(:update_params) { { status_event: status_event } }
|
||||
|
||||
expect(result).to be_success
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'adds a status change system note' do
|
||||
specify do
|
||||
it 'adds a status change system note' do
|
||||
expect { result }.to change { issue.reload.notes.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'adds a status change timeline event' do
|
||||
specify do
|
||||
it 'adds a status change timeline event' do
|
||||
expect(IncidentManagement::TimelineEvents::CreateService)
|
||||
.to receive(:change_incident_status)
|
||||
.with(issue, current_user, escalation_status)
|
||||
|
@ -47,35 +37,13 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::AfterUpdateServic
|
|||
end
|
||||
end
|
||||
|
||||
context 'with status attributes' do
|
||||
it_behaves_like 'adds a status change system note'
|
||||
it_behaves_like 'adds a status change timeline event'
|
||||
context 'with non-status attributes' do
|
||||
let(:update_params) { { updated_at: Time.current } }
|
||||
|
||||
it 'updates the alert with the new alert status' do
|
||||
expect(::AlertManagement::Alerts::UpdateService).to receive(:new).once.and_call_original
|
||||
expect(described_class).to receive(:new).once.and_call_original
|
||||
|
||||
expect { result }.to change { escalation_status.reload.acknowledged? }.to(true)
|
||||
.and change { alert.reload.acknowledged? }.to(true)
|
||||
end
|
||||
|
||||
context 'when incident is not associated with an alert' do
|
||||
before do
|
||||
alert.destroy!
|
||||
end
|
||||
|
||||
it_behaves_like 'does not attempt to update the alert'
|
||||
it_behaves_like 'adds a status change system note'
|
||||
it_behaves_like 'adds a status change timeline event'
|
||||
end
|
||||
|
||||
context 'when new status matches the current status' do
|
||||
let(:status_event) { :trigger }
|
||||
|
||||
it_behaves_like 'does not attempt to update the alert'
|
||||
|
||||
specify { expect { result }.not_to change { issue.reload.notes.count } }
|
||||
specify { expect { result }.not_to change { issue.reload.incident_management_timeline_events.count } }
|
||||
it 'does not add a status change system note or timeline event' do
|
||||
expect { result }
|
||||
.to not_change { issue.reload.notes.count }
|
||||
.and not_change { issue.reload.incident_management_timeline_events.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,10 +11,4 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::BuildService do
|
|||
subject(:execute) { service.execute }
|
||||
|
||||
it_behaves_like 'initializes new escalation status with expected attributes'
|
||||
|
||||
context 'with associated alert' do
|
||||
let_it_be(:alert) { create(:alert_management_alert, :acknowledged, project: project, issue: incident) }
|
||||
|
||||
it_behaves_like 'initializes new escalation status with expected attributes', { status_event: :acknowledge }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,19 +27,4 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::CreateService do
|
|||
expect { execute }.not_to change { incident.reload.escalation_status }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with associated alert' do
|
||||
before do
|
||||
create(:alert_management_alert, :acknowledged, project: project, issue: incident)
|
||||
end
|
||||
|
||||
it 'creates an escalation status matching the alert attributes' do
|
||||
expect { execute }.to change { incident.reload.escalation_status }.from(nil)
|
||||
expect(incident.escalation_status).to have_attributes(
|
||||
status_name: :acknowledged,
|
||||
policy_id: nil,
|
||||
escalations_started_at: nil
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -102,10 +102,4 @@ RSpec.describe IncidentManagement::IssuableEscalationStatuses::PrepareUpdateServ
|
|||
it_behaves_like 'successful response', { status_event: :acknowledge }
|
||||
end
|
||||
end
|
||||
|
||||
context 'with status_change_reason param' do
|
||||
let(:params) { { status_change_reason: ' by changing the incident status' } }
|
||||
|
||||
it_behaves_like 'successful response', { status_change_reason: ' by changing the incident status' }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1146,11 +1146,11 @@ RSpec.describe Issues::UpdateService, :mailer do
|
|||
let(:opts) { { escalation_status: { status: 'acknowledged' } } }
|
||||
let(:escalation_update_class) { ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService }
|
||||
|
||||
shared_examples 'updates the escalation status record' do |expected_status, expected_reason = nil|
|
||||
shared_examples 'updates the escalation status record' do |expected_status|
|
||||
let(:service_double) { instance_double(escalation_update_class) }
|
||||
|
||||
it 'has correct value' do
|
||||
expect(escalation_update_class).to receive(:new).with(issue, user, status_change_reason: expected_reason).and_return(service_double)
|
||||
expect(escalation_update_class).to receive(:new).with(issue, user).and_return(service_double)
|
||||
expect(service_double).to receive(:execute)
|
||||
|
||||
update_issue(opts)
|
||||
|
@ -1193,23 +1193,6 @@ RSpec.describe Issues::UpdateService, :mailer do
|
|||
|
||||
it_behaves_like 'updates the escalation status record', :acknowledged
|
||||
|
||||
context 'with associated alert' do
|
||||
let!(:alert) { create(:alert_management_alert, issue: issue, project: project) }
|
||||
|
||||
it 'syncs the update back to the alert' do
|
||||
update_issue(opts)
|
||||
|
||||
expect(issue.escalation_status.status_name).to eq(:acknowledged)
|
||||
expect(alert.reload.status_name).to eq(:acknowledged)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a status change reason provided' do
|
||||
let(:opts) { { escalation_status: { status: 'acknowledged', status_change_reason: ' by changing the alert status' } } }
|
||||
|
||||
it_behaves_like 'updates the escalation status record', :acknowledged, ' by changing the alert status'
|
||||
end
|
||||
|
||||
context 'with unsupported status value' do
|
||||
let(:opts) { { escalation_status: { status: 'unsupported-status' } } }
|
||||
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
# frozen_string_literal: true
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Packages::Cleanup::ExecutePolicyService do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be_with_reload(:policy) { create(:packages_cleanup_policy, project: project) }
|
||||
|
||||
let(:service) { described_class.new(policy) }
|
||||
|
||||
describe '#execute' do
|
||||
subject(:execute) { service.execute }
|
||||
|
||||
context 'with the keep_n_duplicated_files parameter' do
|
||||
let_it_be(:package1) { create(:package, project: project) }
|
||||
let_it_be(:package2) { create(:package, project: project) }
|
||||
let_it_be(:package3) { create(:package, project: project) }
|
||||
let_it_be(:package4) { create(:package, :pending_destruction, project: project) }
|
||||
|
||||
let_it_be(:package_file1_1) { create(:package_file, package: package1, file_name: 'file_name1') }
|
||||
let_it_be(:package_file1_2) { create(:package_file, package: package1, file_name: 'file_name1') }
|
||||
let_it_be(:package_file1_3) { create(:package_file, package: package1, file_name: 'file_name1') }
|
||||
|
||||
let_it_be(:package_file1_4) { create(:package_file, package: package1, file_name: 'file_name2') }
|
||||
let_it_be(:package_file1_5) { create(:package_file, package: package1, file_name: 'file_name2') }
|
||||
let_it_be(:package_file1_6) { create(:package_file, package: package1, file_name: 'file_name2') }
|
||||
let_it_be(:package_file1_7) do
|
||||
create(:package_file, :pending_destruction, package: package1, file_name: 'file_name2')
|
||||
end
|
||||
|
||||
let_it_be(:package_file2_1) { create(:package_file, package: package2, file_name: 'file_name1') }
|
||||
let_it_be(:package_file2_2) { create(:package_file, package: package2, file_name: 'file_name1') }
|
||||
let_it_be(:package_file2_3) { create(:package_file, package: package2, file_name: 'file_name1') }
|
||||
let_it_be(:package_file2_4) { create(:package_file, package: package2, file_name: 'file_name1') }
|
||||
|
||||
let_it_be(:package_file3_1) { create(:package_file, package: package3, file_name: 'file_name_test') }
|
||||
|
||||
let_it_be(:package_file4_1) { create(:package_file, package: package4, file_name: 'file_name1') }
|
||||
let_it_be(:package_file4_2) { create(:package_file, package: package4, file_name: 'file_name1') }
|
||||
|
||||
let(:package_files_1) { package1.package_files.installable }
|
||||
let(:package_files_2) { package2.package_files.installable }
|
||||
let(:package_files_3) { package3.package_files.installable }
|
||||
|
||||
context 'set to less than the total number of duplicated files' do
|
||||
before do
|
||||
# for each package file duplicate, we keep only the most recent one
|
||||
policy.update!(keep_n_duplicated_package_files: '1')
|
||||
end
|
||||
|
||||
shared_examples 'keeping the most recent package files' do
|
||||
let(:response_payload) do
|
||||
{
|
||||
counts: {
|
||||
marked_package_files_total_count: 7,
|
||||
unique_package_id_and_file_name_total_count: 3
|
||||
},
|
||||
timeout: false
|
||||
}
|
||||
end
|
||||
|
||||
it 'only keeps the most recent package files' do
|
||||
expect { execute }.to change { ::Packages::PackageFile.installable.count }.by(-7)
|
||||
|
||||
expect(package_files_1).to contain_exactly(package_file1_3, package_file1_6)
|
||||
expect(package_files_2).to contain_exactly(package_file2_4)
|
||||
expect(package_files_3).to contain_exactly(package_file3_1)
|
||||
|
||||
expect(execute).to be_success
|
||||
expect(execute.message).to eq("Packages cleanup policy executed for project #{project.id}")
|
||||
expect(execute.payload).to eq(response_payload)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'keeping the most recent package files'
|
||||
|
||||
context 'when the service needs to loop' do
|
||||
before do
|
||||
stub_const("#{described_class.name}::DUPLICATED_FILES_BATCH_SIZE", 2)
|
||||
end
|
||||
|
||||
it_behaves_like 'keeping the most recent package files' do
|
||||
before do
|
||||
expect(::Packages::MarkPackageFilesForDestructionService)
|
||||
.to receive(:new).exactly(3).times.and_call_original
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a timeout is hit' do
|
||||
let(:response_payload) do
|
||||
{
|
||||
counts: {
|
||||
marked_package_files_total_count: 4,
|
||||
unique_package_id_and_file_name_total_count: 3
|
||||
},
|
||||
timeout: true
|
||||
}
|
||||
end
|
||||
|
||||
let(:service_timeout_response) do
|
||||
ServiceResponse.error(
|
||||
message: 'Timeout while marking package files as pending destruction',
|
||||
payload: { marked_package_files_count: 0 }
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
mock_service_timeout(on_iteration: 3)
|
||||
end
|
||||
|
||||
it 'keeps part of the most recent package files' do
|
||||
expect { execute }
|
||||
.to change { ::Packages::PackageFile.installable.count }.by(-4)
|
||||
.and not_change { package_files_2.count } # untouched because of the timeout
|
||||
.and not_change { package_files_3.count } # untouched because of the timeout
|
||||
|
||||
expect(package_files_1).to contain_exactly(package_file1_3, package_file1_6)
|
||||
expect(execute).to be_success
|
||||
expect(execute.message).to eq("Packages cleanup policy executed for project #{project.id}")
|
||||
expect(execute.payload).to eq(response_payload)
|
||||
end
|
||||
|
||||
def mock_service_timeout(on_iteration:)
|
||||
execute_call_count = 1
|
||||
expect_next_instances_of(::Packages::MarkPackageFilesForDestructionService, 3) do |service|
|
||||
expect(service).to receive(:execute).and_wrap_original do |m, *args|
|
||||
# timeout if we are on the right iteration
|
||||
if execute_call_count == on_iteration
|
||||
service_timeout_response
|
||||
else
|
||||
execute_call_count += 1
|
||||
m.call(*args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'set to more than the total number of duplicated files' do
|
||||
before do
|
||||
# using the biggest value for keep_n_duplicated_package_files
|
||||
policy.update!(keep_n_duplicated_package_files: '50')
|
||||
end
|
||||
|
||||
it 'keeps all package files' do
|
||||
expect { execute }.not_to change { ::Packages::PackageFile.installable.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'set to all' do
|
||||
before do
|
||||
policy.update!(keep_n_duplicated_package_files: 'all')
|
||||
end
|
||||
|
||||
it 'skips the policy' do
|
||||
expect(::Packages::MarkPackageFilesForDestructionService).not_to receive(:new)
|
||||
expect { execute }.not_to change { ::Packages::PackageFile.installable.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,9 +6,11 @@ RSpec.describe Packages::MarkPackageFilesForDestructionService, :aggregate_failu
|
|||
let(:service) { described_class.new(package_files) }
|
||||
|
||||
describe '#execute', :aggregate_failures do
|
||||
subject { service.execute }
|
||||
let(:batch_deadline) { nil }
|
||||
|
||||
shared_examples 'executing successfully' do
|
||||
subject { service.execute(batch_deadline: batch_deadline) }
|
||||
|
||||
shared_examples 'executing successfully' do |marked_package_files_count: 0|
|
||||
it 'marks package files for destruction' do
|
||||
expect { subject }
|
||||
.to change { ::Packages::PackageFile.pending_destruction.count }.by(package_files.size)
|
||||
|
@ -17,6 +19,7 @@ RSpec.describe Packages::MarkPackageFilesForDestructionService, :aggregate_failu
|
|||
it 'executes successfully' do
|
||||
expect(subject).to be_success
|
||||
expect(subject.message).to eq('Package files are now pending destruction')
|
||||
expect(subject.payload).to eq(marked_package_files_count: marked_package_files_count)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -30,13 +33,49 @@ RSpec.describe Packages::MarkPackageFilesForDestructionService, :aggregate_failu
|
|||
let_it_be(:package_file) { create(:package_file) }
|
||||
let_it_be(:package_files) { ::Packages::PackageFile.id_in(package_file.id) }
|
||||
|
||||
it_behaves_like 'executing successfully'
|
||||
it_behaves_like 'executing successfully', marked_package_files_count: 1
|
||||
end
|
||||
|
||||
context 'with many package files' do
|
||||
let_it_be(:package_files) { ::Packages::PackageFile.id_in(create_list(:package_file, 3).map(&:id)) }
|
||||
|
||||
it_behaves_like 'executing successfully'
|
||||
it_behaves_like 'executing successfully', marked_package_files_count: 3
|
||||
|
||||
context 'with a batch deadline' do
|
||||
let_it_be(:batch_deadline) { 250.seconds.from_now }
|
||||
|
||||
context 'when the deadline is not hit' do
|
||||
before do
|
||||
expect(Time.zone).to receive(:now).and_return(batch_deadline - 10.seconds)
|
||||
end
|
||||
|
||||
it_behaves_like 'executing successfully', marked_package_files_count: 3
|
||||
end
|
||||
|
||||
context 'when the deadline is hit' do
|
||||
it 'does not execute the batch loop' do
|
||||
expect(Time.zone).to receive(:now).and_return(batch_deadline + 10.seconds)
|
||||
expect { subject }.to not_change { ::Packages::PackageFile.pending_destruction.count }
|
||||
expect(subject).to be_error
|
||||
expect(subject.message).to eq('Timeout while marking package files as pending destruction')
|
||||
expect(subject.payload).to eq(marked_package_files_count: 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a batch size is defined' do
|
||||
let_it_be(:batch_deadline) { 250.seconds.from_now }
|
||||
|
||||
let(:batch_size) { 2 }
|
||||
|
||||
subject { service.execute(batch_deadline: batch_deadline, batch_size: batch_size) }
|
||||
|
||||
before do
|
||||
expect(Time.zone).to receive(:now).twice.and_call_original
|
||||
end
|
||||
|
||||
it_behaves_like 'executing successfully', marked_package_files_count: 3
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an error during the update' do
|
||||
|
|
Loading…
Reference in New Issue