Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-06 15:09:05 +00:00
parent 2e31a394c5
commit c79da5142f
23 changed files with 261 additions and 31 deletions

View File

@ -16,6 +16,10 @@ import Paragraph from './paragraph';
import Strike from './strike';
import TaskList from './task_list';
import TaskItem from './task_item';
import Table from './table';
import TableCell from './table_cell';
import TableHeader from './table_header';
import TableRow from './table_row';
export default Extension.create({
addGlobalAttributes() {
@ -39,6 +43,10 @@ export default Extension.create({
Strike.name,
TaskList.name,
TaskItem.name,
Table.name,
TableCell.name,
TableHeader.name,
TableRow.name,
],
attributes: {
sourceMarkdown: {

View File

@ -22,7 +22,7 @@
import { Mark } from 'prosemirror-model';
import { visitParents } from 'unist-util-visit-parents';
import { toString } from 'hast-util-to-string';
import { isFunction, noop } from 'lodash';
import { isFunction, isString, noop } from 'lodash';
/**
* Merges two ProseMirror text nodes if both text nodes
@ -290,6 +290,7 @@ const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, source)
selector: factorySpec.selector,
skipChildren: factorySpec.skipChildren,
processText: factorySpec.processText,
parent: factorySpec.parent,
};
if (factorySpec.type === 'block') {
@ -361,6 +362,14 @@ const findFactory = (hastNode, ancestors, factories) =>
: [hastNode.tagName, hastNode.type].includes(selector);
})?.[1];
const findParent = (ancestors, parent) => {
if (isString(parent)) {
return ancestors.reverse().find((ancestor) => ancestor.tagName === parent);
}
return ancestors[ancestors.length - 1];
};
/**
* Converts a Hast AST to a ProseMirror document based on a series
* of specifications that describe how to map all the nodes of the former
@ -461,6 +470,13 @@ const findFactory = (hastNode, ancestors, factories) =>
* Use this property along skipChildren to provide custom processing of child nodes
* for a block node.
*
* **parent**
*
* Specifies what is the nodes parent. This is useful when the nodes parent is not
* its direct ancestor in Abstract Syntax Tree. For example, imagine that you want
* to make <tr> elements a direct children of tables and skip `<thead>` and `<tbody>`
* altogether.
*
* @param {model.Document_Schema} params.schema A ProseMirror schema that specifies the shape
* of the ProseMirror document.
* @param {Object} params.factorySpec A factory specification as described above
@ -475,7 +491,6 @@ export const createProseMirrorDocFromMdastTree = ({ schema, factorySpecs, tree,
visitParents(tree, (hastNode, ancestors) => {
const factory = findFactory(hastNode, ancestors, proseMirrorNodeFactories);
const parent = ancestors[ancestors.length - 1];
if (!factory) {
throw new Error(
@ -485,6 +500,8 @@ export const createProseMirrorDocFromMdastTree = ({ schema, factorySpecs, tree,
);
}
const parent = findParent(ancestors, factory.parent);
factory.handle(state, hastNode, parent);
return factory.skipChildren === true ? 'skip' : true;

View File

@ -193,7 +193,7 @@ const defaultSerializerConfig = {
state.write('[[_TOC_]]');
state.closeBlock(node);
},
[Table.name]: renderTable,
[Table.name]: preserveUnchanged(renderTable),
[TableCell.name]: renderTableCell,
[TableHeader.name]: renderTableCell,
[TableRow.name]: renderTableRow,

View File

@ -10,6 +10,11 @@ const isTaskItem = (hastNode) => {
);
};
const getTableCellAttrs = (hastNode) => ({
colspan: parseInt(hastNode.properties.colSpan, 10) || 1,
rowspan: parseInt(hastNode.properties.rowSpan, 10) || 1,
});
const factorySpecs = {
blockquote: { type: 'block', selector: 'blockquote' },
paragraph: { type: 'block', selector: 'p' },
@ -81,6 +86,31 @@ const factorySpecs = {
selector: (hastNode, ancestors) =>
hastNode.tagName === 'input' && isTaskItem(ancestors[ancestors.length - 1]),
},
table: {
type: 'block',
selector: 'table',
},
tableRow: {
type: 'block',
selector: 'tr',
parent: 'table',
},
tableHeader: {
type: 'block',
selector: 'th',
getAttrs: getTableCellAttrs,
wrapTextInParagraph: true,
},
tableCell: {
type: 'block',
selector: 'td',
getAttrs: getTableCellAttrs,
wrapTextInParagraph: true,
},
ignoredTableNodes: {
type: 'ignore',
selector: (hastNode) => ['thead', 'tbody', 'tfoot'].includes(hastNode.tagName),
},
image: {
type: 'inline',
selector: 'img',

View File

@ -1,4 +1,4 @@
import { uniq, isString } from 'lodash';
import { uniq, isString, omit } from 'lodash';
const defaultAttrs = {
td: { colspan: 1, rowspan: 1, colwidth: null },
@ -219,7 +219,7 @@ function renderTableRowAsHTML(state, node) {
node.forEach((cell, _, i) => {
const tag = cell.type.name === 'tableHeader' ? 'th' : 'td';
renderTagOpen(state, tag, cell.attrs);
renderTagOpen(state, tag, omit(cell.attrs, 'sourceMapKey', 'sourceMarkdown'));
if (!containsParagraphWithOnlyText(cell)) {
state.closeBlock(node);

View File

@ -1,5 +1,5 @@
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlButton } from '@gitlab/ui';
import { GlSkeletonLoader, GlButton } from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { sprintf, __ } from '~/locale';
import getRefMixin from '../../mixins/get_ref';
@ -10,7 +10,7 @@ import TableRow from './row.vue';
export default {
components: {
GlSkeletonLoading,
GlSkeletonLoader,
TableHeader,
TableRow,
ParentRow,
@ -158,11 +158,15 @@ export default {
</template>
<template v-if="isLoading">
<tr v-for="i in 5" :key="i" aria-hidden="true">
<td><gl-skeleton-loading :lines="1" class="h-auto" /></td>
<td><gl-skeleton-loader :lines="1" /></td>
<td class="gl-display-none gl-sm-display-block">
<gl-skeleton-loading :lines="1" class="h-auto" />
<gl-skeleton-loader :lines="1" />
</td>
<td>
<div class="gl-display-flex gl-lg-justify-content-end">
<gl-skeleton-loader :equal-width-lines="true" :lines="1" />
</div>
</td>
<td><gl-skeleton-loading :lines="1" class="ml-auto h-auto w-50" /></td>
</tr>
</template>
<template v-if="hasMore">

View File

@ -34,7 +34,7 @@ export default {
<div class="gl-display-flex gl-flex-direction-column gl-xs-mb-3 gl-min-w-0 gl-flex-grow-1">
<div
v-if="$slots['left-primary-text']"
class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0 gl-mb-4"
class="gl-display-flex gl-align-items-center gl-text-body gl-font-weight-bold gl-min-h-6 gl-min-w-0"
>
<slot name="left-primary-text"></slot>
</div>

View File

@ -56,7 +56,7 @@
z-index: $zindex-dropdown-menu;
&.right-sidebar-merge-requests {
width: 270px;
width: 300px;
@include media-breakpoint-up(md) {
z-index: auto;

View File

@ -739,7 +739,7 @@ $tabs-holder-z-index: 250;
.merge-request-overview {
@include media-breakpoint-up(lg) {
display: grid;
grid-template-columns: calc(95% - 270px) auto;
grid-template-columns: calc(95% - 285px) auto;
grid-gap: 5%;
}
}

View File

@ -290,6 +290,15 @@
&.is-merge-request {
@include media-breakpoint-up(lg) {
padding: 0;
form {
position: sticky;
top: 100px;
height: calc(100vh - 100px);
overflow: scroll;
padding: 0 15px;
margin-bottom: -100px;
}
}
}
}

View File

@ -53,6 +53,10 @@ module Types
description: 'Timestamp this to-do item was created.',
null: false
field :note, Types::Notes::NoteType,
description: 'Note which created this to-do item.',
null: true
def project
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end

View File

@ -62,6 +62,11 @@ Gitlab::Cluster::LifecycleEvents.on_master_start do
Gitlab::Metrics.gauge(:deployments, 'GitLab Version', {}, :max).set({ version: Gitlab::VERSION, revision: Gitlab.revision }, 1)
if Gitlab::Runtime.puma?
[
Gitlab::Metrics::Samplers::RubySampler,
Gitlab::Metrics::Samplers::ThreadsSampler
].each { |sampler| sampler.instance(logger: Gitlab::AppLogger).start }
Gitlab::Metrics::Samplers::PumaSampler.instance.start
MetricsServer.start_for_puma if puma_dedicated_metrics_server?
@ -76,9 +81,14 @@ end
Gitlab::Cluster::LifecycleEvents.on_worker_start do
defined?(::Prometheus::Client.reinitialize_on_pid_change) && ::Prometheus::Client.reinitialize_on_pid_change
logger = Gitlab::AppLogger
Gitlab::Metrics::Samplers::RubySampler.initialize_instance(logger: logger).start
# Since we also run these samplers in the Puma primary, we need to re-create them each time we fork.
# For Sidekiq, this does not make any difference, since there is no primary.
[
Gitlab::Metrics::Samplers::RubySampler,
Gitlab::Metrics::Samplers::ThreadsSampler
].each { |sampler| sampler.initialize_instance(logger: logger, recreate: true).start }
Gitlab::Metrics::Samplers::DatabaseSampler.initialize_instance(logger: logger).start
Gitlab::Metrics::Samplers::ThreadsSampler.initialize_instance(logger: logger).start
if Gitlab::Runtime.puma?
# Since we are observing a metrics server from the Puma primary, we would inherit

View File

@ -15555,6 +15555,7 @@ Returns [`[SecurityTrainingUrl!]`](#securitytrainingurl).
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="projectsecuritytrainingurlsfilename"></a>`filename` | [`String`](#string) | Filename to filter security training URLs by programming language. |
| <a id="projectsecuritytrainingurlsidentifierexternalids"></a>`identifierExternalIds` | [`[String!]!`](#string) | List of external IDs of vulnerability identifiers. |
| <a id="projectsecuritytrainingurlslanguage"></a>`language` | [`String`](#string) | Desired language for training urls. |
@ -17000,6 +17001,7 @@ Representing a to-do entry.
| <a id="todocreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp this to-do item was created. |
| <a id="todogroup"></a>`group` | [`Group`](#group) | Group this to-do item is associated with. |
| <a id="todoid"></a>`id` | [`ID!`](#id) | ID of the to-do item. |
| <a id="todonote"></a>`note` | [`Note`](#note) | Note which created this to-do item. |
| <a id="todoproject"></a>`project` | [`Project`](#project) | Project this to-do item is associated with. |
| <a id="todostate"></a>`state` | [`TodoStateEnum!`](#todostateenum) | State of the to-do item. |
| <a id="todotarget"></a>`target` | [`Todoable!`](#todoable) | Target of the to-do item. |

View File

@ -1179,7 +1179,7 @@ This is how it renders on the GitLab documentation site:
> Notes:
>
> - The `figure` tag is required for semantic SEO and the `video_container`
> - The `figure` tag is required for semantic SEO and the `video-container`
class is necessary to make sure the video is responsive and displays on
different mobile devices.
> - The `<div class="video-fallback">` is a fallback necessary for

View File

@ -60,7 +60,7 @@ module.exports = (path, options = {}) => {
'emojis(/.*).json': '<rootDir>/fixtures/emojis$1.json',
'^spec/test_constants$': '<rootDir>/spec/frontend/__helpers__/test_constants',
'^jest/(.*)$': '<rootDir>/spec/frontend/$1',
'^any_jest/(.*)$': '<rootDir>/spec/frontend/$1',
'^ee_else_ce_jest/(.*)$': '<rootDir>/spec/frontend/$1',
'^jquery$': '<rootDir>/node_modules/jquery/dist/jquery.slim.js',
...extModuleNameMapper,
};
@ -75,7 +75,7 @@ module.exports = (path, options = {}) => {
'^ee_component(/.*)$': rootDirEE,
'^ee_else_ce(/.*)$': rootDirEE,
'^ee_jest/(.*)$': specDirEE,
'^any_jest/(.*)$': specDirEE,
'^ee_else_ce_jest/(.*)$': specDirEE,
'^any_else_ce(/.*)$': rootDirEE,
'^jh_else_ee(/.*)$': rootDirEE,
[TEST_FIXTURES_PATTERN]: '<rootDir>/tmp/tests/frontend/fixtures-ee$1',
@ -86,13 +86,13 @@ module.exports = (path, options = {}) => {
}
if (IS_JH) {
// DO NOT add additional path to Jihu side, it might break things.
const rootDirJH = '<rootDir>/jh/app/assets/javascripts$1';
const specDirJH = '<rootDir>/jh/spec/frontend/$1';
Object.assign(moduleNameMapper, {
'^jh(/.*)$': rootDirJH,
'^jh_component(/.*)$': rootDirJH,
'^jh_jest/(.*)$': specDirJH,
'^any_jest/(.*)$': specDirJH,
// jh path alias https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74305#note_732793956
'^jh_else_ce(/.*)$': rootDirJH,
'^jh_else_ee(/.*)$': rootDirJH,

View File

@ -2,10 +2,20 @@
module Gitlab
class Daemon
def self.initialize_instance(...)
raise "#{name} singleton instance already initialized" if @instance
# Options:
# - recreate: We usually only allow a single instance per process to exist;
# this can be overridden with this switch, so that existing
# instances are stopped and recreated.
def self.initialize_instance(*args, recreate: false, **options)
if @instance
if recreate
@instance.stop
else
raise "#{name} singleton instance already initialized"
end
end
@instance = new(...)
@instance = new(*args, **options)
Kernel.at_exit(&@instance.method(:stop))
@instance
end

View File

@ -40864,6 +40864,9 @@ msgstr ""
msgid "UsageQuota|Gitlab-integrated Docker Container Registry for storing Docker Images."
msgstr ""
msgid "UsageQuota|Gitlab-integrated Docker Container Registry for storing Docker Images. %{linkStart}More information%{linkEnd}"
msgstr ""
msgid "UsageQuota|Includes artifacts, repositories, wiki, uploads, and other items."
msgstr ""

View File

@ -14,6 +14,10 @@ import OrderedList from '~/content_editor/extensions/ordered_list';
import Paragraph from '~/content_editor/extensions/paragraph';
import Sourcemap from '~/content_editor/extensions/sourcemap';
import Strike from '~/content_editor/extensions/strike';
import Table from '~/content_editor/extensions/table';
import TableHeader from '~/content_editor/extensions/table_header';
import TableRow from '~/content_editor/extensions/table_row';
import TableCell from '~/content_editor/extensions/table_cell';
import TaskList from '~/content_editor/extensions/task_list';
import TaskItem from '~/content_editor/extensions/task_item';
import remarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer';
@ -38,6 +42,10 @@ const tiptapEditor = createTestEditor({
OrderedList,
Sourcemap,
Strike,
Table,
TableRow,
TableHeader,
TableCell,
TaskList,
TaskItem,
],
@ -61,6 +69,10 @@ const {
listItem,
orderedList,
strike,
table,
tableRow,
tableHeader,
tableCell,
taskItem,
taskList,
},
@ -82,6 +94,10 @@ const {
orderedList: { nodeType: OrderedList.name },
paragraph: { nodeType: Paragraph.name },
strike: { nodeType: Strike.name },
table: { nodeType: Table.name },
tableCell: { nodeType: TableCell.name },
tableHeader: { nodeType: TableHeader.name },
tableRow: { nodeType: TableRow.name },
taskItem: { nodeType: TaskItem.name },
taskList: { nodeType: TaskList.name },
},
@ -759,6 +775,70 @@ const fn = () => 'GitLab';
),
),
},
{
markdown: `
| a | b |
|---|---|
| c | d |
`,
expectedDoc: doc(
table(
sourceAttrs('0:29', '| a | b |\n|---|---|\n| c | d |'),
tableRow(
sourceAttrs('0:9', '| a | b |'),
tableHeader(sourceAttrs('0:5', '| a |'), paragraph(sourceAttrs('0:5', '| a |'), 'a')),
tableHeader(sourceAttrs('5:9', ' b |'), paragraph(sourceAttrs('5:9', ' b |'), 'b')),
),
tableRow(
sourceAttrs('20:29', '| c | d |'),
tableCell(sourceAttrs('20:25', '| c |'), paragraph(sourceAttrs('20:25', '| c |'), 'c')),
tableCell(sourceAttrs('25:29', ' d |'), paragraph(sourceAttrs('25:29', ' d |'), 'd')),
),
),
),
},
{
markdown: `
<table>
<tr>
<th colspan="2" rowspan="5">Header</th>
</tr>
<tr>
<td colspan="2" rowspan="5">Body</td>
</tr>
</table>
`,
expectedDoc: doc(
table(
sourceAttrs(
'0:132',
'<table>\n <tr>\n <th colspan="2" rowspan="5">Header</th>\n </tr>\n <tr>\n <td colspan="2" rowspan="5">Body</td>\n </tr>\n</table>',
),
tableRow(
sourceAttrs('10:66', '<tr>\n <th colspan="2" rowspan="5">Header</th>\n </tr>'),
tableHeader(
{
...sourceAttrs('19:58', '<th colspan="2" rowspan="5">Header</th>'),
colspan: 2,
rowspan: 5,
},
paragraph(sourceAttrs('19:58', '<th colspan="2" rowspan="5">Header</th>'), 'Header'),
),
),
tableRow(
sourceAttrs('69:123', '<tr>\n <td colspan="2" rowspan="5">Body</td>\n </tr>'),
tableCell(
{
...sourceAttrs('78:115', '<td colspan="2" rowspan="5">Body</td>'),
colspan: 2,
rowspan: 5,
},
paragraph(sourceAttrs('78:115', '<td colspan="2" rowspan="5">Body</td>'), 'Body'),
),
),
),
),
},
])('processes %s correctly', async ({ markdown, expectedDoc }) => {
const trimmed = markdown.trim();
const document = await deserialize(trimmed);

View File

@ -1,4 +1,4 @@
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading, GlButton } from '@gitlab/ui';
import { GlSkeletonLoader, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import Table from '~/repository/components/table/index.vue';
@ -103,7 +103,7 @@ describe('Repository table component', () => {
it('shows loading icon', () => {
factory({ path: '/', isLoading: true });
expect(vm.find(GlSkeletonLoading).exists()).toBe(true);
expect(vm.findComponent(GlSkeletonLoader).exists()).toBe(true);
});
it('renders table rows', () => {

View File

@ -1,6 +1,12 @@
import { createLocalVue } from '@vue/test-utils';
import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import {
fetchData,
fetchError,
mutationData,
mutationError,
} from 'ee_else_ce_jest/sidebar/components/incidents/mock_data';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { mountExtended } from 'helpers/vue_test_utils_helper';
@ -9,12 +15,6 @@ import SidebarEditableItem from '~/sidebar/components/sidebar_editable_item.vue'
import { escalationStatusQuery, escalationStatusMutation } from '~/sidebar/constants';
import waitForPromises from 'helpers/wait_for_promises';
import EscalationStatus from 'ee_else_ce/sidebar/components/incidents/escalation_status.vue';
import {
fetchData,
fetchError,
mutationData,
mutationError,
} from 'any_jest/sidebar/components/incidents/mock_data';
import { STATUS_ACKNOWLEDGED } from '~/sidebar/components/incidents/constants';
import { createAlert } from '~/flash';
import { logError } from '~/lib/logger';

View File

@ -4,7 +4,19 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Todo'] do
it 'has the correct fields' do
expected_fields = [:id, :project, :group, :author, :action, :target, :target_type, :body, :state, :created_at]
expected_fields = [
:id,
:project,
:group,
:author,
:action,
:target,
:target_type,
:body,
:state,
:created_at,
:note
]
expect(described_class).to have_graphql_fields(*expected_fields)
end

View File

@ -34,6 +34,43 @@ RSpec.describe Gitlab::Daemon do
end
end
describe '.initialize_instance' do
before do
allow(Kernel).to receive(:at_exit)
end
after do
described_class.instance_variable_set(:@instance, nil)
end
it 'provides instance of Daemon' do
expect(described_class.instance).to be_instance_of(described_class)
end
context 'when instance has already been created' do
before do
described_class.instance
end
context 'and recreate flag is false' do
it 'raises an error' do
expect { described_class.initialize_instance }.to raise_error(/singleton instance already initialized/)
end
end
context 'and recreate flag is true' do
it 'calls stop on existing instance and returns new instance' do
old_instance = described_class.instance
expect(old_instance).to receive(:stop)
new_instance = described_class.initialize_instance(recreate: true)
expect(new_instance.object_id).not_to eq(old_instance.object_id)
end
end
end
end
context 'when Daemon is enabled' do
before do
allow(subject).to receive(:enabled?).and_return(true)

View File

@ -8,6 +8,10 @@ module NamespacesTestHelper
def get_buy_storage_path(namespace)
buy_storage_subscriptions_path(selected_group: namespace.id)
end
def get_buy_storage_url(namespace)
buy_storage_subscriptions_url(selected_group: namespace.id)
end
end
NamespacesTestHelper.prepend_mod