Commit e60fa82f authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents ed8c5ee4 9ba51c65
......@@ -45,3 +45,7 @@ export const TEXT_STYLE_DROPDOWN_ITEMS = [
export const LOADING_CONTENT_EVENT = 'loadingContent';
export const LOADING_SUCCESS_EVENT = 'loadingSuccess';
export const LOADING_ERROR_EVENT = 'loadingError';
export const PARSE_HTML_PRIORITY_LOWEST = 1;
export const PARSE_HTML_PRIORITY_DEFAULT = 50;
export const PARSE_HTML_PRIORITY_HIGHEST = 100;
import { Mark, mergeAttributes, markInputRule } from '@tiptap/core';
import { PARSE_HTML_PRIORITY_LOWEST } from '../constants';
import { markInputRegex, extractMarkAttributesFromMatch } from '../services/mark_utils';
const marks = [
'ins',
'abbr',
'bdo',
'cite',
'dfn',
'mark',
'small',
'span',
'time',
'kbd',
'q',
'samp',
'var',
'ruby',
'rp',
'rt',
];
const attrs = {
time: ['datetime'],
abbr: ['title'],
span: ['dir'],
bdo: ['dir'],
};
export default marks.map((name) =>
Mark.create({
name,
inclusive: false,
defaultOptions: {
HTMLAttributes: {},
},
addAttributes() {
return (attrs[name] || []).reduce(
(acc, attr) => ({
...acc,
[attr]: {
default: null,
parseHTML: (element) => ({ [attr]: element.getAttribute(attr) }),
},
}),
{},
);
},
parseHTML() {
return [{ tag: name, priority: PARSE_HTML_PRIORITY_LOWEST }];
},
renderHTML({ HTMLAttributes }) {
return [name, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
},
addInputRules() {
return [markInputRule(markInputRegex(name), this.type, extractMarkAttributesFromMatch)];
},
}),
);
import { Image } from '@tiptap/extension-image';
import { VueNodeViewRenderer } from '@tiptap/vue-2';
import ImageWrapper from '../components/wrappers/image.vue';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
const resolveImageEl = (element) =>
element.nodeName === 'IMG' ? element : element.querySelector('img');
......@@ -65,7 +66,7 @@ export default Image.extend({
parseHTML() {
return [
{
priority: 100,
priority: PARSE_HTML_PRIORITY_HIGHEST,
tag: 'a.no-attachment-icon',
},
{
......
import { Node } from '@tiptap/core';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
const getAnchor = (element) => {
if (element.nodeName === 'A') return element;
return element.querySelector('a');
};
export default Node.create({
name: 'reference',
......@@ -15,7 +21,7 @@ export default Node.create({
default: null,
parseHTML: (element) => {
return {
className: element.className,
className: getAnchor(element).className,
};
},
},
......@@ -23,7 +29,7 @@ export default Node.create({
default: null,
parseHTML: (element) => {
return {
referenceType: element.dataset.referenceType,
referenceType: getAnchor(element).dataset.referenceType,
};
},
},
......@@ -31,7 +37,7 @@ export default Node.create({
default: null,
parseHTML: (element) => {
return {
originalText: element.dataset.original,
originalText: getAnchor(element).dataset.original,
};
},
},
......@@ -39,7 +45,7 @@ export default Node.create({
default: null,
parseHTML: (element) => {
return {
href: element.getAttribute('href'),
href: getAnchor(element).getAttribute('href'),
};
},
},
......@@ -47,7 +53,7 @@ export default Node.create({
default: null,
parseHTML: (element) => {
return {
text: element.textContent,
text: getAnchor(element).textContent,
};
},
},
......@@ -58,7 +64,10 @@ export default Node.create({
return [
{
tag: 'a.gfm:not([data-link=true])',
priority: 51,
priority: PARSE_HTML_PRIORITY_HIGHEST,
},
{
tag: 'span.gl-label',
},
];
},
......
export { Subscript as default } from '@tiptap/extension-subscript';
import { markInputRule } from '@tiptap/core';
import { Subscript } from '@tiptap/extension-subscript';
import { markInputRegex, extractMarkAttributesFromMatch } from '../services/mark_utils';
export default Subscript.extend({
addInputRules() {
return [markInputRule(markInputRegex('sub'), this.type, extractMarkAttributesFromMatch)];
},
});
export { Superscript as default } from '@tiptap/extension-superscript';
import { markInputRule } from '@tiptap/core';
import { Superscript } from '@tiptap/extension-superscript';
import { markInputRegex, extractMarkAttributesFromMatch } from '../services/mark_utils';
export default Superscript.extend({
addInputRules() {
return [markInputRule(markInputRegex('sup'), this.type, extractMarkAttributesFromMatch)];
},
});
import { TaskItem } from '@tiptap/extension-task-item';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
export default TaskItem.extend({
defaultOptions: {
......@@ -26,7 +27,7 @@ export default TaskItem.extend({
return [
{
tag: 'li.task-list-item',
priority: 100,
priority: PARSE_HTML_PRIORITY_HIGHEST,
},
];
},
......
import { mergeAttributes } from '@tiptap/core';
import { TaskList } from '@tiptap/extension-task-list';
import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
export default TaskList.extend({
addAttributes() {
......@@ -19,7 +20,7 @@ export default TaskList.extend({
return [
{
tag: '.task-list',
priority: 100,
priority: PARSE_HTML_PRIORITY_HIGHEST,
},
];
},
......
......@@ -15,6 +15,7 @@ import HardBreak from '../extensions/hard_break';
import Heading from '../extensions/heading';
import History from '../extensions/history';
import HorizontalRule from '../extensions/horizontal_rule';
import HTMLMarks from '../extensions/html_marks';
import Image from '../extensions/image';
import InlineDiff from '../extensions/inline_diff';
import Italic from '../extensions/italic';
......@@ -75,6 +76,7 @@ export const createContentEditor = ({
Heading,
History,
HorizontalRule,
...HTMLMarks,
Image,
InlineDiff,
Italic,
......
export const markInputRegex = (tag) =>
new RegExp(`(<(${tag})((?: \\w+=".+?")+)?>([^<]+)</${tag}>)$`, 'gm');
export const extractMarkAttributesFromMatch = ([, , , attrsString]) => {
const attrRegex = /(\w+)="(.+?)"/g;
const attrs = {};
let key;
let value;
do {
[, key, value] = attrRegex.exec(attrsString) || [];
if (key) attrs[key] = value;
} while (key);
return attrs;
};
......@@ -12,6 +12,7 @@ import Emoji from '../extensions/emoji';
import HardBreak from '../extensions/hard_break';
import Heading from '../extensions/heading';
import HorizontalRule from '../extensions/horizontal_rule';
import HTMLMarks from '../extensions/html_marks';
import Image from '../extensions/image';
import InlineDiff from '../extensions/inline_diff';
import Italic from '../extensions/italic';
......@@ -35,6 +36,8 @@ import {
renderTable,
renderTableCell,
renderTableRow,
openTag,
closeTag,
} from './serialization_helpers';
const defaultSerializerConfig = {
......@@ -70,6 +73,19 @@ const defaultSerializerConfig = {
mixable: true,
expelEnclosingWhitespace: true,
},
...HTMLMarks.reduce(
(acc, { name }) => ({
...acc,
[name]: {
mixable: true,
open(state, node) {
return openTag(name, node.attrs);
},
close: closeTag(name),
},
}),
{},
),
},
nodes: {
......
......@@ -80,21 +80,30 @@ function shouldRenderHTMLTable(table) {
return true;
}
function openTag(state, tagName, attrs) {
function htmlEncode(str = '') {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&#34;');
}
export function openTag(tagName, attrs) {
let str = `<${tagName}`;
str += Object.entries(attrs || {})
.map(([key, value]) => {
if (defaultAttrs[tagName]?.[key] === value) return '';
return ` ${key}=${state.quote(value?.toString() || '')}`;
return ` ${key}="${htmlEncode(value?.toString())}"`;
})
.join('');
return `${str}>`;
}
function closeTag(state, tagName) {
export function closeTag(tagName) {
return `</${tagName}>`;
}
......@@ -131,11 +140,11 @@ function unsetIsInBlockTable(table) {
function renderTagOpen(state, tagName, attrs) {
state.ensureNewLine();
state.write(openTag(state, tagName, attrs));
state.write(openTag(tagName, attrs));
}
function renderTagClose(state, tagName, insertNewline = true) {
state.write(closeTag(state, tagName));
state.write(closeTag(tagName));
if (insertNewline) state.ensureNewLine();
}
......
......@@ -63,7 +63,7 @@ export default {
</gl-sprintf>
<gl-sprintf
v-else
:message="n__('1 merge request selected', '%d merge request selected', issuableCount)"
:message="n__('1 merge request selected', '%d merge requests selected', issuableCount)"
>
<template #issuableCount>{{ issuableCount }}</template>
</gl-sprintf>
......
-# Note: This file should stay aligned with:
-# `app/views/admin/runners/_runner.html.haml`
.gl-responsive-table-row{ id: dom_id(runner) }
.table-section.section-10.section-wrap
.table-mobile-header{ role: 'rowheader' }= _('Type')
......
---
type: reference, concepts
stage: Enablement
group: Alliances
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
---
# EKS cluster provisioning best practices
GitLab can be used to provision an EKS cluster into AWS, however, it necessarily focuses on a basic EKS configuration. Using the AWS tools can help with advanced cluster configuration, automation, and maintenance.
This documentation is not for clusters for deployment of GitLab itself, but instead clusters purpose built for:
- EKS Clusters for GitLab Runners
- Application Deployment Clusters for GitLab review apps
- Application Deployment Cluster for production applications
Information on deploying GitLab onto EKS can be found in [Provisioning GitLab Cloud Native Hybrid on AWS EKS](gitlab_hybrid_on_aws.md).
## Use AWS EKS quick start or `eksctl`
Using the EKS Quick Start or `eksctl` enables the following when building an EKS Cluster:
- It can be part of CloudFormation IaC or [CLI (`eksctl`)](https://eksctl.io/) automation
- You have various cluster configuration options:
- Selection of operating system: Amazon Linux 2, Windows, Bottlerocket
- Selection of Hardware Architecture: x86, ARM, GPU
- Selection of Fargate backend
- It can deploy high value-add items to the cluster, including:
- A bastion host to keep the cluster endpoint private and possible perform performance testing.
- Prometheus and Grafana for monitoring.
- EKS Autoscaler for automatic K8s Node scaling.
- 2 or 3 Availability Zones (AZ) spread for balance between High Availability (HA) and cost control.
- Ability to specify spot compute.
Read more about Amazon EKS architecture quick start guide:
- [Landing page](https://aws.amazon.com/quickstart/architecture/amazon-eks/)
- [Reference guide](https://aws-quickstart.github.io/quickstart-amazon-eks/)
- [Reference guide deployment steps](https://aws-quickstart.github.io/quickstart-amazon-eks/#_deployment_steps)
- [Reference guide parameter reference](https://aws-quickstart.github.io/quickstart-amazon-eks/#_parameter_reference)
## Inject GitLab configuration for integrating clusters
Read more how to [configure an App Deployment cluster](../../user/project/clusters/add_existing_cluster.md) and extract information from it to integrate it into GitLab.
## Provision GitLab Runners using Helm charts
Read how to [use the GitLab Runner Helm Chart](https://docs.gitlab.com/runner/install/kubernetes.html) to deploy a runner into a cluster.
## Runner Cache
Since the EKS Quick Start provides for EFS provisioning, the best approach is to use EFS for runner caching. Eventually we will publish information on using an S3 bucket for runner caching here.
---
type: reference, concepts
stage: Enablement
group: Alliances
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
---
# Gitaly SRE Considerations
Gitaly and Gitaly Cluster have been engineered by GitLab to overcome fundamental challenges with horizontal scaling of the open source Git binaries. Here is indepth technical reading on the topic:
## Why Gitaly was built
Below are some links to better understand why Gitaly was built:
- [Git characteristics that make horizontal scaling difficult](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/DESIGN.md#git-characteristics-that-make-horizontal-scaling-difficult)
- [Git architectural characteristics and assumptions](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/DESIGN.md#git-architectural-characteristics-and-assumptions)
- [Affects on horizontal compute architecture](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/DESIGN.md#affects-on-horizontal-compute-architecture)
- [Evidence to back building a new horizontal layer to scale Git](https://gitlab.com/gitlab-org/gitaly/-/blob/master/doc/DESIGN.md#evidence-to-back-building-a-new-horizontal-layer-to-scale-git)
## Gitaly and Praefect elections
As part of Gitaly cluster consistency, Praefect nodes will occasionally need to vote on what data copy is the most accurate. This requires an uneven number of Praefect nodes to avoid stalemates. This means that for HA, Gitaly and Praefect require a minimum of three nodes.
## Gitaly performance monitoring
Complete performance metrics should be collected for Gitaly instances for identification of bottlenecks, as they could have to do with disk IO, network IO or memory.
Gitaly must be implemented on instance compute.
## Gitaly EBS volume sizing guidelines
Gitaly storage is expected to be local (not NFS of any type including EFS).
Gitaly servers also need disk space for building and caching Git pack files.
Background:
- When not using provisioned EBS IO, EBS volume size determines the IO level, so provisioning volumes that are much larger than needed can be the least expensive way to improve EBS IO.
- Only use nitro instance types due to higher IO and EBS optimization.
- Use Amazon Linux 2 to ensure the best disk and memory optimizations (for example, ENA network adapters and drivers).
- If GitLab backup scripts are used, they need a temporary space location large enough to hold 2 times the current size of the Git File system. If that will be done on Gitaly servers, separate volumes should be used.
## Gitaly HA in EKS quick start
The AWS EKS quick start for GitLab Cloud Native implements Gitaly as a multi-zone, self-healing infrastructure. It has specific code for reestablishing a Gitaly node when one fails, including AZ failure.
## Gitaly long term management
Gitaly node disk sizes will need to be monitored and increased to accommodate Git repository growth and Gitaly temporary and caching storage needs. The storage configuration on all nodes should be kept identical.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -1263,7 +1263,7 @@ msgstr[0] ""
msgstr[1] ""
msgid "1 merge request selected"
msgid_plural "%d merge request selected"
msgid_plural "%d merge requests selected"
msgstr[0] ""
msgstr[1] ""
......
import {
markInputRegex,
extractMarkAttributesFromMatch,
} from '~/content_editor/services/mark_utils';
describe('content_editor/services/mark_utils', () => {
describe.each`
tag | input | matches
${'tag'} | ${'<tag>hello</tag>'} | ${true}
${'tag'} | ${'<tag title="tooltip">hello</tag>'} | ${true}
${'kbd'} | ${'Hold <kbd>Ctrl</kbd>'} | ${true}
${'time'} | ${'Lets meet at <time title="today" datetime="20:00">20:00</time>'} | ${true}
${'tag'} | ${'<tag width=30 height=30>attrs not quoted</tag>'} | ${false}
${'tag'} | ${"<tag title='abc'>single quote attrs not supported</tag>"} | ${false}
${'tag'} | ${'<tag title>attr has no value</tag>'} | ${false}
${'tag'} | ${'<tag>tag opened but not closed'} | ${false}
${'tag'} | ${'</tag>tag closed before opened<tag>'} | ${false}
`('inputRegex("$tag")', ({ tag, input, matches }) => {
it(`${matches ? 'matches' : 'does not match'}: "${input}"`, () => {
const match = markInputRegex(tag).test(input);
expect(match).toBe(matches);
});
});
describe.each`
tag | input | attrs
${'kbd'} | ${'Hold <kbd>Ctrl</kbd>'} | ${{}}
${'tag'} | ${'<tag title="tooltip">hello</tag>'} | ${{ title: 'tooltip' }}
${'time'} | ${'Lets meet at <time title="today" datetime="20:00">20:00</time>'} | ${{ title: 'today', datetime: '20:00' }}
${'abbr'} | ${'Sure, you can try it out but <abbr title="Your mileage may vary">YMMV</abbr>'} | ${{ title: 'Your mileage may vary' }}
`('extractAttributesFromMatch(inputRegex("$tag").exec(\'$input\'))', ({ tag, input, attrs }) => {
it(`returns: "${JSON.stringify(attrs)}"`, () => {
const matches = markInputRegex(tag).exec(input);
expect(extractMarkAttributesFromMatch(matches)).toEqual(attrs);
});
});
});
......@@ -12,14 +12,27 @@
markdown: |-
* {-deleted-}
* {+added+}
- name: subscript
markdown: H<sub>2</sub>O
- name: superscript
markdown: 2<sup>8</sup> = 256
- name: strike
markdown: '~~del~~'
- name: horizontal_rule
markdown: '---'
- name: html_marks
markdown: |-
* Content editor is ~~great~~<ins>amazing</ins>.
* If the changes <abbr title="Looks good to merge">LGTM</abbr>, please <abbr title="Merge when pipeline succeeds">MWPS</abbr>.
* The English song <q>Oh I do like to be beside the seaside</q> looks like this in Hebrew: <span dir="rtl">אה, אני אוהב להיות ליד חוף הים</span>. In the computer's memory, this is stored as <bdo dir="ltr">אה, אני אוהב להיות ליד חוף הים</bdo>.
* <cite>The Scream</cite> by Edvard Munch. Painted in 1893.
* <dfn>HTML</dfn> is the standard markup language for creating web pages.
* Do not forget to buy <mark>milk</mark> today.
* This is a paragraph and <small>smaller text goes here</small>.
* The concert starts at <time datetime="20:00">20:00</time> and you'll be able to enjoy the band for at least <time datetime="PT2H30M">2h 30m</time>.
* Press <kbd>Ctrl</kbd> + <kbd>C</kbd> to copy text (Windows).
* WWF's goal is to: <q>Build a future where people live in harmony with nature.</q> We hope they succeed.
* The error occured was: <samp>Keyboard not found. Press F1 to continue.</samp>
* The area of a triangle is: 1/2 x <var>b</var> x <var>h</var>, where <var>b</var> is the base, and <var>h</var> is the vertical height.
* <ruby>漢<rt>ㄏㄢˋ</rt></ruby>
* C<sub>7</sub>H<sub>16</sub> + O<sub>2</sub> → CO<sub>2</sub> + H<sub>2</sub>O
* The **Pythagorean theorem** is often expressed as <var>a<sup>2</sup></var> + <var>b<sup>2</sup></var> = <var>c<sup>2</sup></var>
- name: link
markdown: '[GitLab](https://gitlab.com)'
- name: attachment_link
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment