diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-27 03:08:37 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-27 03:08:37 +0300 |
commit | b9a77f953c360d82777f8f1652b5cdf369779605 (patch) | |
tree | 70dba8351315c44e1078d0d144da77aa7ecca12e | |
parent | 413c6d807b260f6086fdd05c01618f459335d465 (diff) |
Add latest changes from gitlab-org/gitlab@master
19 files changed, 902 insertions, 71 deletions
diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index 7c018f78928..39359d7b827 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -77,7 +77,7 @@ review-build-cng: variables: HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}" DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}" - GITLAB_HELM_CHART_REF: "v5.5.0" + GITLAB_HELM_CHART_REF: "v5.10.0" environment: name: review/${CI_COMMIT_REF_SLUG}${FREQUENCY} url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN} diff --git a/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js new file mode 100644 index 00000000000..b6a3e0bc26a --- /dev/null +++ b/app/assets/javascripts/content_editor/services/hast_to_prosemirror_converter.js @@ -0,0 +1,475 @@ +/** + * This module implements a function that converts a Hast Abstract + * Syntax Tree (AST) to a ProseMirror document. + * + * It is based on the prosemirror-markdown’s from_markdown module + * https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/from_markdown.js. + * + * It deviates significantly from the original because + * prosemirror-markdown supports converting an markdown-it AST instead of a + * HAST one. It also adds sourcemap attributes automatically to every + * ProseMirror node and mark created during the conversion process. + * + * We recommend becoming familiar with HAST and ProseMirror documents to + * facilitate the understanding of the behavior implemented in this module. + * + * Unist syntax tree documentation: https://github.com/syntax-tree/unist + * Hast tree documentation: https://github.com/syntax-tree/hast + * ProseMirror document documentation: https://prosemirror.net/docs/ref/#model.Document_Structure + * visit-parents documentation: https://github.com/syntax-tree/unist-util-visit-parents + */ + +import { Mark } from 'prosemirror-model'; +import { visitParents } from 'unist-util-visit-parents'; +import { toString } from 'hast-util-to-string'; +import { isFunction } from 'lodash'; + +/** + * Merges two ProseMirror text nodes if both text nodes + * have the same set of marks. + * + * @param {ProseMirror.Node} a first ProseMirror node + * @param {ProseMirror.Node} b second ProseMirror node + * @returns {model.Node} A new text node that results from combining + * the text of the two text node parameters or null. + */ +function maybeMerge(a, b) { + if (a && a.isText && b && b.isText && Mark.sameSet(a.marks, b.marks)) { + return a.withText(a.text + b.text); + } + + return null; +} + +/** + * Creates an object that contains sourcemap position information + * included in a Hast Abstract Syntax Tree. The Content + * Editor uses the sourcemap information to restore the + * original source of a node when the user doesn’t change it. + * + * Unist syntax tree documentation: https://github.com/syntax-tree/unist + * Hast node documentation: https://github.com/syntax-tree/hast + * + * @param {HastNode} hastNode A Hast node + * @param {String} source Markdown source file + * + * @returns It returns an object with the following attributes: + * + * - sourceMapKey: A string that uniquely identifies what is + * the position of the hast node in the Markdown source file. + * - sourceMarkdown: A node’s original Markdown source extrated + * from the Markdown source file. + */ +function createSourceMapAttributes(hastNode, source) { + const { position } = hastNode; + + return { + sourceMapKey: `${position.start.offset}:${position.end.offset}`, + sourceMarkdown: source.substring(position.start.offset, position.end.offset), + }; +} + +/** + * Compute ProseMirror node’s attributes from a Hast node. + * By default, this function includes sourcemap position + * information in the object returned. + * + * Other attributes are retrieved by invoking a getAttrs + * function provided by the ProseMirror node factory spec. + * + * @param {*} proseMirrorNodeSpec ProseMirror node spec object + * @param {HastNode} hastNode A hast node + * @param {Array<HastNode>} hastParents All the ancestors of the hastNode + * @param {String} source Markdown source file’s content + * + * @returns An object that contains a ProseMirror node’s attributes + */ +function getAttrs(proseMirrorNodeSpec, hastNode, hastParents, source) { + const { getAttrs: specGetAttrs } = proseMirrorNodeSpec; + + return { + ...createSourceMapAttributes(hastNode, source), + ...(isFunction(specGetAttrs) ? specGetAttrs(hastNode, hastParents, source) : {}), + }; +} + +/** + * Keeps track of the Hast -> ProseMirror conversion process. + * + * When the `openNode` method is invoked, it adds the node to a stack + * data structure. When the `closeNode` method is invoked, it removes the + * last element from the Stack, creates a ProseMirror node, and adds that + * ProseMirror node to the previous node in the Stack. + * + * For example, given a Hast tree with three levels of nodes: + * + * - blockquote + * - paragraph + * - text + * + * 3. text + * 2. paragraph + * 1. blockquote + * + * Calling `closeNode` will fold the text node into paragraph. A 2nd + * call to this method will fold "paragraph" into "blockquote". + * + * Mark state + * + * When the `openMark` method is invoked, this class adds the Mark to a `MarkSet` + * object. When a text node is added, it assigns all the opened marks to that text + * node and cleans the marks. It takes care of merging text nodes with the same + * set of marks as well. + */ +class HastToProseMirrorConverterState { + constructor() { + this.stack = []; + this.marks = Mark.none; + } + + /** + * Gets the first element of the node stack + */ + get top() { + return this.stack[this.stack.length - 1]; + } + + /** + * Detects if the node stack is empty + */ + get empty() { + return this.stack.length === 0; + } + + /** + * Creates a text node and adds it to + * the top node in the stack. + * + * It applies the marks stored temporarily + * by calling the `addMark` method. After + * the text node is added, it clears the mark + * set afterward. + * + * If the top block node has a text + * node with the same set of marks as the + * text node created, this method merges + * both text nodes + * + * @param {ProseMirror.Schema} schema ProseMirror schema + * @param {String} text Text + * @returns + */ + addText(schema, text) { + if (!text) return; + const nodes = this.top.content; + const last = nodes[nodes.length - 1]; + const node = schema.text(text, this.marks); + const merged = maybeMerge(last, node); + + if (last && merged) { + nodes[nodes.length - 1] = merged; + } else { + nodes.push(node); + } + + this.closeMarks(); + } + + /** + * Adds a mark to the set of marks stored temporarily + * until addText is called. + * @param {*} markType + * @param {*} attrs + */ + openMark(markType, attrs) { + this.marks = markType.create(attrs).addToSet(this.marks); + } + + /** + * Empties the temporary Mark set. + */ + closeMarks() { + this.marks = Mark.none; + } + + /** + * 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 {*} 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 }); + } + + /** + * 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); + + if (!node) return null; + + if (this.marks.length) { + this.marks = Mark.none; + } + + if (!this.empty) { + this.top.content.push(node); + } + + return node; + } + + closeUntil(hastNode) { + while (hastNode !== this.top?.hastNode) { + this.closeNode(); + } + } +} + +/** + * Create ProseMirror node/mark factories based on one or more + * factory specifications. + * + * Note: Read `createProseMirrorDocFromMdastTree` documentation + * for instructions about how to define these specifications. + * + * @param {model.ProseMirrorSchema} schema A ProseMirror schema used to create the + * ProseMirror nodes and marks. + * @param {Object} proseMirrorFactorySpecs ProseMirror nodes factory specifications. + * @param {String} source Markdown source file’s content + * + * @returns An object that contains ProseMirror node factories + */ +const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, source) => { + const handlers = { + root: (state, hastNode) => state.openNode(schema.topNodeType, hastNode, {}), + text: (state, hastNode) => { + const { factorySpec } = state.top; + + if (/^\s+$/.test(hastNode.value)) { + return; + } + + if (factorySpec.wrapTextInParagraph === true) { + state.openNode(schema.nodeType('paragraph')); + state.addText(schema, hastNode.value); + state.closeNode(); + } else { + state.addText(schema, hastNode.value); + } + }, + }; + + for (const [hastNodeTagName, factorySpec] of Object.entries(proseMirrorFactorySpecs)) { + if (factorySpec.block) { + handlers[hastNodeTagName] = (state, hastNode, parent, ancestors) => { + const nodeType = schema.nodeType( + isFunction(factorySpec.block) + ? factorySpec.block(hastNode, parent, ancestors) + : factorySpec.block, + ); + + state.closeUntil(parent); + state.openNode( + nodeType, + hastNode, + getAttrs(factorySpec, hastNode, parent, source), + factorySpec, + ); + + /** + * If a getContent function is provided, we immediately close + * the node to delegate content processing to this function. + * */ + if (isFunction(factorySpec.getContent)) { + state.addText( + schema, + factorySpec.getContent({ hastNode, hastNodeText: toString(hastNode) }), + ); + state.closeNode(); + } + }; + } else if (factorySpec.inline) { + const nodeType = schema.nodeType(factorySpec.inline); + handlers[hastNodeTagName] = (state, hastNode, parent) => { + state.closeUntil(parent); + state.openNode( + nodeType, + hastNode, + getAttrs(factorySpec, hastNode, parent, source), + factorySpec, + ); + // Inline nodes do not have children therefore they are immediately closed + state.closeNode(); + }; + } else if (factorySpec.mark) { + const markType = schema.marks[factorySpec.mark]; + handlers[hastNodeTagName] = (state, hastNode, parent) => { + state.openMark(markType, getAttrs(factorySpec, hastNode, parent, source)); + + if (factorySpec.inlineContent) { + state.addText(schema, hastNode.value); + } + }; + } else { + throw new RangeError(`Unrecognized node factory spec ${JSON.stringify(factorySpec)}`); + } + } + + return handlers; +}; + +/** + * 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 + * to ProseMirror nodes or marks. + * + * The specification object describes how to map a Hast node to a ProseMirror node or mark. + * The converter will trigger an error if it doesn’t find a specification + * for a Hast node while traversing the AST. + * + * The object should have the following shape: + * + * { + * [hastNode.tagName]: { + * [block|node|mark]: [ProseMirror.Node.name], + * ...configurationOptions + * } + * } + * + * Where each property in the object represents a HAST node with a given tag name, for example: + * + * { + * h1: {}, + * h2: {}, + * table: {}, + * strong: {}, + * // etc + * } + * + * You can specify the type of ProseMirror object adding one the following + * properties: + * + * 1. "block": A ProseMirror node that contains one or more children. + * 2. "inline": A ProseMirror node that doesn’t contain any children although + * it can have inline content like a code block or a reference. + * 3. "mark": A ProseMirror mark. + * + * The value of that property should be the name of the ProseMirror node or mark, i.e: + * + * { + * h1: { + * block: 'heading', + * }, + * h2: { + * block: 'heading', + * }, + * img: { + * node: 'image', + * }, + * strong: { + * mark: 'bold', + * } + * } + * + * You can compute a ProseMirror’s node or mark name based on the HAST node + * by passing a function instead of a String. The converter invokes the function + * and provides a HAST node object: + * + * { + * list: { + * block: (hastNode) => { + * let type = 'bulletList'; + + * if (hastNode.children.some(isTaskItem)) { + * type = 'taskList'; + * } else if (hastNode.ordered) { + * type = 'orderedList'; + * } + + * return type; + * } + * } + * } + * + * Configuration options + * ---------------------- + * + * You can customize the conversion process for every node or mark + * setting the following properties in the specification object: + * + * **getAttrs** + * + * Computes a ProseMirror node or mark attributes. The converter will invoke + * `getAttrs` with the following parameters: + * + * 1. hastNode: The hast node + * 2. hasParents: All the hast node’s ancestors up to the root node + * 3. source: Markdown source file’s content + * + * **wrapTextInParagraph** + * + * This property only applies to block nodes. If a block node contains text, + * it will wrap that text in a paragraph. This is useful for ProseMirror block + * nodes that don’t allow text directly such as list items and tables. + * + * **skipChildren** + * + * Skips a hast node’s children while traversing the tree. + * + * **getContent** + * + * Allows to pass a custom function that returns the content of a block node. The + * Content is limited to a single text node therefore the function should return + * a String value. + * + * Use this property along skipChildren to provide custom processing of child nodes + * for a block node. + * + * @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 + * @param {Hast} params.tree https://github.com/syntax-tree/hast + * @param {String} params.source Markdown source from which the MDast tree was generated + * + * @returns A ProseMirror document + */ +export const createProseMirrorDocFromMdastTree = ({ schema, factorySpecs, tree, source }) => { + const proseMirrorNodeFactories = createProseMirrorNodeFactories(schema, factorySpecs, source); + const state = new HastToProseMirrorConverterState(); + + visitParents(tree, (hastNode, ancestors) => { + const parent = ancestors[ancestors.length - 1]; + const skipChildren = factorySpecs[hastNode.tagName]?.skipChildren; + + const handler = proseMirrorNodeFactories[hastNode.tagName || hastNode.type]; + + if (!handler) { + throw new Error( + `Hast node of type "${ + hastNode.tagName || hastNode.type + }" not supported by this converter. Please, provide an specification.`, + ); + } + + handler(state, hastNode, parent, ancestors); + + return skipChildren === true ? 'skip' : true; + }); + + let doc; + + do { + doc = state.closeNode(); + } while (!state.empty); + + return doc; +}; diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js new file mode 100644 index 00000000000..ef0a6ce0fac --- /dev/null +++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js @@ -0,0 +1,87 @@ +import { isString } from 'lodash'; +import { render } from '~/lib/gfm'; +import { createProseMirrorDocFromMdastTree } from './hast_to_prosemirror_converter'; + +const factorySpecs = { + blockquote: { block: 'blockquote' }, + p: { block: 'paragraph' }, + li: { block: 'listItem', wrapTextInParagraph: true }, + ul: { block: 'bulletList' }, + ol: { block: 'orderedList' }, + h1: { + block: 'heading', + getAttrs: () => ({ level: 1 }), + }, + h2: { + block: 'heading', + getAttrs: () => ({ level: 2 }), + }, + h3: { + block: 'heading', + getAttrs: () => ({ level: 3 }), + }, + h4: { + block: 'heading', + getAttrs: () => ({ level: 4 }), + }, + h5: { + block: 'heading', + getAttrs: () => ({ level: 5 }), + }, + h6: { + block: 'heading', + getAttrs: () => ({ level: 6 }), + }, + pre: { + block: 'codeBlock', + skipChildren: true, + getContent: ({ hastNodeText }) => hastNodeText, + getAttrs: (hastNode) => { + const languageClass = hastNode.children[0]?.properties.className?.[0]; + const language = isString(languageClass) ? languageClass.replace('language-', '') : ''; + + return { language }; + }, + }, + hr: { inline: 'horizontalRule' }, + img: { + inline: 'image', + getAttrs: (hastNode) => ({ + src: hastNode.properties.src, + title: hastNode.properties.title, + alt: hastNode.properties.alt, + }), + }, + br: { inline: 'hardBreak' }, + code: { mark: 'code' }, + em: { mark: 'italic' }, + i: { mark: 'italic' }, + strong: { mark: 'bold' }, + b: { mark: 'bold' }, + a: { + mark: 'link', + getAttrs: (hastNode) => ({ + href: hastNode.properties.href, + title: hastNode.properties.title, + }), + }, +}; + +export default () => { + return { + deserialize: async ({ schema, content: markdown }) => { + const document = await render({ + markdown, + renderer: (tree) => + createProseMirrorDocFromMdastTree({ + schema, + factorySpecs, + tree, + source: markdown, + }), + }); + + return { document, languages: [] }; + }, + }; +}; diff --git a/app/assets/javascripts/lib/gfm/index.js b/app/assets/javascripts/lib/gfm/index.js index 07388f1fdfa..4e704eb69b2 100644 --- a/app/assets/javascripts/lib/gfm/index.js +++ b/app/assets/javascripts/lib/gfm/index.js @@ -32,7 +32,7 @@ const compilerFactory = (renderer) => * the MDast tree */ export const render = async ({ markdown, renderer }) => { - const { value } = await createParser().use(compilerFactory(renderer)).process(markdown); + const { result } = await createParser().use(compilerFactory(renderer)).process(markdown); - return value; + return result; }; diff --git a/app/controllers/admin/background_migrations_controller.rb b/app/controllers/admin/background_migrations_controller.rb index 0730d372c8c..03a9b436453 100644 --- a/app/controllers/admin/background_migrations_controller.rb +++ b/app/controllers/admin/background_migrations_controller.rb @@ -2,6 +2,7 @@ class Admin::BackgroundMigrationsController < Admin::ApplicationController feature_category :database + urgency :low def index @relations_by_tab = { diff --git a/app/models/project_pages_metadatum.rb b/app/models/project_pages_metadatum.rb index dc1e9319340..7a3ece4bc92 100644 --- a/app/models/project_pages_metadatum.rb +++ b/app/models/project_pages_metadatum.rb @@ -8,8 +8,6 @@ class ProjectPagesMetadatum < ApplicationRecord self.primary_key = :project_id - ignore_columns :artifacts_archive_id, remove_with: '15.0', remove_after: '2022-04-22' - belongs_to :project, inverse_of: :pages_metadatum belongs_to :pages_deployment diff --git a/db/docs/project_feature_usages.yml b/db/docs/project_feature_usages.yml index 51e8191c6e5..b3182de243b 100644 --- a/db/docs/project_feature_usages.yml +++ b/db/docs/project_feature_usages.yml @@ -3,7 +3,7 @@ table_name: project_feature_usages classes: - ProjectFeatureUsage feature_categories: -- product_analytics -description: TODO +- integrations +description: Track Jira DVCS usage introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/21db9a55e200b23a5a47251e9df46fd548c74559 milestone: '11.8' diff --git a/db/docs/taggings.yml b/db/docs/taggings.yml index a13108e705a..71078ab9c19 100644 --- a/db/docs/taggings.yml +++ b/db/docs/taggings.yml @@ -3,7 +3,8 @@ table_name: taggings classes: - ActsAsTaggableOn::Tagging feature_categories: +- continuous_integration - runner -description: TODO +description: Taggings applied to arbitrary models based on entries in the 'tags' table introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/b946da44695c9c8fe8867bb87bcdf801c52177d3 -milestone: "<6.0" +milestone: "1.2" diff --git a/db/docs/tags.yml b/db/docs/tags.yml index 2d4820d6095..9ae2a4361ff 100644 --- a/db/docs/tags.yml +++ b/db/docs/tags.yml @@ -3,7 +3,8 @@ table_name: tags classes: - ActsAsTaggableOn::Tag feature_categories: +- continuous_integration - runner -description: TODO +description: Tags applied to arbitrary models through the 'taggings' table introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/commit/b946da44695c9c8fe8867bb87bcdf801c52177d3 milestone: "<6.0" diff --git a/doc/administration/index.md b/doc/administration/index.md index 73ea4a8e1d0..b094bd59e58 100644 --- a/doc/administration/index.md +++ b/doc/administration/index.md @@ -203,7 +203,6 @@ Learn how to install, configure, update, and maintain your GitLab instance. - [Enable Performance Monitoring](monitoring/performance/gitlab_configuration.md): Enable GitLab Performance Monitoring. - [GitLab performance monitoring with Prometheus](monitoring/prometheus/index.md): Configure GitLab and Prometheus for measuring performance metrics. - [GitLab performance monitoring with Grafana](monitoring/performance/grafana_configuration.md): Configure GitLab to visualize time series metrics through graphs and dashboards. - - [Request Profiling](monitoring/performance/request_profiling.md): Get a detailed profile on slow requests. - [Performance Bar](monitoring/performance/performance_bar.md): Get performance information for the current page. ## Analytics diff --git a/doc/administration/monitoring/performance/index.md b/doc/administration/monitoring/performance/index.md index 20fad8baf91..b063a20dc7f 100644 --- a/doc/administration/monitoring/performance/index.md +++ b/doc/administration/monitoring/performance/index.md @@ -17,7 +17,6 @@ documents to understand and properly configure GitLab Performance Monitoring: - [Prometheus documentation](../prometheus/index.md) - [Grafana Install/Configuration](grafana_configuration.md) - [Performance bar](performance_bar.md) -- [Request profiling](request_profiling.md) ## Introduction to GitLab Performance Monitoring diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md deleted file mode 100644 index 6cd20132092..00000000000 --- a/doc/administration/monitoring/performance/request_profiling.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -stage: Monitor -group: Respond -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 ---- - -# Request profiling (DEPRECATED) **(FREE SELF)** - -> [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/352488) in GitLab 14.8, and planned for removal in GitLab 15.0. - -WARNING: -This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/352488) -in GitLab 14.8, and is planned for removal in GitLab 15.0. - -To profile a request: - -1. Sign in to GitLab as an Administrator or a user with the [Maintainer role](../../../user/permissions.md). -1. In the navigation bar, click **Admin area**. -1. Go to **Monitoring > Requests Profiles**. -1. In the **Requests Profiles** section, copy the token. -1. Pass the headers `X-Profile-Token: <token>` and `X-Profile-Mode: <mode>`(where - `<mode>` can be `execution` or `memory`) to the request you want to profile. When - passing headers, you can use: - - - Browser extensions such as the - [ModHeader](https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj) - Chrome extension. - - `curl`. For example: - - ```shell - curl --header 'X-Profile-Token: <token>' --header 'X-Profile-Mode: <mode>' "https://gitlab.example.com/group/project" - ``` - - Profiled requests can take longer than usual. - -After the request completes, you can view the profiling output from the -**Monitoring > Requests Profiles** administration page: - -![Profiling output](img/request_profile_result.png) - -## Cleaning up profiled requests - -The output from profiled requests is cleared out once each day through a -Sidekiq worker. diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index 80e5dd18726..1ae3aa4adb6 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -276,7 +276,6 @@ You can use these fake tokens as examples: | Trigger token | `be20d8dcc028677c931e04f3871a9b` | | Webhook secret token | `6XhDroRcYPM5by_h-HLY` | | Health check token | `Tu7BgjR9qeZTEyRzGG2P` | -| Request profile token | `7VgpS4Ax5utVD2esNstz` | ### Contractions diff --git a/doc/development/performance.md b/doc/development/performance.md index 1e3e0570206..716a0d5199a 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -75,7 +75,6 @@ GitLab provides built-in tools to help improve performance and availability: - [Profiling](profiling.md). - [Distributed Tracing](distributed_tracing.md) - [GitLab Performance Monitoring](../administration/monitoring/performance/index.md). -- [Request Profiling](../administration/monitoring/performance/request_profiling.md). - [QueryRecoder](query_recorder.md) for preventing `N+1` regressions. - [Chaos endpoints](chaos_endpoints.md) for testing failure scenarios. Intended mainly for testing availability. - [Service measurement](service_measurement.md) for measuring and logging service execution. diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index f57672d3d36..de36018e194 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -25,7 +25,7 @@ The Admin Area is made up of the following sections: | Section | Description | |:-----------------------------------------------|:------------| | **{overview}** [Overview](#overview-section) | View your GitLab [Dashboard](#admin-area-dashboard), and administer [projects](#administering-projects), [users](#administering-users), [groups](#administering-groups), [topics](#administering-topics), [jobs](#administering-jobs), [runners](#administering-runners), and [Gitaly servers](#administering-gitaly-servers). | -| **{monitor}** Monitoring | View GitLab [system information](#system-information), and information on [background jobs](#background-jobs), [logs](#logs), [health checks](monitoring/health_check.md), [requests profiles](#requests-profiles), and [audit events](#audit-events). | +| **{monitor}** Monitoring | View GitLab [system information](#system-information), and information on [background jobs](#background-jobs), [logs](#logs), [health checks](monitoring/health_check.md), and [audit events](#audit-events). | | **{messages}** Messages | Send and manage [broadcast messages](broadcast_messages.md) for your users. | | **{hook}** System Hooks | Configure [system hooks](../../administration/system_hooks.md) for many events. | | **{applications}** Applications | Create system [OAuth applications](../../integration/oauth_provider.md) for integrations with other services. | @@ -430,10 +430,6 @@ For details of these log files and their contents, see [Log system](../../admini The content of each log file is listed in chronological order. To minimize performance issues, a maximum 2000 lines of each log file are shown. -### Requests Profiles - -The **Requests Profiles** page contains the token required for profiling. For more details, see [Request Profiling](../../administration/monitoring/performance/request_profiling.md). - ### Audit Events **(PREMIUM SELF)** The **Audit Events** page lists changes made within the GitLab server. With this information you can control, analyze, and track every change. diff --git a/package.json b/package.json index 88973c5e4e9..c1b4d670938 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "2.10.0", "@gitlab/ui": "39.2.1", - "@gitlab/visual-review-tools": "1.6.1", + "@gitlab/visual-review-tools": "1.7.0", "@rails/actioncable": "6.1.4-7", "@rails/ujs": "6.1.4-7", "@sentry/browser": "5.30.0", @@ -129,6 +129,7 @@ "fuzzaldrin-plus": "^0.6.0", "graphql": "^15.7.2", "graphql-tag": "^2.11.0", + "hast-util-to-string": "^2.0.0", "highlight.js": "^11.3.1", "immer": "^7.0.7", "ipaddr.js": "^1.9.1", @@ -179,6 +180,7 @@ "three-stl-loader": "^1.0.4", "timeago.js": "^4.0.2", "unified": "^10.1.2", + "unist-util-visit-parents": "^5.1.0", "url-loader": "^4.1.1", "uuid": "8.1.0", "visibilityjs": "^1.2.4", diff --git a/spec/frontend/content_editor/services/remark_markdown_deserializer_spec.js b/spec/frontend/content_editor/services/remark_markdown_deserializer_spec.js new file mode 100644 index 00000000000..66ca1587ec6 --- /dev/null +++ b/spec/frontend/content_editor/services/remark_markdown_deserializer_spec.js @@ -0,0 +1,309 @@ +import Bold from '~/content_editor/extensions/bold'; +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 DescriptionItem from '~/content_editor/extensions/description_item'; +import DescriptionList from '~/content_editor/extensions/description_list'; +import Details from '~/content_editor/extensions/details'; +import DetailsContent from '~/content_editor/extensions/details_content'; +import Division from '~/content_editor/extensions/division'; +import Figure from '~/content_editor/extensions/figure'; +import FigureCaption from '~/content_editor/extensions/figure_caption'; +import HardBreak from '~/content_editor/extensions/hard_break'; +import Heading from '~/content_editor/extensions/heading'; +import HorizontalRule from '~/content_editor/extensions/horizontal_rule'; +import Image from '~/content_editor/extensions/image'; +import Italic from '~/content_editor/extensions/italic'; +import Link from '~/content_editor/extensions/link'; +import ListItem from '~/content_editor/extensions/list_item'; +import OrderedList from '~/content_editor/extensions/ordered_list'; +import Paragraph from '~/content_editor/extensions/paragraph'; +import createRemarkMarkdownDeserializer from '~/content_editor/services/remark_markdown_deserializer'; +import { createTestEditor, createDocBuilder } from '../test_utils'; + +const tiptapEditor = createTestEditor({ + extensions: [ + Blockquote, + Bold, + BulletList, + Code, + CodeBlockHighlight, + HardBreak, + Heading, + HorizontalRule, + Image, + Italic, + Link, + ListItem, + OrderedList, + ], +}); + +const { + builders: { + doc, + blockquote, + bold, + bulletList, + code, + codeBlock, + heading, + hardBreak, + horizontalRule, + image, + italic, + link, + listItem, + orderedList, + paragraph, + }, +} = createDocBuilder({ + tiptapEditor, + names: { + blockquote: { nodeType: Blockquote.name }, + bold: { markType: Bold.name }, + bulletList: { nodeType: BulletList.name }, + code: { markType: Code.name }, + codeBlock: { nodeType: CodeBlockHighlight.name }, + details: { nodeType: Details.name }, + detailsContent: { nodeType: DetailsContent.name }, + division: { nodeType: Division.name }, + descriptionItem: { nodeType: DescriptionItem.name }, + descriptionList: { nodeType: DescriptionList.name }, + figure: { nodeType: Figure.name }, + figureCaption: { nodeType: FigureCaption.name }, + hardBreak: { nodeType: HardBreak.name }, + heading: { nodeType: Heading.name }, + horizontalRule: { nodeType: HorizontalRule.name }, + image: { nodeType: Image.name }, + italic: { nodeType: Italic.name }, + link: { markType: Link.name }, + listItem: { nodeType: ListItem.name }, + orderedList: { nodeType: OrderedList.name }, + paragraph: { nodeType: Paragraph.name }, + }, +}); + +describe('content_editor/services/remark_markdown_deserializer', () => { + let deserializer; + + beforeEach(() => { + deserializer = createRemarkMarkdownDeserializer(); + }); + + it.each([ + { + markdown: '__bold text__', + doc: doc(paragraph(bold('bold text'))), + }, + { + markdown: '**bold text**', + doc: doc(paragraph(bold('bold text'))), + }, + { + markdown: '<strong>bold text</strong>', + doc: doc(paragraph(bold('bold text'))), + }, + { + markdown: '<b>bold text</b>', + doc: doc(paragraph(bold('bold text'))), + }, + { + markdown: '_italic text_', + doc: doc(paragraph(italic('italic text'))), + }, + { + markdown: '*italic text*', + doc: doc(paragraph(italic('italic text'))), + }, + { + markdown: '<em>italic text</em>', + doc: doc(paragraph(italic('italic text'))), + }, + { + markdown: '<i>italic text</i>', + doc: doc(paragraph(italic('italic text'))), + }, + { + markdown: '`inline code`', + doc: doc(paragraph(code('inline code'))), + }, + { + markdown: '**`inline code bold`**', + doc: doc(paragraph(bold(code('inline code bold')))), + }, + { + markdown: '[GitLab](https://gitlab.com "Go to GitLab")', + doc: doc(paragraph(link({ href: 'https://gitlab.com', title: 'Go to GitLab' }, 'GitLab'))), + }, + { + markdown: '**[GitLab](https://gitlab.com "Go to GitLab")**', + doc: doc( + paragraph(bold(link({ href: 'https://gitlab.com', title: 'Go to GitLab' }, 'GitLab'))), + ), + }, + { + markdown: ` +This is a paragraph with a\\ +hard line break`, + doc: doc(paragraph('This is a paragraph with a', hardBreak(), '\nhard line break')), + }, + { + markdown: '![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")', + doc: doc( + paragraph( + image({ src: 'https://gitlab.com/logo.png', alt: 'GitLab Logo', title: 'GitLab Logo' }), + ), + ), + }, + { + markdown: '---', + doc: doc(horizontalRule()), + }, + { + markdown: '***', + doc: doc(horizontalRule()), + }, + { + markdown: '___', + doc: doc(horizontalRule()), + }, + { + markdown: '<hr>', + doc: doc(horizontalRule()), + }, + { + markdown: '# Heading 1', + doc: doc(heading({ level: 1 }, 'Heading 1')), + }, + { + markdown: '## Heading 2', + doc: doc(heading({ level: 2 }, 'Heading 2')), + }, + { + markdown: '### Heading 3', + doc: doc(heading({ level: 3 }, 'Heading 3')), + }, + { + markdown: '#### Heading 4', + doc: doc(heading({ level: 4 }, 'Heading 4')), + }, + { + markdown: '##### Heading 5', + doc: doc(heading({ level: 5 }, 'Heading 5')), + }, + { + markdown: '###### Heading 6', + doc: doc(heading({ level: 6 }, 'Heading 6')), + }, + { + markdown: ` +Heading +one +====== +`, + doc: doc(heading({ level: 1 }, 'Heading\none')), + }, + { + markdown: ` +Heading +two +------- +`, + doc: doc(heading({ level: 2 }, 'Heading\ntwo')), + }, + { + markdown: ` +- List item 1 +- List item 2 +`, + doc: doc(bulletList(listItem(paragraph('List item 1')), listItem(paragraph('List item 2')))), + }, + { + markdown: ` +1. List item 1 +1. List item 2 +`, + doc: doc(orderedList(listItem(paragraph('List item 1')), listItem(paragraph('List item 2')))), + }, + { + markdown: ` +- List item 1 + - Sub list item 1 +`, + doc: doc( + bulletList( + listItem(paragraph('List item 1\n'), bulletList(listItem(paragraph('Sub list item 1')))), + ), + ), + }, + { + markdown: ` +- List item 1 paragraph 1 + + List item 1 paragraph 2 +- List item 2 +`, + doc: doc( + bulletList( + listItem(paragraph('List item 1 paragraph 1'), paragraph('List item 1 paragraph 2')), + listItem(paragraph('List item 2')), + ), + ), + }, + { + markdown: ` +> This is a blockquote +`, + doc: doc(blockquote(paragraph('This is a blockquote'))), + }, + { + markdown: ` +> - List item 1 +> - List item 2 +`, + doc: doc( + blockquote( + bulletList(listItem(paragraph('List item 1')), listItem(paragraph('List item 2'))), + ), + ), + }, + { + markdown: ` + const fn = () => 'GitLab'; +`, + doc: doc(codeBlock({ language: '' }, "const fn = () => 'GitLab';\n")), + }, + { + markdown: ` +\`\`\`javascript + const fn = () => 'GitLab'; +\`\`\`\ +`, + doc: doc(codeBlock({ language: 'javascript' }, " const fn = () => 'GitLab';\n")), + }, + { + markdown: ` +\`\`\` +\`\`\`\ +`, + doc: doc(codeBlock({ language: '' }, '')), + }, + { + markdown: ` +\`\`\`javascript + const fn = () => 'GitLab'; + + +\`\`\`\ +`, + doc: doc(codeBlock({ language: 'javascript' }, " const fn = () => 'GitLab';\n\n\n")), + }, + ])('deserializes %s correctly', async ({ markdown, doc: expectedDoc }) => { + const { schema } = tiptapEditor; + const { document } = await deserializer.deserialize({ schema, content: markdown }); + + expect(document.toJSON()).toEqual(expectedDoc.toJSON()); + }); +}); diff --git a/spec/frontend/lib/gfm/index_spec.js b/spec/frontend/lib/gfm/index_spec.js index 5c72b5a51a7..c9a480e9943 100644 --- a/spec/frontend/lib/gfm/index_spec.js +++ b/spec/frontend/lib/gfm/index_spec.js @@ -33,14 +33,16 @@ describe('gfm', () => { }); it('returns the result of executing the renderer function', async () => { + const rendered = { value: 'rendered tree' }; + const result = await render({ markdown: '<strong>This is bold text</strong>', renderer: () => { - return 'rendered tree'; + return rendered; }, }); - expect(result).toBe('rendered tree'); + expect(result).toEqual(rendered); }); }); }); diff --git a/yarn.lock b/yarn.lock index ca94db51272..200dab1ca33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -991,10 +991,10 @@ url-search-params-polyfill "^5.0.0" vue-runtime-helpers "^1.1.2" -"@gitlab/visual-review-tools@1.6.1": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.6.1.tgz#0d8f3ff9f51b05f7c80b9a107727703d48997e4e" - integrity sha512-vY8K1igwZFoEOmU0h4E7XTLlilsQ4ylPr27O01UsSe6ZTKi6oEMREsRAEpNIUgRlxUARCsf+Opp4pgSFzFkFcw== +"@gitlab/visual-review-tools@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.7.0.tgz#18a59911484ddbbee433d3701316b5ab8e4077a5" + integrity sha512-o9gM3W6guSl00aS0hJcXePuR/mkmq38F5FhUgTlMBkB5+R68aO87md3cSvSMfJin0MjPlWktBNAfaz+y5CQ39g== "@graphql-eslint/eslint-plugin@3.10.2": version "3.10.2" @@ -6613,6 +6613,13 @@ hast-util-to-parse5@^7.0.0: web-namespaces "^2.0.0" zwitch "^2.0.0" +hast-util-to-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-2.0.0.tgz#b008b0a4ea472bf34dd390b7eea1018726ae152a" + integrity sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A== + dependencies: + "@types/hast" "^2.0.0" + hastscript@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-7.0.2.tgz#d811fc040817d91923448a28156463b2e40d590a" @@ -12366,7 +12373,7 @@ unist-util-visit-parents@^4.0.0: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" -unist-util-visit-parents@^5.0.0: +unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz#44bbc5d25f2411e7dfc5cecff12de43296aa8521" integrity sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg== |