Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-08-25 18:10:08 +00:00
parent 5f85444a43
commit 421200ab0d
27 changed files with 787 additions and 74 deletions

View File

@ -293,7 +293,7 @@ export default {
</div>
<gl-modal
v-if="canRenderPipelineButton"
v-if="canRenderPipelineButton || shouldRenderEmptyState"
:id="modalId"
ref="modal"
:modal-id="modalId"

View File

@ -64,3 +64,5 @@ export const EXTENSION_PRIORITY_HIGHEST = 200;
*/
export const SAFE_VIDEO_EXT = ['mp4', 'm4v', 'mov', 'webm', 'ogv'];
export const SAFE_AUDIO_EXT = ['mp3', 'oga', 'ogg', 'spx', 'wav'];
export const DIAGRAM_LANGUAGES = ['plantuml', 'mermaid'];

View File

@ -5,6 +5,7 @@ import Bold from './bold';
import BulletList from './bullet_list';
import Code from './code';
import CodeBlockHighlight from './code_block_highlight';
import Diagram from './diagram';
import FootnoteReference from './footnote_reference';
import FootnoteDefinition from './footnote_definition';
import Frontmatter from './frontmatter';
@ -39,6 +40,7 @@ export default Extension.create({
BulletList.name,
Code.name,
CodeBlockHighlight.name,
Diagram.name,
FootnoteReference.name,
FootnoteDefinition.name,
Frontmatter.name,

View File

@ -126,7 +126,7 @@ const defaultSerializerConfig = {
}),
[BulletList.name]: preserveUnchanged(renderBulletList),
[CodeBlockHighlight.name]: preserveUnchanged(renderCodeBlock),
[Diagram.name]: renderCodeBlock,
[Diagram.name]: preserveUnchanged(renderCodeBlock),
[DescriptionList.name]: renderHTMLNode('dl', true),
[DescriptionItem.name]: (state, node, parent, index) => {
if (index === 1) state.ensureNewLine();

View File

@ -1,6 +1,6 @@
import { render } from '~/lib/gfm';
import { isValidAttribute } from '~/lib/dompurify';
import { SAFE_AUDIO_EXT, SAFE_VIDEO_EXT } from '../constants';
import { SAFE_AUDIO_EXT, SAFE_VIDEO_EXT, DIAGRAM_LANGUAGES } from '../constants';
import { createProseMirrorDocFromMdastTree } from './hast_to_prosemirror_converter';
const ALL_AUDIO_VIDEO_EXT = [...SAFE_AUDIO_EXT, ...SAFE_VIDEO_EXT];
@ -40,6 +40,12 @@ const extractMediaFileExtension = (url) => {
}
};
const isCodeBlock = (hastNode) => hastNode.tagName === 'codeblock';
const isDiagramCodeBlock = (hastNode) => DIAGRAM_LANGUAGES.includes(hastNode.properties?.language);
const getCodeBlockAttrs = (hastNode) => ({ language: hastNode.properties.language });
const factorySpecs = {
blockquote: { type: 'block', selector: 'blockquote' },
paragraph: { type: 'block', selector: 'p' },
@ -68,8 +74,13 @@ const factorySpecs = {
},
codeBlock: {
type: 'block',
selector: 'codeblock',
getAttrs: (hastNode) => ({ ...hastNode.properties }),
selector: (hastNode) => isCodeBlock(hastNode) && !isDiagramCodeBlock(hastNode),
getAttrs: getCodeBlockAttrs,
},
diagram: {
type: 'block',
selector: (hastNode) => isCodeBlock(hastNode) && isDiagramCodeBlock(hastNode),
getAttrs: getCodeBlockAttrs,
},
horizontalRule: {
type: 'block',

View File

@ -1,8 +0,0 @@
---
name: seat_count_alerts
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89204
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/362041
milestone: '15.1'
type: development
group: group::purchase
default_enabled: false

View File

@ -957,6 +957,33 @@ For example:
Pattern matching is case-sensitive by default. Use the `i` flag modifier to make a
pattern case-insensitive. For example: `/pattern/i`.
#### Store the regex pattern in a variable
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/35438) in GitLab 15.0 [with a flag](../../administration/feature_flags.md) named `ci_fix_rules_if_comparison_with_regexp_variable`, disabled by default.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/359740) and feature flag `ci_fix_rules_if_comparison_with_regexp_variable` removed in GitLab 15.1.
Variables on the right side of `=~` and `!~` expressions are evaluated as regular expressions.
The regular expression must be enclosed in forward slashes (`/`). For example:
```yaml
variables:
pattern: '/^ab.*/'
regex-job1:
variables:
teststring: 'abcde'
script: echo "This job will run, because 'abcde' matches the /^ab.*/ pattern."
rules:
- if: '$teststring =~ $pattern'
regex-job2:
variables:
teststring: 'fghij'
script: echo "This job will not run, because 'fghi' does not match the /^ab.*/ pattern."
rules:
- if: '$teststring =~ $pattern'
```
### Join variable expressions together with `&&` or `||`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/62867) in GitLab 12.0

View File

@ -3224,8 +3224,7 @@ job:
- Unlike variables in [`script`](../variables/index.md#use-cicd-variables-in-job-scripts)
sections, variables in rules expressions are always formatted as `$VARIABLE`.
- You can use `rules:if` with `include` to [conditionally include other configuration files](includes.md#use-rules-with-include).
- In [GitLab 15.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/35438),
variables on the right side of `=~` and `!~` expressions are evaluated as regular expressions.
- CI/CD variables on the right side of `=~` and `!~` expressions are [evaluated as regular expressions](../jobs/job_control.md#store-the-regex-pattern-in-a-variable).
**Related topics**:

View File

@ -19,6 +19,10 @@ For guidance not on this page, we defer to these style guides:
<!-- vale off -->
<!-- markdownlint-disable -->
## `&`
Do not use Latin abbreviations. Use **and** instead, unless you are documenting a UI element that uses an `&`.
## `@mention`
Try to avoid **`@mention`**. Say **mention** instead, and consider linking to the

View File

@ -90,7 +90,10 @@ SILENCE_DEPRECATIONS=1 bin/rspec spec/models/project_spec.rb
All new spec files are run in [random order](https://gitlab.com/gitlab-org/gitlab/-/issues/337399)
to surface flaky tests that are dependent on test order.
When randomized the used seed is shown in the spec output below the test suite summary. For example, `Randomized with seed 27443`.
When randomized:
- The string `# order random` is added below the example group description.
- The used seed is shown in the spec output below the test suite summary. For example, `Randomized with seed 27443`.
For a list of spec files which are still run in defined order, see [`rspec_order_todo.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/support/rspec_order_todo.yml).

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -33,22 +33,19 @@ you can automatically configure Mattermost slash commands:
1. On the left sidebar, select **Settings > Integrations**.
1. In **Add an integration**, select **Mattermost slash commands**.
1. In **Enable integration**, ensure the **Active** checkbox is selected.
1. Select **Add to Mattermost** and **Save changes**.
1. Select **Add to Mattermost**, and select **Save changes**.
## Configure manually
To manually configure slash commands in Mattermost, you must:
1. [Enable custom slash commands](#enable-custom-slash-commands) in Mattermost.
1. [Get configuration values](#get-configuration-values-from-gitlab) from GitLab.
1. [Create a new slash command](#create-a-slash-command) in Mattermost.
1. [Provide the Mattermost token](#provide-the-mattermost-token-to-gitlab) to GitLab.
1. [Enable custom slash commands in Mattermost](#enable-custom-slash-commands-in-mattermost).
(This step is required only for installations from source.)
1. [Get configuration values from GitLab](#get-configuration-values-from-gitlab).
1. [Create a slash command in Mattermost](#create-a-slash-command-in-mattermost).
1. [Provide the Mattermost token to GitLab](#provide-the-mattermost-token-to-gitlab).
### Enable custom slash commands
NOTE:
Omnibus GitLab installations are preconfigured. This step is required only for
installations from source.
### Enable custom slash commands in Mattermost
To enable custom slash commands from the Mattermost administrator console:
@ -59,65 +56,51 @@ To enable custom slash commands from the Mattermost administrator console:
- **Enable Custom Slash Commands**
- **Enable integrations to override usernames**
- **Enable integrations to override profile picture icons**
1. Select **Save**, but do not close this browser tab, because you need it in
1. Select **Save**, but do not close this browser tab. You need it in
a later step.
### Get configuration values from GitLab
After you enable custom slash commands in Mattermost, you need configuration
information from GitLab. To get this information:
To get configuration values from GitLab:
1. In a different browser tab than your current Mattermost session, sign in to
1. In a different browser tab, sign in to
GitLab as a user with administrator access.
1. On the top bar, select **Menu > Admin**.
1. In the left menu, select **Settings > Integrations**, then select
**Mattermost slash commands**.
1. GitLab displays potential values for Mattermost settings. Copy the **Request URL**
as you need it for the next step. All other values are suggestions.
1. Do not close this browser tab, because you need it in future steps.
1. On the left sidebar, select **Settings > Integrations**.
1. Select **Mattermost slash commands**. GitLab displays potential values for Mattermost settings.
1. Copy the **Request URL** value. All other values are suggestions.
1. Do not close this browser tab. You need it in a later step.
Next, create a slash command in Mattermost with the values from GitLab.
### Create a slash command in Mattermost
### Create a slash command
To create a slash command in Mattermost:
To create a slash command, you need the values you obtained from GitLab in
the previous step:
1. In the Mattermost tab you left open when you
[enabled custom slash commands](#enable-custom-slash-commands), go to your
team page.
1. [In the Mattermost browser tab](#enable-custom-slash-commands-in-mattermost),
go to your team page.
1. Select the **{ellipsis_v}** **Settings** icon, and select **Integrations**.
1. In the left menu, select **Slash commands**.
1. Select **Add Slash Command**:
![Mattermost add command](img/mattermost_add_slash_command.png)
1. On the left sidebar, select **Slash commands**.
1. Select **Add Slash Command**.
1. Provide a **Display Name** and **Description** for your new command.
1. Provide a **Command Trigger Word** according to your application's configuration:
1. Provide a **Command Trigger Word** based on your application's configuration:
- **If you intend to only connect one project to your Mattermost team**: Use
- **If you intend to only connect one project to your Mattermost team**, use
`/gitlab` for your trigger word.
- **If you intend to connect multiple projects**: Use a trigger word that relates
- **If you intend to connect multiple projects**, use a trigger word that relates
to your project, such as `/project-name` or `/gitlab-project-name`.
1. For **Request URL**, provide the value you copied from GitLab when you
[viewed configuration values](#get-configuration-values-from-gitlab).
1. For all other values, you may use the suggestions from GitLab or use your
1. For **Request URL**, [paste the value you copied from GitLab](#get-configuration-values-from-gitlab).
1. For all other values, you may use the suggestions from GitLab or your
preferred values.
1. Copy the **Token** value, as you need it in a later step, and select **Done**.
1. Copy the **Token** value, and select **Done**.
### Provide the Mattermost token to GitLab
When you create a new slash command in Mattermost, it generates a token you must
Creating a slash command in Mattermost generates a token you must
provide to GitLab:
1. In the GitLab browser tab from
[getting configuration values from GitLab](#get-configuration-values-from-gitlab),
select the **Active** checkbox to enable this configuration.
1. In the **Token** field, paste the token you obtained from Mattermost.
ensure that the **Active** toggle is enabled.
![Mattermost copy token to GitLab](img/mattermost_gitlab_token.png)
1. Select **Save changes** for the changes to take effect.
1. [In the GitLab browser tab](#get-configuration-values-from-gitlab),
select the **Active** checkbox.
1. In the **Token** text box, [paste the token you copied from Mattermost](#create-a-slash-command-in-mattermost).
1. Select **Save changes**.
Your slash command can now communicate with your GitLab project.

View File

@ -15,6 +15,8 @@ module Banzai
include RequestStoreReferenceCache
include OutputSafety
REFERENCE_TYPE_DATA_ATTRIBUTE = 'data-reference-type='
class << self
# Implement in child class
# Example: self.reference_type = :merge_request
@ -132,13 +134,19 @@ module Banzai
def data_attribute(attributes = {})
attributes = attributes.reject { |_, v| v.nil? }
attributes[:reference_type] ||= self.class.reference_type
# "data-reference-type=" attribute got moved into a constant because we need
# to use it on ReferenceRewriter class to detect if the markdown contains any reference
reference_type_attribute = "#{REFERENCE_TYPE_DATA_ATTRIBUTE}#{escape_once(self.class.reference_type)} "
attributes[:container] ||= 'body'
attributes[:placement] ||= 'top'
attributes.delete(:original) if context[:no_original_data]
attributes.map do |key, value|
%Q(data-#{key.to_s.dasherize}="#{escape_once(value)}")
end.join(' ')
end
.join(' ')
.prepend(reference_type_attribute)
end
def ignore_ancestor_query

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
module Gitlab
module Database
class TablesSortedByForeignKeys
include TSort
def initialize(connection, tables)
@connection = connection
@tables = tables
end
def execute
strongly_connected_components
end
private
def tsort_each_node(&block)
tables_dependencies.each_key(&block)
end
def tsort_each_child(node, &block)
tables_dependencies[node].each(&block)
end
# it maps the tables to the tables that depend on it
def tables_dependencies
@tables.to_h do |table_name|
[table_name, all_foreign_keys[table_name]&.map(&:from_table).to_a]
end
end
def all_foreign_keys
@all_foreign_keys ||= @tables.flat_map do |table_name|
@connection.foreign_keys(table_name)
end.group_by(&:to_table)
end
end
end
end

View File

@ -0,0 +1,89 @@
# frozen_string_literal: true
module Gitlab
module Database
class TablesTruncate
GITLAB_SCHEMAS_TO_IGNORE = %i[gitlab_geo].freeze
def initialize(database_name:, min_batch_size:, logger: nil, until_table: nil, dry_run: false)
@database_name = database_name
@min_batch_size = min_batch_size
@logger = logger
@until_table = until_table
@dry_run = dry_run
end
def execute
raise "Cannot truncate legacy tables in single-db setup" unless Gitlab::Database.has_config?(:ci)
raise "database is not supported" unless %w[main ci].include?(database_name)
logger&.info "DRY RUN:" if dry_run
connection = Gitlab::Database.database_base_models[database_name].connection
schemas_for_connection = Gitlab::Database.gitlab_schemas_for_connection(connection)
tables_to_truncate = Gitlab::Database::GitlabSchema.tables_to_schema.reject do |_, schema_name|
(GITLAB_SCHEMAS_TO_IGNORE.union(schemas_for_connection)).include?(schema_name)
end.keys
tables_sorted = Gitlab::Database::TablesSortedByForeignKeys.new(connection, tables_to_truncate).execute
# Checking if all the tables have the write-lock triggers
# to make sure we are deleting the right tables on the right database.
tables_sorted.flatten.each do |table_name|
query = <<~SQL
SELECT COUNT(*) from information_schema.triggers
WHERE event_object_table = '#{table_name}'
AND trigger_name = 'gitlab_schema_write_trigger_for_#{table_name}'
SQL
if connection.select_value(query) == 0
raise "Table '#{table_name}' is not locked for writes. Run the rake task gitlab:db:lock_writes first"
end
end
if until_table
table_index = tables_sorted.find_index { |tables_group| tables_group.include?(until_table) }
raise "The table '#{until_table}' is not within the truncated tables" if table_index.nil?
tables_sorted = tables_sorted[0..table_index]
end
# min_batch_size is the minimum number of new tables to truncate at each stage.
# But in each stage we have also have to truncate the already truncated tables in the previous stages
logger&.info "Truncating legacy tables for the database #{database_name}"
truncate_tables_in_batches(connection, tables_sorted, min_batch_size)
end
private
attr_accessor :database_name, :min_batch_size, :logger, :dry_run, :until_table
def truncate_tables_in_batches(connection, tables_sorted, min_batch_size)
truncated_tables = []
unless dry_run
tables_sorted.flatten.compact.each do |table|
sql_statement = "SELECT set_config('lock_writes.#{table}', 'false', false)"
logger&.info sql_statement
connection.execute(sql_statement)
end
end
# We do the truncation in stages to avoid high IO
# In each stage, we truncate the new tables along with the already truncated
# tables before. That's because PostgreSQL doesn't allow to truncate any table (A)
# without truncating any other table (B) that has a Foreign Key pointing to the table (A).
# even if table (B) is empty, because it has been already truncated in a previous stage.
tables_sorted.in_groups_of(min_batch_size).each do |tables_groups|
new_tables_to_truncate = tables_groups.flatten.compact
logger&.info "= New tables to truncate: #{new_tables_to_truncate.join(', ')}"
truncated_tables.push(*new_tables_to_truncate).tap(&:sort!)
sql_statement = "TRUNCATE TABLE #{truncated_tables.join(', ')} RESTRICT"
logger&.info sql_statement
connection.execute(sql_statement) unless dry_run
end
end
end
end
end

View File

@ -55,7 +55,12 @@ module Gitlab
end
def needs_rewrite?
strong_memoize(:needs_rewrite) { @text_html.include?('data-reference-type=') }
strong_memoize(:needs_rewrite) do
reference_type_attribute =
Banzai::Filter::References::ReferenceFilter::REFERENCE_TYPE_DATA_ATTRIBUTE
@text_html.include?(reference_type_attribute)
end
end
private

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
namespace :gitlab do
namespace :db do
namespace :truncate_legacy_tables do
desc "GitLab | DB | Truncate CI Tables on Main"
task :main, [:min_batch_size] => [:environment, 'gitlab:db:validate_config'] do |_t, args|
args.with_defaults(min_batch_size: 5)
Gitlab::Database::TablesTruncate.new(
database_name: 'main',
min_batch_size: args.min_batch_size.to_i,
logger: Logger.new($stdout),
dry_run: ENV['DRY_RUN'] == 'true',
until_table: ENV['UNTIL_TABLE']
).execute
end
desc "GitLab | DB | Truncate Main Tables on CI"
task :ci, [:min_batch_size] => [:environment, 'gitlab:db:validate_config'] do |_t, args|
args.with_defaults(min_batch_size: 5)
Gitlab::Database::TablesTruncate.new(
database_name: 'ci',
min_batch_size: args.min_batch_size.to_i,
logger: Logger.new($stdout),
dry_run: ENV['DRY_RUN'] == 'true',
until_table: ENV['UNTIL_TABLE']
).execute
end
end
end
end

View File

@ -39373,6 +39373,9 @@ msgstr ""
msgid "The parent epic is confidential and can only contain confidential epics and issues"
msgstr ""
msgid "The parsed YAML is too big"
msgstr ""
msgid "The password for the Jenkins server."
msgstr ""

View File

@ -46,11 +46,11 @@ class StaticAnalysis
# around this we will only enable this task on EE installations.
TASKS_WITH_DURATIONS_SECONDS = [
(Gitlab.ee? ? Task.new(%w[bin/rake gettext:updated_check], 360) : nil),
Task.new(%w[yarn run lint:prettier], 160),
Task.new(%w[bin/rake gettext:lint], 85),
Task.new(%W[scripts/license-check.sh #{project_path}], 20),
Task.new(%w[bin/rake lint:static_verification], 35),
Task.new(%w[scripts/rubocop-max-files-in-cache-check], 20),
Task.new(%w[yarn run lint:prettier], 200),
Task.new(%w[bin/rake gettext:lint], 105),
Task.new(%W[scripts/license-check.sh #{project_path}], 200),
Task.new(%w[bin/rake lint:static_verification], 40),
Task.new(%w[scripts/rubocop-max-files-in-cache-check], 25),
Task.new(%w[bin/rake config_lint], 10),
Task.new(%w[bin/rake gitlab:sidekiq:all_queues_yml:check], 15),
(Gitlab.ee? ? Task.new(%w[bin/rake gitlab:sidekiq:sidekiq_queues_yml:check], 11) : nil),

View File

@ -773,7 +773,7 @@
markdown: |-
Hi @gfm_user - thank you for reporting this bug (#1) we hope to fix it in %1.1 as part of !1
html: |-
<p data-sourcepos="1:1-1:92" dir="auto">Hi <a href="/gfm_user" data-user="1" data-reference-type="user" data-container="body" data-placement="top" class="gfm gfm-project_member js-user-link" title="John Doe1">@gfm_user</a> - thank you for reporting this bug (<a href="/group1/project1/-/issues/1" data-original="#1" data-link="false" data-link-reference="false" data-project="11" data-issue="11" data-project-path="group1/project1" data-iid="1" data-issue-type="issue" data-reference-type="issue" data-container="body" data-placement="top" title="My title 1" class="gfm gfm-issue">#1</a>) we hope to fix it in <a href="/group1/project1/-/milestones/1" data-original="%1.1" data-link="false" data-link-reference="false" data-project="11" data-milestone="11" data-reference-type="milestone" data-container="body" data-placement="top" title="" class="gfm gfm-milestone has-tooltip">%1.1</a> as part of <a href="/group1/project1/-/merge_requests/1" data-original="!1" data-link="false" data-link-reference="false" data-project="11" data-merge-request="11" data-project-path="group1/project1" data-iid="1" data-reference-type="merge_request" data-container="body" data-placement="top" title="My title 2" class="gfm gfm-merge_request">!1</a></p>
<p data-sourcepos="1:1-1:92" dir="auto">Hi <a href="/gfm_user" data-reference-type="user" data-user="1" data-container="body" data-placement="top" class="gfm gfm-project_member js-user-link" title="John Doe1">@gfm_user</a> - thank you for reporting this bug (<a href="/group1/project1/-/issues/1" data-reference-type="issue" data-original="#1" data-link="false" data-link-reference="false" data-project="11" data-issue="11" data-project-path="group1/project1" data-iid="1" data-issue-type="issue" data-container="body" data-placement="top" title="My title 1" class="gfm gfm-issue">#1</a>) we hope to fix it in <a href="/group1/project1/-/milestones/1" data-reference-type="milestone" data-original="%1.1" data-link="false" data-link-reference="false" data-project="11" data-milestone="11" data-container="body" data-placement="top" title="" class="gfm gfm-milestone has-tooltip">%1.1</a> as part of <a href="/group1/project1/-/merge_requests/1" data-reference-type="merge_request" data-original="!1" data-link="false" data-link-reference="false" data-project="11" data-merge-request="11" data-project-path="group1/project1" data-iid="1" data-container="body" data-placement="top" title="My title 2" class="gfm gfm-merge_request">!1</a></p>
- name: strike
markdown: |-
~~del~~

View File

@ -302,6 +302,33 @@ describe('Pipelines table in Commits and Merge requests', () => {
expect(findModal()).not.toBeNull();
});
});
describe('when no pipelines were created on a forked merge request', () => {
beforeEach(async () => {
mock.onGet('endpoint.json').reply(200, []);
createComponent({
projectId: '5',
mergeRequestId: 3,
canCreatePipelineInTargetProject: true,
sourceProjectFullPath: 'test/parent-project',
targetProjectFullPath: 'test/fork-project',
});
jest.spyOn(findModal().vm, 'show').mockReturnValue();
await waitForPromises();
});
it('should show security modal from empty state run pipeline button', () => {
expect(findEmptyState().exists()).toBe(true);
expect(findModal().exists()).toBe(true);
findRunPipelineBtn().trigger('click');
expect(findModal().vm.show).toHaveBeenCalled();
});
});
});
describe('unsuccessfull request', () => {

View File

@ -4,6 +4,7 @@ import Blockquote from '~/content_editor/extensions/blockquote';
import BulletList from '~/content_editor/extensions/bullet_list';
import Code from '~/content_editor/extensions/code';
import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight';
import Diagram from '~/content_editor/extensions/diagram';
import FootnoteDefinition from '~/content_editor/extensions/footnote_definition';
import FootnoteReference from '~/content_editor/extensions/footnote_reference';
import Frontmatter from '~/content_editor/extensions/frontmatter';
@ -29,7 +30,7 @@ import TaskItem from '~/content_editor/extensions/task_item';
import Video from '~/content_editor/extensions/video';
import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
import markdownSerializer from '~/content_editor/services/markdown_serializer';
import { SAFE_VIDEO_EXT, SAFE_AUDIO_EXT } from '~/content_editor/constants';
import { SAFE_VIDEO_EXT, SAFE_AUDIO_EXT, DIAGRAM_LANGUAGES } from '~/content_editor/constants';
import { createTestEditor, createDocBuilder } from './test_utils';
@ -41,6 +42,7 @@ const tiptapEditor = createTestEditor({
BulletList,
Code,
CodeBlockHighlight,
Diagram,
FootnoteDefinition,
FootnoteReference,
Frontmatter,
@ -77,6 +79,7 @@ const {
code,
codeBlock,
div,
diagram,
footnoteDefinition,
footnoteReference,
frontmatter,
@ -108,6 +111,7 @@ const {
bulletList: { nodeType: BulletList.name },
code: { markType: Code.name },
codeBlock: { nodeType: CodeBlockHighlight.name },
diagram: { nodeType: Diagram.name },
footnoteDefinition: { nodeType: FootnoteDefinition.name },
footnoteReference: { nodeType: FootnoteReference.name },
frontmatter: { nodeType: Frontmatter.name },
@ -1280,6 +1284,16 @@ title: 'layout'
),
};
}),
...DIAGRAM_LANGUAGES.map((language) => {
const markdown = `\`\`\`${language}
content
\`\`\``;
return {
markdown,
expectedDoc: doc(diagram({ ...source(markdown), language }, 'content')),
};
}),
];
const runOnly = examples.find((example) => example.only === true);

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::TablesSortedByForeignKeys do
let(:connection) { ApplicationRecord.connection }
let(:tables) { %w[_test_gitlab_main_items _test_gitlab_main_references] }
subject do
described_class.new(connection, tables).execute
end
before do
statement = <<~SQL
CREATE TABLE _test_gitlab_main_items (id serial NOT NULL PRIMARY KEY);
CREATE TABLE _test_gitlab_main_references (
id serial NOT NULL PRIMARY KEY,
item_id BIGINT NOT NULL,
CONSTRAINT fk_constrained_1 FOREIGN KEY(item_id) REFERENCES _test_gitlab_main_items(id)
);
SQL
connection.execute(statement)
end
describe '#execute' do
it 'returns the tables sorted by the foreign keys dependency' do
expect(subject).to eq([['_test_gitlab_main_references'], ['_test_gitlab_main_items']])
end
it 'returns both tables together if they are strongly connected' do
statement = <<~SQL
ALTER TABLE _test_gitlab_main_items ADD COLUMN reference_id BIGINT
REFERENCES _test_gitlab_main_references(id)
SQL
connection.execute(statement)
expect(subject).to eq([tables])
end
end
end

View File

@ -0,0 +1,255 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Database::TablesTruncate, :reestablished_active_record_base,
:suppress_gitlab_schemas_validate_connection do
include MigrationsHelpers
let(:logger) { instance_double(Logger) }
let(:dry_run) { false }
let(:until_table) { nil }
let(:min_batch_size) { 1 }
let(:main_connection) { ApplicationRecord.connection }
let(:ci_connection) { Ci::ApplicationRecord.connection }
let(:test_gitlab_main_table) { '_test_gitlab_main_table' }
let(:test_gitlab_ci_table) { '_test_gitlab_ci_table' }
# Main Database
let(:main_db_main_item_model) { table("_test_gitlab_main_items", database: "main") }
let(:main_db_main_reference_model) { table("_test_gitlab_main_references", database: "main") }
let(:main_db_ci_item_model) { table("_test_gitlab_ci_items", database: "main") }
let(:main_db_ci_reference_model) { table("_test_gitlab_ci_references", database: "main") }
let(:main_db_shared_item_model) { table("_test_gitlab_shared_items", database: "main") }
# CI Database
let(:ci_db_main_item_model) { table("_test_gitlab_main_items", database: "ci") }
let(:ci_db_main_reference_model) { table("_test_gitlab_main_references", database: "ci") }
let(:ci_db_ci_item_model) { table("_test_gitlab_ci_items", database: "ci") }
let(:ci_db_ci_reference_model) { table("_test_gitlab_ci_references", database: "ci") }
let(:ci_db_shared_item_model) { table("_test_gitlab_shared_items", database: "ci") }
subject(:truncate_legacy_tables) do
described_class.new(
database_name: database_name,
min_batch_size: min_batch_size,
logger: logger,
dry_run: dry_run,
until_table: until_table
).execute
end
shared_examples 'truncating legacy tables on a database' do
before do
skip_if_multiple_databases_not_setup
# Creating some test tables on the main database
main_tables_sql = <<~SQL
CREATE TABLE _test_gitlab_main_items (id serial NOT NULL PRIMARY KEY);
CREATE TABLE _test_gitlab_main_references (
id serial NOT NULL PRIMARY KEY,
item_id BIGINT NOT NULL,
CONSTRAINT fk_constrained_1 FOREIGN KEY(item_id) REFERENCES _test_gitlab_main_items(id)
);
SQL
ApplicationRecord.connection.execute(main_tables_sql)
Ci::ApplicationRecord.connection.execute(main_tables_sql)
ci_tables_sql = <<~SQL
CREATE TABLE _test_gitlab_ci_items (id serial NOT NULL PRIMARY KEY);
CREATE TABLE _test_gitlab_ci_references (
id serial NOT NULL PRIMARY KEY,
item_id BIGINT NOT NULL,
CONSTRAINT fk_constrained_1 FOREIGN KEY(item_id) REFERENCES _test_gitlab_ci_items(id)
);
SQL
ApplicationRecord.connection.execute(ci_tables_sql)
Ci::ApplicationRecord.connection.execute(ci_tables_sql)
internal_tables_sql = <<~SQL
CREATE TABLE _test_gitlab_shared_items (id serial NOT NULL PRIMARY KEY);
SQL
ApplicationRecord.connection.execute(internal_tables_sql)
Ci::ApplicationRecord.connection.execute(internal_tables_sql)
# Filling the tables
5.times do |i|
# Main Database
main_db_main_item_model.create!(id: i)
main_db_main_reference_model.create!(item_id: i)
main_db_ci_item_model.create!(id: i)
main_db_ci_reference_model.create!(item_id: i)
main_db_shared_item_model.create!(id: i)
# CI Database
ci_db_main_item_model.create!(id: i)
ci_db_main_reference_model.create!(item_id: i)
ci_db_ci_item_model.create!(id: i)
ci_db_ci_reference_model.create!(item_id: i)
ci_db_shared_item_model.create!(id: i)
end
allow(Gitlab::Database::GitlabSchema).to receive(:tables_to_schema).and_return(
{
"_test_gitlab_main_items" => :gitlab_main,
"_test_gitlab_main_references" => :gitlab_main,
"_test_gitlab_ci_items" => :gitlab_ci,
"_test_gitlab_ci_references" => :gitlab_ci,
"_test_gitlab_shared_items" => :gitlab_shared,
"_test_gitlab_geo_items" => :gitlab_geo
}
)
allow(logger).to receive(:info).with(any_args)
end
context 'when the truncated tables are not locked for writes' do
it 'raises an error that the tables are not locked for writes' do
error_message = /is not locked for writes. Run the rake task gitlab:db:lock_writes first/
expect { truncate_legacy_tables }.to raise_error(error_message)
end
end
context 'when the truncated tables are locked for writes' do
before do
legacy_tables_models.map(&:table_name).each do |table|
Gitlab::Database::LockWritesManager.new(
table_name: table,
connection: connection,
database_name: database_name
).lock_writes
end
end
it 'truncates the legacy tables' do
old_counts = legacy_tables_models.map(&:count)
expect do
truncate_legacy_tables
end.to change { legacy_tables_models.map(&:count) }.from(old_counts).to([0] * legacy_tables_models.length)
end
it 'does not affect the other tables' do
expect do
truncate_legacy_tables
end.not_to change { other_tables_models.map(&:count) }
end
it 'logs the sql statements to the logger' do
expect(logger).to receive(:info)
.with(/TRUNCATE TABLE #{legacy_tables_models.map(&:table_name).sort.join(', ')} RESTRICT/)
truncate_legacy_tables
end
context 'when running in dry_run mode' do
let(:dry_run) { true }
it 'does not truncate the legacy tables if running in dry run mode' do
legacy_tables_models = [main_db_ci_reference_model, main_db_ci_reference_model]
expect do
truncate_legacy_tables
end.not_to change { legacy_tables_models.map(&:count) }
end
end
context 'when passing until_table parameter' do
context 'with a table that exists' do
let(:until_table) { referencing_table_model.table_name }
it 'only truncates until the table specified' do
expect do
truncate_legacy_tables
end.to change(referencing_table_model, :count).by(-5)
.and change(referenced_table_model, :count).by(0)
end
end
context 'with a table that does not exist' do
let(:until_table) { 'foobar' }
it 'raises an error if the specified table does not exist' do
expect do
truncate_legacy_tables
end.to raise_error(/The table 'foobar' is not within the truncated tables/)
end
end
end
context 'with geo configured' do
let(:geo_connection) { Gitlab::Database.database_base_models[:geo].connection }
before do
skip unless geo_configured?
geo_connection.execute('CREATE TABLE _test_gitlab_geo_items (id serial NOT NULL PRIMARY KEY)')
geo_connection.execute('INSERT INTO _test_gitlab_geo_items VALUES(generate_series(1, 50))')
end
it 'does not truncate gitlab_geo tables' do
expect do
truncate_legacy_tables
end.not_to change { geo_connection.select_value("select count(*) from _test_gitlab_geo_items") }
end
end
end
end
context 'when truncating gitlab_ci tables on the main database' do
let(:connection) { ApplicationRecord.connection }
let(:database_name) { "main" }
let(:legacy_tables_models) { [main_db_ci_item_model, main_db_ci_reference_model] }
let(:referencing_table_model) { main_db_ci_reference_model }
let(:referenced_table_model) { main_db_ci_item_model }
let(:other_tables_models) do
[
main_db_main_item_model, main_db_main_reference_model,
ci_db_ci_item_model, ci_db_ci_reference_model,
ci_db_main_item_model, ci_db_main_reference_model,
main_db_shared_item_model, ci_db_shared_item_model
]
end
it_behaves_like 'truncating legacy tables on a database'
end
context 'when truncating gitlab_main tables on the ci database' do
let(:connection) { Ci::ApplicationRecord.connection }
let(:database_name) { "ci" }
let(:legacy_tables_models) { [ci_db_main_item_model, ci_db_main_reference_model] }
let(:referencing_table_model) { ci_db_main_reference_model }
let(:referenced_table_model) { ci_db_main_item_model }
let(:other_tables_models) do
[
main_db_main_item_model, main_db_main_reference_model,
ci_db_ci_item_model, ci_db_ci_reference_model,
main_db_ci_item_model, main_db_ci_reference_model,
main_db_shared_item_model, ci_db_shared_item_model
]
end
it_behaves_like 'truncating legacy tables on a database'
end
context 'when running in a single database mode' do
before do
skip_if_multiple_databases_are_setup
end
it 'raises an error when truncating the main database that it is a single database setup' do
expect do
described_class.new(database_name: 'main', min_batch_size: min_batch_size).execute
end.to raise_error(/Cannot truncate legacy tables in single-db setup/)
end
it 'raises an error when truncating the ci database that it is a single database setup' do
expect do
described_class.new(database_name: 'ci', min_batch_size: min_batch_size).execute
end.to raise_error(/Cannot truncate legacy tables in single-db setup/)
end
end
def geo_configured?
!!ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: 'geo')
end
end

View File

@ -30,9 +30,28 @@ module Support
@todo.include?(path)
end
# Adds '# order <ORDER>` below the example group description if the order
# has been set to help debugging in case of failure.
#
# Previously, we've modified metadata[:description] directly but that led
# to bugs. See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96137
module DocumentationFormatterPatch
# See https://github.com/rspec/rspec-core/blob/v3.11.0/lib/rspec/core/formatters/documentation_formatter.rb#L24-L29
def example_group_started(notification)
super
order = notification.group.metadata[:order]
return unless order
output.puts "#{current_indentation}# order #{order}"
end
end
end
end
RSpec::Core::Formatters::DocumentationFormatter.prepend Support::RspecOrder::DocumentationFormatterPatch
RSpec.configure do |config|
# Useful to find order-dependent specs.
config.register_ordering(:reverse, &:reverse)

View File

@ -0,0 +1,157 @@
# frozen_string_literal: true
require 'rake_helper'
RSpec.describe 'gitlab:db:truncate_legacy_tables', :silence_stdout, :reestablished_active_record_base,
:suppress_gitlab_schemas_validate_connection do
let(:main_connection) { ApplicationRecord.connection }
let(:ci_connection) { Ci::ApplicationRecord.connection }
let(:test_gitlab_main_table) { '_test_gitlab_main_table' }
let(:test_gitlab_ci_table) { '_test_gitlab_ci_table' }
before :all do
Rake.application.rake_require 'active_record/railties/databases'
Rake.application.rake_require 'tasks/seed_fu'
Rake.application.rake_require 'tasks/gitlab/db/validate_config'
Rake.application.rake_require 'tasks/gitlab/db/truncate_legacy_tables'
# empty task as env is already loaded
Rake::Task.define_task :environment
end
before do
skip_if_multiple_databases_not_setup
# Filling the table on both databases main and ci
Gitlab::Database.database_base_models.each_value do |base_model|
base_model.connection.execute(<<~SQL)
CREATE TABLE #{test_gitlab_main_table} (id integer NOT NULL);
INSERT INTO #{test_gitlab_main_table} VALUES(generate_series(1, 50));
SQL
base_model.connection.execute(<<~SQL)
CREATE TABLE #{test_gitlab_ci_table} (id integer NOT NULL);
INSERT INTO #{test_gitlab_ci_table} VALUES(generate_series(1, 50));
SQL
end
allow(Gitlab::Database::GitlabSchema).to receive(:tables_to_schema).and_return(
{
test_gitlab_main_table => :gitlab_main,
test_gitlab_ci_table => :gitlab_ci
}
)
end
shared_examples 'truncating legacy tables' do
before do
allow(ENV).to receive(:[]).and_return(nil)
end
context 'when tables are not locked for writes' do
it 'raises an error when trying to truncate the tables' do
error_message = /is not locked for writes. Run the rake task gitlab:db:lock_writes first/
expect { truncate_legacy_tables }.to raise_error(error_message)
end
end
context 'when tables are locked for writes' do
before do
# Locking ci table on the main database
Gitlab::Database::LockWritesManager.new(
table_name: test_gitlab_ci_table,
connection: main_connection,
database_name: "main"
).lock_writes
# Locking main table on the ci database
Gitlab::Database::LockWritesManager.new(
table_name: test_gitlab_main_table,
connection: ci_connection,
database_name: "ci"
).lock_writes
end
it 'calls TablesTruncate with the correct parameters and default minimum batch size' do
expect(Gitlab::Database::TablesTruncate).to receive(:new).with(
database_name: database_name,
min_batch_size: 5,
logger: anything,
dry_run: false,
until_table: nil
).and_call_original
truncate_legacy_tables
end
it 'truncates the legacy table' do
expect do
truncate_legacy_tables
end.to change { connection.select_value("SELECT count(*) from #{legacy_table}") }.from(50).to(0)
end
it 'does not truncate the table that belongs to the connection schema' do
expect do
truncate_legacy_tables
end.not_to change { connection.select_value("SELECT count(*) from #{active_table}") }
end
context 'when running in dry_run mode' do
before do
allow(ENV).to receive(:[]).with("DRY_RUN").and_return("true")
end
it 'does not truncate any tables' do
expect do
truncate_legacy_tables
end.not_to change { connection.select_value("SELECT count(*) from #{legacy_table}") }
end
it 'prints the truncation sql statement to the output' do
expect do
truncate_legacy_tables
end.to output(/TRUNCATE TABLE #{legacy_table} RESTRICT/).to_stdout
end
end
context 'when passing until_table parameter via environment variable' do
before do
allow(ENV).to receive(:[]).with("UNTIL_TABLE").and_return(legacy_table)
end
it 'sends the table name to TablesTruncate' do
expect(Gitlab::Database::TablesTruncate).to receive(:new).with(
database_name: database_name,
min_batch_size: 5,
logger: anything,
dry_run: false,
until_table: legacy_table
).and_call_original
truncate_legacy_tables
end
end
end
end
context 'when truncating ci tables on the main database' do
subject(:truncate_legacy_tables) { run_rake_task('gitlab:db:truncate_legacy_tables:main') }
let(:connection) { ApplicationRecord.connection }
let(:database_name) { 'main' }
let(:active_table) { test_gitlab_main_table }
let(:legacy_table) { test_gitlab_ci_table }
it_behaves_like 'truncating legacy tables'
end
context 'when truncating main tables on the ci database' do
subject(:truncate_legacy_tables) { run_rake_task('gitlab:db:truncate_legacy_tables:ci') }
let(:connection) { Ci::ApplicationRecord.connection }
let(:database_name) { 'ci' }
let(:active_table) { test_gitlab_ci_table }
let(:legacy_table) { test_gitlab_main_table }
it_behaves_like 'truncating legacy tables'
end
end