diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-22 09:09:20 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-22 09:09:20 +0000 |
commit | e9a834ee298801dec92de77783fffa0cea9deb34 (patch) | |
tree | d425f2e96ce226ff80b1f6ef5f4efc2c57f73520 | |
parent | 054cda9ea6511f78169201c2bc12ee25dcb74ab0 (diff) | |
download | gitlab-ce-e9a834ee298801dec92de77783fffa0cea9deb34.tar.gz |
Add latest changes from gitlab-org/gitlab@master
54 files changed, 806 insertions, 535 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 8c999b7e644..c6a89ddfa9c 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -76,6 +76,7 @@ /doc/user/project/web_ide/index.md @aqualls /doc/user/project/wiki/index.md @aqualls /doc/user/search/ @marcia @aqualls +/doc/user/workspace/ @fneill [Docs Create] /doc/administration/file_hooks.md @aqualls diff --git a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue index 0da7be4040f..b0b787ac3f7 100644 --- a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue +++ b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue @@ -1,8 +1,7 @@ <script> -import { GlModal, GlButton, GlFormInput } from '@gitlab/ui'; -import { escape } from 'lodash'; +import { GlModal, GlButton, GlFormInput, GlSprintf } from '@gitlab/ui'; import csrf from '~/lib/utils/csrf'; -import { s__, sprintf } from '~/locale'; +import { s__ } from '~/locale'; import SplitButton from '~/vue_shared/components/split_button.vue'; const splitButtonActionItems = [ @@ -29,6 +28,7 @@ export default { GlModal, GlButton, GlFormInput, + GlSprintf, }, props: { clusterPath: { @@ -67,17 +67,11 @@ export default { : s__('ClusterIntegration|You are about to remove your cluster integration.'); }, confirmationTextLabel() { - return sprintf( - this.confirmCleanup - ? s__( - 'ClusterIntegration|To remove your integration and resources, type %{clusterName} to confirm:', - ) - : s__('ClusterIntegration|To remove your integration, type %{clusterName} to confirm:'), - { - clusterName: `<code>${escape(this.clusterName)}</code>`, - }, - false, - ); + return this.confirmCleanup + ? s__( + 'ClusterIntegration|To remove your integration and resources, type %{clusterName} to confirm:', + ) + : s__('ClusterIntegration|To remove your integration, type %{clusterName} to confirm:'); }, canSubmit() { return this.enteredClusterName === this.clusterName; @@ -140,7 +134,13 @@ export default { <!-- eslint-enable @gitlab/vue-require-i18n-strings --> </ul> </div> - <strong v-html="confirmationTextLabel /* eslint-disable-line vue/no-v-html */"></strong> + <strong> + <gl-sprintf :message="confirmationTextLabel"> + <template #clusterName> + <code>{{ clusterName }}</code> + </template> + </gl-sprintf> + </strong> <form ref="form" :action="clusterPath" method="post" class="gl-mb-5"> <input ref="method" type="hidden" name="_method" value="delete" /> <input :value="csrfToken" type="hidden" name="authenticity_token" /> diff --git a/app/assets/javascripts/content_editor/extensions/blockquote.js b/app/assets/javascripts/content_editor/extensions/blockquote.js index 4512ead44bc..5632bc28592 100644 --- a/app/assets/javascripts/content_editor/extensions/blockquote.js +++ b/app/assets/javascripts/content_editor/extensions/blockquote.js @@ -1,10 +1,8 @@ import { Blockquote } from '@tiptap/extension-blockquote'; -import { wrappingInputRule } from 'prosemirror-inputrules'; +import { wrappingInputRule } from '@tiptap/core'; import { getParents } from '~/lib/utils/dom_utils'; import { getMarkdownSource } from '../services/markdown_sourcemap'; -export const multilineInputRegex = /^\s*>>>\s$/gm; - export default Blockquote.extend({ addAttributes() { return { @@ -25,9 +23,15 @@ export default Blockquote.extend({ }, addInputRules() { + const multilineInputRegex = /^\s*>>>\s$/gm; + return [ ...this.parent?.(), - wrappingInputRule(multilineInputRegex, this.type, () => ({ multiline: true })), + wrappingInputRule({ + find: multilineInputRegex, + type: this.type, + getAttributes: () => ({ multiline: true }), + }), ]; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/description_list.js b/app/assets/javascripts/content_editor/extensions/description_list.js index a516dfad2b8..8f5b145cfa3 100644 --- a/app/assets/javascripts/content_editor/extensions/description_list.js +++ b/app/assets/javascripts/content_editor/extensions/description_list.js @@ -1,7 +1,4 @@ -import { Node, mergeAttributes } from '@tiptap/core'; -import { wrappingInputRule } from 'prosemirror-inputrules'; - -export const inputRegex = /^\s*(<dl>)$/; +import { Node, mergeAttributes, wrappingInputRule } from '@tiptap/core'; export default Node.create({ name: 'descriptionList', @@ -18,6 +15,8 @@ export default Node.create({ }, addInputRules() { - return [wrappingInputRule(inputRegex, this.type)]; + const inputRegex = /^\s*(<dl>)$/; + + return [wrappingInputRule({ find: inputRegex, type: this.type })]; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/details.js b/app/assets/javascripts/content_editor/extensions/details.js index e3d54ed01fd..46c906d45b1 100644 --- a/app/assets/javascripts/content_editor/extensions/details.js +++ b/app/assets/javascripts/content_editor/extensions/details.js @@ -1,10 +1,7 @@ -import { Node } from '@tiptap/core'; +import { Node, wrappingInputRule } from '@tiptap/core'; import { VueNodeViewRenderer } from '@tiptap/vue-2'; -import { wrappingInputRule } from 'prosemirror-inputrules'; import DetailsWrapper from '../components/wrappers/details.vue'; -export const inputRegex = /^\s*(<details>)$/; - export default Node.create({ name: 'details', content: 'detailsContent+', @@ -24,7 +21,9 @@ export default Node.create({ }, addInputRules() { - return [wrappingInputRule(inputRegex, this.type)]; + const inputRegex = /^\s*(<details>)$/; + + return [wrappingInputRule({ find: inputRegex, type: this.type })]; }, addCommands() { diff --git a/app/assets/javascripts/content_editor/extensions/emoji.js b/app/assets/javascripts/content_editor/extensions/emoji.js index de608c3aaa2..7f8b5da5f46 100644 --- a/app/assets/javascripts/content_editor/extensions/emoji.js +++ b/app/assets/javascripts/content_editor/extensions/emoji.js @@ -1,9 +1,6 @@ -import { Node } from '@tiptap/core'; -import { InputRule } from 'prosemirror-inputrules'; +import { Node, InputRule } from '@tiptap/core'; import { initEmojiMap, getAllEmoji } from '~/emoji'; -export const emojiInputRegex = /(?:^|\s)((?::)((?:\w+))(?::))$/; - export default Node.create({ name: 'emoji', @@ -54,23 +51,28 @@ export default Node.create({ }, addInputRules() { + const emojiInputRegex = /(?:^|\s)(:(\w+):)$/; + return [ - new InputRule(emojiInputRegex, (state, match, start, end) => { - const [, , name] = match; - const emojis = getAllEmoji(); - const emoji = emojis[name]; - const { tr } = state; + new InputRule({ + find: emojiInputRegex, + handler: ({ state, range: { from, to }, match }) => { + const [, , name] = match; + const emojis = getAllEmoji(); + const emoji = emojis[name]; + const { tr } = state; - if (emoji) { - tr.replaceWith(start, end, [ - state.schema.text(' '), - this.type.create({ name, moji: emoji.e, unicodeVersion: emoji.u, title: emoji.d }), - ]); + if (emoji) { + tr.replaceWith(from, to, [ + state.schema.text(' '), + this.type.create({ name, moji: emoji.e, unicodeVersion: emoji.u, title: emoji.d }), + ]); - return tr; - } + return tr; + } - return null; + return null; + }, }), ]; }, diff --git a/app/assets/javascripts/content_editor/extensions/horizontal_rule.js b/app/assets/javascripts/content_editor/extensions/horizontal_rule.js index c8ec45d835c..c4f31e5f981 100644 --- a/app/assets/javascripts/content_editor/extensions/horizontal_rule.js +++ b/app/assets/javascripts/content_editor/extensions/horizontal_rule.js @@ -1,10 +1,10 @@ import { nodeInputRule } from '@tiptap/core'; import { HorizontalRule } from '@tiptap/extension-horizontal-rule'; -export const hrInputRuleRegExp = /^---$/; - export default HorizontalRule.extend({ addInputRules() { - return [nodeInputRule(hrInputRuleRegExp, this.type)]; + const hrInputRuleRegExp = /^---$/; + + return [nodeInputRule({ find: hrInputRuleRegExp, type: this.type })]; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/html_marks.js b/app/assets/javascripts/content_editor/extensions/html_marks.js index 54adb9efa0c..3abf0e3eee2 100644 --- a/app/assets/javascripts/content_editor/extensions/html_marks.js +++ b/app/assets/javascripts/content_editor/extensions/html_marks.js @@ -60,7 +60,13 @@ export default marks.map((name) => }, addInputRules() { - return [markInputRule(markInputRegex(name), this.type, extractMarkAttributesFromMatch)]; + return [ + markInputRule({ + find: markInputRegex(name), + type: this.type, + getAttributes: extractMarkAttributesFromMatch, + }), + ]; }, }), ); diff --git a/app/assets/javascripts/content_editor/extensions/inline_diff.js b/app/assets/javascripts/content_editor/extensions/inline_diff.js index 3bd328958df..22bb1ac072e 100644 --- a/app/assets/javascripts/content_editor/extensions/inline_diff.js +++ b/app/assets/javascripts/content_editor/extensions/inline_diff.js @@ -1,8 +1,5 @@ import { Mark, markInputRule, mergeAttributes } from '@tiptap/core'; -export const inputRegexAddition = /(\{\+(.+?)\+\})$/gm; -export const inputRegexDeletion = /(\{-(.+?)-\})$/gm; - export default Mark.create({ name: 'inlineDiff', @@ -38,9 +35,20 @@ export default Mark.create({ }, addInputRules() { + const inputRegexAddition = /(\{\+(.+?)\+\})$/gm; + const inputRegexDeletion = /(\{-(.+?)-\})$/gm; + return [ - markInputRule(inputRegexAddition, this.type, () => ({ type: 'addition' })), - markInputRule(inputRegexDeletion, this.type, () => ({ type: 'deletion' })), + markInputRule({ + find: inputRegexAddition, + type: this.type, + getAttributes: () => ({ type: 'addition' }), + }), + markInputRule({ + find: inputRegexDeletion, + type: this.type, + getAttributes: () => ({ type: 'deletion' }), + }), ]; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/link.js b/app/assets/javascripts/content_editor/extensions/link.js index fc0f38e6935..27bc05dce6f 100644 --- a/app/assets/javascripts/content_editor/extensions/link.js +++ b/app/assets/javascripts/content_editor/extensions/link.js @@ -1,9 +1,6 @@ import { markInputRule } from '@tiptap/core'; import { Link } from '@tiptap/extension-link'; -export const markdownLinkSyntaxInputRuleRegExp = /(?:^|\s)\[([\w|\s|-]+)\]\((?<href>.+?)\)$/gm; -export const urlSyntaxRegExp = /(?:^|\s)(?<href>(?:https?:\/\/|www\.)[\S]+)(?:\s|\n)$/gim; - const extractHrefFromMatch = (match) => { return { href: match.groups.href }; }; @@ -26,9 +23,20 @@ export default Link.extend({ openOnClick: false, }, addInputRules() { + const markdownLinkSyntaxInputRuleRegExp = /(?:^|\s)\[([\w|\s|-]+)\]\((?<href>.+?)\)$/gm; + const urlSyntaxRegExp = /(?:^|\s)(?<href>(?:https?:\/\/|www\.)[\S]+)(?:\s|\n)$/gim; + return [ - markInputRule(markdownLinkSyntaxInputRuleRegExp, this.type, extractHrefFromMarkdownLink), - markInputRule(urlSyntaxRegExp, this.type, extractHrefFromMatch), + markInputRule({ + find: markdownLinkSyntaxInputRuleRegExp, + type: this.type, + getAttributes: extractHrefFromMarkdownLink, + }), + markInputRule({ + find: urlSyntaxRegExp, + type: this.type, + getAttributes: extractHrefFromMatch, + }), ]; }, addAttributes() { diff --git a/app/assets/javascripts/content_editor/extensions/math_inline.js b/app/assets/javascripts/content_editor/extensions/math_inline.js index 60f5288dcf6..4844f6feb29 100644 --- a/app/assets/javascripts/content_editor/extensions/math_inline.js +++ b/app/assets/javascripts/content_editor/extensions/math_inline.js @@ -2,8 +2,6 @@ import { Mark, markInputRule } from '@tiptap/core'; import { __ } from '~/locale'; import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; -export const inputRegex = /(?:^|\s)\$`([^`]+)`\$$/gm; - export default Mark.create({ name: 'mathInline', @@ -30,6 +28,8 @@ export default Mark.create({ }, addInputRules() { - return [markInputRule(inputRegex, this.type)]; + const inputRegex = /(?:^|\s)\$`([^`]+)`\$$/gm; + + return [markInputRule({ find: inputRegex, type: this.type })]; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/subscript.js b/app/assets/javascripts/content_editor/extensions/subscript.js index d0766f42308..a8c087e8bf0 100644 --- a/app/assets/javascripts/content_editor/extensions/subscript.js +++ b/app/assets/javascripts/content_editor/extensions/subscript.js @@ -4,6 +4,12 @@ import { markInputRegex, extractMarkAttributesFromMatch } from '../services/mark export default Subscript.extend({ addInputRules() { - return [markInputRule(markInputRegex('sub'), this.type, extractMarkAttributesFromMatch)]; + return [ + markInputRule({ + find: markInputRegex('sub'), + type: this.type, + getAttributes: extractMarkAttributesFromMatch, + }), + ]; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/superscript.js b/app/assets/javascripts/content_editor/extensions/superscript.js index 6cd814977ea..b86906f01f2 100644 --- a/app/assets/javascripts/content_editor/extensions/superscript.js +++ b/app/assets/javascripts/content_editor/extensions/superscript.js @@ -4,6 +4,12 @@ import { markInputRegex, extractMarkAttributesFromMatch } from '../services/mark export default Superscript.extend({ addInputRules() { - return [markInputRule(markInputRegex('sup'), this.type, extractMarkAttributesFromMatch)]; + return [ + markInputRule({ + find: markInputRegex('sup'), + type: this.type, + getAttributes: extractMarkAttributesFromMatch, + }), + ]; }, }); diff --git a/app/assets/javascripts/content_editor/extensions/table_of_contents.js b/app/assets/javascripts/content_editor/extensions/table_of_contents.js index 9e31158837e..a8882f9ede4 100644 --- a/app/assets/javascripts/content_editor/extensions/table_of_contents.js +++ b/app/assets/javascripts/content_editor/extensions/table_of_contents.js @@ -1,10 +1,7 @@ -import { Node } from '@tiptap/core'; -import { InputRule } from 'prosemirror-inputrules'; +import { Node, InputRule } from '@tiptap/core'; import { s__ } from '~/locale'; import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; -export const inputRuleRegExps = [/^\[\[_TOC_\]\]$/, /^\[TOC\]$/]; - export default Node.create({ name: 'tableOfContents', @@ -34,17 +31,21 @@ export default Node.create({ addInputRules() { const { type } = this; + const inputRuleRegExps = [/^\[\[_TOC_\]\]$/, /^\[TOC\]$/]; return inputRuleRegExps.map( (regex) => - new InputRule(regex, (state, match, start, end) => { - const { tr } = state; + new InputRule({ + find: regex, + handler: ({ state, range: { from, to }, match }) => { + const { tr } = state; - if (match) { - tr.replaceWith(start - 1, end, type.create()); - } + if (match) { + tr.replaceWith(from - 1, to, type.create()); + } - return tr; + return tr; + }, }), ); }, diff --git a/app/assets/javascripts/content_editor/extensions/word_break.js b/app/assets/javascripts/content_editor/extensions/word_break.js index 93b42466850..fa7e02f8cc8 100644 --- a/app/assets/javascripts/content_editor/extensions/word_break.js +++ b/app/assets/javascripts/content_editor/extensions/word_break.js @@ -1,7 +1,5 @@ import { Node, mergeAttributes, nodeInputRule } from '@tiptap/core'; -export const inputRegex = /^<wbr>$/; - export default Node.create({ name: 'wordBreak', inline: true, @@ -24,6 +22,8 @@ export default Node.create({ }, addInputRules() { - return [nodeInputRule(inputRegex, this.type)]; + const inputRegex = /^<wbr>$/; + + return [nodeInputRule({ find: inputRegex, type: this.type })]; }, }); diff --git a/app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js b/app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js index d26f32a7e7a..9b1cb76f845 100644 --- a/app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js +++ b/app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js @@ -1,5 +1,5 @@ import { mapValues } from 'lodash'; -import { InputRule } from 'prosemirror-inputrules'; +import { InputRule } from '@tiptap/core'; import { ENTER_KEY, BACKSPACE_KEY } from '~/lib/utils/keys'; import Tracking from '~/tracking'; import { @@ -17,17 +17,20 @@ const trackKeyboardShortcut = (contentType, commandFn, shortcut) => () => { }; const trackInputRule = (contentType, inputRule) => { - return new InputRule(inputRule.match, (...args) => { - const result = inputRule.handler(...args); + return new InputRule({ + find: inputRule.find, + handler: (...args) => { + const result = inputRule.handler(...args); - if (result) { - Tracking.event(undefined, INPUT_RULE_TRACKING_ACTION, { - label: CONTENT_EDITOR_TRACKING_LABEL, - property: contentType, - }); - } + if (result !== null) { + Tracking.event(undefined, INPUT_RULE_TRACKING_ACTION, { + label: CONTENT_EDITOR_TRACKING_LABEL, + property: contentType, + }); + } - return result; + return result; + }, }); }; diff --git a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue index 05858c7469d..d6187e1b527 100644 --- a/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue +++ b/app/assets/javascripts/related_merge_requests/components/related_merge_requests.vue @@ -64,58 +64,51 @@ export default { </script> <template> - <div - v-if="isFetchingMergeRequests || (!isFetchingMergeRequests && totalCount)" - id="related-merge-requests" - > - <div id="merge-requests" class="card card-slim mt-3"> + <div v-if="isFetchingMergeRequests || (!isFetchingMergeRequests && totalCount)"> + <div class="card card-slim gl-mt-5"> <div class="card-header"> - <div class="card-title mt-0 mb-0 h5 merge-requests-title position-relative"> + <div + class="card-title gl-relative gl-display-flex gl-align-items-center gl-line-height-20 gl-font-weight-bold gl-m-0" + > <gl-link - id="user-content-related-merge-requests" - class="anchor position-absolute text-decoration-none" + class="anchor gl-absolute gl-text-decoration-none" href="#related-merge-requests" - aria-hidden="true" + aria-labelledby="related-merge-requests" /> - <span class="mr-1"> + <h3 id="related-merge-requests" class="gl-font-base gl-m-0"> {{ __('Related merge requests') }} - </span> - <div v-if="totalCount" class="d-inline-flex lh-100 align-middle"> - <div - class="mr-count-badge gl-display-inline-flex gl-align-items-center gl-py-2 gl-px-3" - > - <svg class="s16 mr-1 text-secondary"> - <gl-icon name="merge-request" class="mr-1 text-secondary" /> - </svg> - <span class="js-items-count">{{ totalCount }}</span> - </div> - </div> + </h3> + <template v-if="totalCount"> + <gl-icon name="merge-request" class="gl-ml-5 gl-mr-2 gl-text-gray-500" /> + <span data-testid="count">{{ totalCount }}</span> + </template> </div> </div> - <div> - <div v-if="isFetchingMergeRequests" class="qa-related-merge-requests-loading-icon"> - <gl-loading-icon size="sm" label="Fetching related merge requests" class="py-2" /> - </div> - <ul v-else class="content-list related-items-list"> - <li v-for="mr in mergeRequests" :key="mr.id" class="list-item pt-0 pb-0"> - <related-issuable-item - :id-key="mr.id" - :display-reference="mr.reference" - :title="mr.title" - :milestone="mr.milestone" - :assignees="getAssignees(mr)" - :created-at="mr.created_at" - :closed-at="mr.closed_at" - :merged-at="mr.merged_at" - :path="mr.web_url" - :state="mr.state" - :is-merge-request="true" - :pipeline-status="mr.head_pipeline && mr.head_pipeline.detailed_status" - path-id-separator="!" - /> - </li> - </ul> - </div> + <gl-loading-icon + v-if="isFetchingMergeRequests" + size="sm" + label="Fetching related merge requests" + class="gl-py-3" + /> + <ul v-else class="content-list related-items-list"> + <li v-for="mr in mergeRequests" :key="mr.id" class="list-item gl-m-0! gl-p-0!"> + <related-issuable-item + :id-key="mr.id" + :display-reference="mr.reference" + :title="mr.title" + :milestone="mr.milestone" + :assignees="getAssignees(mr)" + :created-at="mr.created_at" + :closed-at="mr.closed_at" + :merged-at="mr.merged_at" + :path="mr.web_url" + :state="mr.state" + :is-merge-request="true" + :pipeline-status="mr.head_pipeline && mr.head_pipeline.detailed_status" + path-id-separator="!" + /> + </li> + </ul> </div> <div v-if="hasClosingMergeRequest && !isFetchingMergeRequests" diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue index 6c70a8c33db..bc13150c99c 100644 --- a/app/assets/javascripts/security_configuration/components/app.vue +++ b/app/assets/javascripts/security_configuration/components/app.vue @@ -1,5 +1,5 @@ <script> -import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui'; +import { GlTab, GlTabs, GlSprintf, GlLink, GlAlert } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue'; import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue'; @@ -31,6 +31,7 @@ export default { AutoDevOpsAlert, AutoDevOpsEnabledAlert, FeatureCard, + GlAlert, GlLink, GlSprintf, GlTab, @@ -79,6 +80,7 @@ export default { data() { return { autoDevopsEnabledAlertDismissedProjects: [], + errorMessage: '', }; }, computed: { @@ -106,6 +108,12 @@ export default { dismissedProjects.add(this.projectPath); this.autoDevopsEnabledAlertDismissedProjects = Array.from(dismissedProjects); }, + onError(message) { + this.errorMessage = message; + }, + dismissAlert() { + this.errorMessage = ''; + }, }, autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY, }; @@ -113,6 +121,16 @@ export default { <template> <article> + <gl-alert + v-if="errorMessage" + sticky + class="gl-top-8 gl-z-index-1" + data-testid="manage-via-mr-error-alert" + variant="danger" + @dismiss="dismissAlert" + > + {{ errorMessage }} + </gl-alert> <local-storage-sync v-model="autoDevopsEnabledAlertDismissedProjects" :storage-key="$options.autoDevopsEnabledAlertStorageKey" @@ -174,6 +192,7 @@ export default { data-testid="security-testing-card" :feature="feature" class="gl-mb-6" + @error="onError" /> </template> </section-layout> @@ -207,6 +226,7 @@ export default { :key="feature.type" :feature="feature" class="gl-mb-6" + @error="onError" /> </template> </section-layout> diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue index 86afdbfeb8c..33d72b54f86 100644 --- a/app/assets/javascripts/security_configuration/components/feature_card.vue +++ b/app/assets/javascripts/security_configuration/components/feature_card.vue @@ -66,6 +66,11 @@ export default { return Boolean(name && description && configurationText); }, }, + methods: { + onError(message) { + this.$emit('error', message); + }, + }, i18n: { enabled: s__('SecurityConfiguration|Enabled'), notEnabled: s__('SecurityConfiguration|Not enabled'), @@ -129,6 +134,7 @@ export default { category="primary" class="gl-mt-5" :data-qa-selector="`${feature.type}_mr_button`" + @error="onError" /> <gl-button diff --git a/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue b/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue index 3a4453bc7ae..e0669b3ed27 100644 --- a/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue +++ b/app/assets/javascripts/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue @@ -26,6 +26,11 @@ export default { type: Number, required: true, }, + injectedArtifacts: { + type: Array, + required: false, + default: () => [], + }, }, data() { return { @@ -56,6 +61,9 @@ export default { isLoadingReportArtifacts() { return this.$apollo.queries.reportArtifacts.loading; }, + mergedReportArtifacts() { + return [...this.reportArtifacts, ...this.injectedArtifacts]; + }, }, methods: { showError(error) { @@ -77,7 +85,7 @@ export default { <template> <security-report-download-dropdown :title="s__('SecurityReports|Download results')" - :artifacts="reportArtifacts" + :artifacts="mergedReportArtifacts" :loading="isLoadingReportArtifacts" /> </template> diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 79c23d73a68..def7703231c 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -132,16 +132,7 @@ For example, the following two definitions are equal: When a CI job runs in a Docker container, the `before_script`, `script`, and `after_script` commands run in the `/builds/<project-path>/` directory. Your image may have a different default `WORKDIR` defined. To move to your `WORKDIR`, save the `WORKDIR` as an environment variable so you can reference it in the container during the job's runtime. -### Available settings for `image` - -> Introduced in GitLab and GitLab Runner 9.4. - -| Setting | Required | Description | -|------------|----------| ----------- | -| `name` | Yes, when used with any other option. | Full name of the image. It should contain the registry part if needed. | -| `entrypoint` | No. | Command or script to execute as the container's entrypoint. It's translated to Docker's `--entrypoint` option while creating the container. The syntax is similar to [`Dockerfile`'s `ENTRYPOINT`](https://docs.docker.com/engine/reference/builder/#entrypoint) directive, where each shell token is a separate string in the array. | - -### Overriding the entrypoint of an image +### Override the entrypoint of an image > Introduced in GitLab and GitLab Runner 9.4. Read more about the [extended configuration options](../docker/using_docker_images.md#extended-docker-configuration-options). diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md index 2ce0250ad6b..f1c21f44e4e 100644 --- a/doc/ci/docker/using_kaniko.md +++ b/doc/ci/docker/using_kaniko.md @@ -37,7 +37,7 @@ few important details: - The kaniko debug image is recommended (`gcr.io/kaniko-project/executor:debug`) because it has a shell, and a shell is required for an image to be used with GitLab CI/CD. -- The entrypoint needs to be [overridden](using_docker_images.md#overriding-the-entrypoint-of-an-image), +- The entrypoint needs to be [overridden](using_docker_images.md#override-the-entrypoint-of-an-image), otherwise the build script doesn't run. - A Docker `config.json` file needs to be created with the authentication information for the desired container registry. diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md index 0df653acca4..dc5faf0188e 100644 --- a/doc/ci/git_submodules.md +++ b/doc/ci/git_submodules.md @@ -59,3 +59,8 @@ To make submodules work correctly in CI/CD jobs: variables: GIT_SUBMODULE_STRATEGY: recursive ``` + +If you use the [`CI_JOB_TOKEN`](jobs/ci_job_token.md) to clone a submodule in a +pipeline job, the user executing the job must be assigned to a role that has +[permission](../user/permissions.md#gitlab-cicd-permissions) to trigger a pipeline +in the upstream submodule project. diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 205d49955d9..73e26cdee39 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -645,24 +645,87 @@ The following topics explain how to use keywords to configure CI/CD pipelines. ### `image` -Use `image` to specify [a Docker image](../docker/using_docker_images.md#what-is-an-image) to use for the job. +Use `image` to specify a Docker image that the job runs in. -For: +**Keyword type**: Job keyword. You can use it only as part of a job or in the +[`default:` section](#custom-default-keyword-values). -- Usage examples, see [Define `image` in the `.gitlab-ci.yml` file](../docker/using_docker_images.md#define-image-in-the-gitlab-ciyml-file). -- Detailed usage information, refer to [Docker integration](../docker/index.md) documentation. +**Possible inputs**: The name of the image, including the registry path if needed, in one of these formats: + +- `<image-name>` (Same as using `<image-name>` with the `latest` tag) +- `<image-name>:<tag>` +- `<image-name>@<digest>` + +**Example of `image`**: + +```yaml +default: + image: ruby:3.0 + +rspec: + script: bundle exec rspec + +rspec 2.7: + image: registry.example.com/my-group/my-project/ruby:2.7 + script: bundle exec rspec +``` + +In this example, the `ruby:3.0` image is the default for all jobs in the pipeline. +The `rspec 2.7` job does not use the default, because it overrides the default with +a job-specific `image:` section. + +**Related topics**: + +- [Run your CI/CD jobs in Docker containers](../docker/using_docker_images.md). #### `image:name` -An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options). +The name of the Docker image that the job runs in. Similar to [`image:`](#image) used by itself. -For more information, see [Available settings for `image`](../docker/using_docker_images.md#available-settings-for-image). +**Keyword type**: Job keyword. You can use it only as part of a job or in the +[`default:` section](#custom-default-keyword-values). + +**Possible inputs**: The name of the image, including the registry path if needed, in one of these formats: + +- `<image-name>` (Same as using `<image-name>` with the `latest` tag) +- `<image-name>:<tag>` +- `<image-name>@<digest>` + +**Example of `image:name`**: + +```yaml +image: + name: "registry.example.com/my/image:latest" +``` + +**Related topics**: + +- [Run your CI/CD jobs in Docker containers](../docker/using_docker_images.md). #### `image:entrypoint` -An [extended Docker configuration option](../docker/using_docker_images.md#extended-docker-configuration-options). +Command or script to execute as the container's entry point. + +When the Docker container is created, the `entrypoint` is translated to the Docker `--entrypoint` option. +The syntax is similar to the [Dockerfile `ENTRYPOINT` directive](https://docs.docker.com/engine/reference/builder/#entrypoint), +where each shell token is a separate string in the array. + +**Keyword type**: Job keyword. You can use it only as part of a job or in the +[`default:` section](#custom-default-keyword-values). + +**Possible inputs**: A string. + +**Example of `image:entrypoint`**: + +```yaml +image: + name: super/sql:experimental + entrypoint: [""] +``` + +**Related topics**: -For more information, see [Available settings for `image`](../docker/using_docker_images.md#available-settings-for-image). +- [Override the entrypoint of an image](../docker/using_docker_images.md#override-the-entrypoint-of-an-image). #### `services` diff --git a/doc/integration/jira/development_panel.md b/doc/integration/jira/development_panel.md index 6fa084ee872..168c3135f01 100644 --- a/doc/integration/jira/development_panel.md +++ b/doc/integration/jira/development_panel.md @@ -38,7 +38,7 @@ After the integration is [set up on GitLab and Jira](#configure-the-integration) - Refer to any Jira issue by its ID (in uppercase) in GitLab branch names, commit messages, and merge request titles. - See the linked branches, commits, and merge requests in Jira issues. -- Create GitLab branches from Jira issues ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66032) in GitLab 14.2). +- Create GitLab branches from Jira Cloud issues ([introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66032) in GitLab 14.2). At this time, merge requests are called "pull requests" in Jira issues. This name may change in a future Jira release. diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 2c062c2c592..4c440b486b0 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -83,9 +83,11 @@ can assign, categorize, and track progress on a merge request: ## Add changes to a merge request If you have permission to add changes to a merge request, you can add your changes -to an existing merge request in several ways, depending on the complexity of your change and whether you need access to a development environment: +to an existing merge request in several ways, depending on the complexity of your +change and whether you need access to a development environment: -- [Edit changes in the Web IDE](../web_ide/index.md) in your browser. Use this +- [Edit changes in the Web IDE](../web_ide/index.md) in your browser with the + <kbd>.</kbd> [keyboard shortcut](../../shortcuts.md). Use this browser-based method to edit multiple files, or if you are not comfortable with Git commands. You cannot run tests from the Web IDE. - [Edit changes in Gitpod](../../../integration/gitpod.md#launch-gitpod-in-gitlab), if you diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index 71cf0c03549..7d655128268 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -15,7 +15,8 @@ projects by providing an advanced editor with commit staging. ## Open the Web IDE -You can open the Web IDE when viewing a file, from the repository file list, +Use the <kbd>.</kbd> [keyboard shortcut](../../shortcuts.md) to open the Web IDE. +You can also open the Web IDE when viewing a file, from the repository file list, and from merge requests: - *When viewing a file, or the repository file list* - diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md index f46c5428248..d6cbbf352fc 100644 --- a/doc/user/shortcuts.md +++ b/doc/user/shortcuts.md @@ -43,6 +43,7 @@ These shortcuts are available in most areas of GitLab: | <kbd>Shift</kbd> + <kbd>t</kbd> | Go to your To-Do List page. | | <kbd>p</kbd> + <kbd>b</kbd> | Show or hide the Performance Bar. | | <kbd>g</kbd> + <kbd>x</kbd> | Toggle between [GitLab](https://gitlab.com/) and [GitLab Next](https://next.gitlab.com/) (GitLab SaaS only). | +| <kbd>.</kbd> | Open the [Web IDE](project/web_ide/index.md). | Additionally, the following shortcuts are available when editing text in text fields (for example, comments, replies, issue descriptions, and merge request @@ -101,6 +102,7 @@ These shortcuts are available when viewing issues and [merge requests](project/m | <kbd>]</kbd> or <kbd>j</kbd> | Move to next file (merge requests only). | | <kbd>[</kbd> or <kbd>k</kbd> | Move to previous file (merge requests only). | | <kbd>b</kbd> | Copy source branch name (merge requests only). | +| <kbd>.</kbd> | Open the [Web IDE](project/web_ide/index.md). | ### Project files @@ -114,6 +116,7 @@ These shortcuts are available when browsing the files in a project (go to | <kbd>enter</kbd> | Open selection. | | <kbd>Escape</kbd> | Go back to file list screen (only while searching for files, **Repository > Files**, then select **Find File**). | | <kbd>y</kbd> | Go to file permalink (only while viewing a file). | +| <kbd>.</kbd> | Open the [Web IDE](project/web_ide/index.md). | ### Web IDE diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index a1a0356ff58..bfa4e4cf5f8 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -27,6 +27,8 @@ module Gitlab path: request.fullpath ) Rack::Response.new('', 403).finish + rescue Gitlab::Auth::MissingPersonalAccessTokenError + Rack::Response.new('', 401).finish end private diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e118cfe00e4..23d729a63b7 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9829,7 +9829,7 @@ msgstr "" msgid "CreateValueStreamForm|Restore stage" msgstr "" -msgid "CreateValueStreamForm|Save Value Stream" +msgid "CreateValueStreamForm|Save value stream" msgstr "" msgid "CreateValueStreamForm|Select end event" @@ -29682,9 +29682,6 @@ msgstr "" msgid "Save Changes" msgstr "" -msgid "Save Value Stream" -msgstr "" - msgid "Save application" msgstr "" @@ -30418,9 +30415,6 @@ msgstr "" msgid "SecurityReports|Download scanned URLs" msgstr "" -msgid "SecurityReports|Download scanned resources" -msgstr "" - msgid "SecurityReports|Either you don't have permission to view this dashboard or the dashboard has not been setup. Please check your permission settings with your administrator or check your dashboard configurations to proceed." msgstr "" @@ -30631,6 +30625,9 @@ msgstr "" msgid "SecurityReports|Your feedback is important to us! We will ask again in a week." msgstr "" +msgid "SecurityReports|scanned resources" +msgstr "" + msgid "See example DevOps Score page in our documentation." msgstr "" diff --git a/package.json b/package.json index b2d0c1d56c3..77a46ea8f4c 100644 --- a/package.json +++ b/package.json @@ -63,36 +63,36 @@ "@rails/ujs": "6.1.4-1", "@sentry/browser": "5.30.0", "@sourcegraph/code-host-integration": "0.0.60", - "@tiptap/core": "^2.0.0-beta.118", - "@tiptap/extension-blockquote": "^2.0.0-beta.15", - "@tiptap/extension-bold": "^2.0.0-beta.15", - "@tiptap/extension-bullet-list": "^2.0.0-beta.15", - "@tiptap/extension-code": "^2.0.0-beta.16", - "@tiptap/extension-code-block-lowlight": "2.0.0-beta.41", + "@tiptap/core": "^2.0.0-beta.125", + "@tiptap/extension-blockquote": "^2.0.0-beta.19", + "@tiptap/extension-bold": "^2.0.0-beta.19", + "@tiptap/extension-bullet-list": "^2.0.0-beta.18", + "@tiptap/extension-code": "^2.0.0-beta.20", + "@tiptap/extension-code-block-lowlight": "2.0.0-beta.47", "@tiptap/extension-document": "^2.0.0-beta.13", "@tiptap/extension-dropcursor": "^2.0.0-beta.19", - "@tiptap/extension-gapcursor": "^2.0.0-beta.24", - "@tiptap/extension-hard-break": "^2.0.0-beta.21", - "@tiptap/extension-heading": "^2.0.0-beta.15", + "@tiptap/extension-gapcursor": "^2.0.0-beta.27", + "@tiptap/extension-hard-break": "^2.0.0-beta.24", + "@tiptap/extension-heading": "^2.0.0-beta.18", "@tiptap/extension-history": "^2.0.0-beta.16", - "@tiptap/extension-horizontal-rule": "^2.0.0-beta.21", - "@tiptap/extension-image": "^2.0.0-beta.15", - "@tiptap/extension-italic": "^2.0.0-beta.15", - "@tiptap/extension-link": "^2.0.0-beta.20", + "@tiptap/extension-horizontal-rule": "^2.0.0-beta.24", + "@tiptap/extension-image": "^2.0.0-beta.19", + "@tiptap/extension-italic": "^2.0.0-beta.19", + "@tiptap/extension-link": "^2.0.0-beta.23", "@tiptap/extension-list-item": "^2.0.0-beta.14", - "@tiptap/extension-ordered-list": "^2.0.0-beta.16", + "@tiptap/extension-ordered-list": "^2.0.0-beta.19", "@tiptap/extension-paragraph": "^2.0.0-beta.17", - "@tiptap/extension-strike": "^2.0.0-beta.17", + "@tiptap/extension-strike": "^2.0.0-beta.21", "@tiptap/extension-subscript": "^2.0.0-beta.4", "@tiptap/extension-superscript": "^2.0.0-beta.4", - "@tiptap/extension-table": "^2.0.0-beta.31", + "@tiptap/extension-table": "^2.0.0-beta.34", "@tiptap/extension-table-cell": "^2.0.0-beta.15", "@tiptap/extension-table-header": "^2.0.0-beta.17", "@tiptap/extension-table-row": "^2.0.0-beta.14", - "@tiptap/extension-task-item": "^2.0.0-beta.18", - "@tiptap/extension-task-list": "^2.0.0-beta.17", + "@tiptap/extension-task-item": "^2.0.0-beta.21", + "@tiptap/extension-task-list": "^2.0.0-beta.18", "@tiptap/extension-text": "^2.0.0-beta.13", - "@tiptap/vue-2": "^2.0.0-beta.57", + "@tiptap/vue-2": "^2.0.0-beta.60", "@toast-ui/editor": "^2.5.2", "@toast-ui/vue-editor": "^2.5.2", "apollo-cache-inmemory": "^1.6.6", @@ -159,12 +159,11 @@ "popper.js": "^1.16.1", "portal-vue": "^2.1.7", "prismjs": "^1.21.0", - "prosemirror-inputrules": "^1.1.3", "prosemirror-markdown": "^1.6.0", "prosemirror-model": "^1.14.3", "prosemirror-state": "^1.3.4", "prosemirror-tables": "^1.1.1", - "prosemirror-view": "^1.20.2", + "prosemirror-view": "^1.20.3", "raphael": "^2.2.7", "raw-loader": "^4.0.2", "scrollparent": "^2.0.1", @@ -246,7 +245,7 @@ "prettier": "2.2.1", "prosemirror-schema-basic": "^1.1.2", "prosemirror-schema-list": "^1.1.6", - "prosemirror-test-builder": "^1.0.4", + "prosemirror-test-builder": "^1.0.5", "purgecss": "^4.0.3", "purgecss-from-html": "^4.0.3", "readdir-enhanced": "^2.2.4", diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb index 748da2dc6b7..d0313670e8b 100644 --- a/qa/qa/resource/fork.rb +++ b/qa/qa/resource/fork.rb @@ -39,6 +39,8 @@ module QA # Sign out as admin and sign is as the fork user Flow::Login.sign_in(as: user) + @api_client = Runtime::API::Client.new(:gitlab, is_new_session: false, user: user) + upstream.visit! Page::Project::Show.perform(&:fork_project) diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb index 07d4271eed7..2dcabb38b8f 100644 --- a/spec/features/issuables/markdown_references/internal_references_spec.rb +++ b/spec/features/issuables/markdown_references/internal_references_spec.rb @@ -53,9 +53,7 @@ RSpec.describe "Internal references", :js do end it "doesn't show any references", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do - page.within(".issue-details") do - expect(page).not_to have_content("#merge-requests .merge-requests-title") - end + expect(page).not_to have_text 'Related merge requests' end end @@ -65,12 +63,9 @@ RSpec.describe "Internal references", :js do end it "shows references", :sidekiq_might_not_need_inline do - page.within("#merge-requests .merge-requests-title") do - expect(page).to have_content("Related merge requests") - expect(page).to have_css(".mr-count-badge") - end + expect(page).to have_text 'Related merge requests 1' - page.within("#merge-requests ul") do + page.within('.related-items-list') do expect(page).to have_content(private_project_merge_request.title) expect(page).to have_css(".ic-issue-open-m") end @@ -122,9 +117,7 @@ RSpec.describe "Internal references", :js do end it "doesn't show any references", quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/257832' do - page.within(".merge-request-details") do - expect(page).not_to have_content("#merge-requests .merge-requests-title") - end + expect(page).not_to have_text 'Related merge requests' end end diff --git a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js index e2726b93ea5..41bd492148e 100644 --- a/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js +++ b/spec/frontend/clusters/components/remove_cluster_confirmation_spec.js @@ -1,18 +1,20 @@ -import { GlModal } from '@gitlab/ui'; +import { GlModal, GlSprintf } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import { stubComponent } from 'helpers/stub_component'; import RemoveClusterConfirmation from '~/clusters/components/remove_cluster_confirmation.vue'; import SplitButton from '~/vue_shared/components/split_button.vue'; describe('Remove cluster confirmation modal', () => { let wrapper; - const createComponent = (props = {}) => { + const createComponent = ({ props = {}, stubs = {} } = {}) => { wrapper = mount(RemoveClusterConfirmation, { propsData: { clusterPath: 'clusterPath', clusterName: 'clusterName', ...props, }, + stubs, }); }; @@ -27,35 +29,44 @@ describe('Remove cluster confirmation modal', () => { }); describe('split button dropdown', () => { - const findModal = () => wrapper.find(GlModal).vm; - const findSplitButton = () => wrapper.find(SplitButton); + const findModal = () => wrapper.findComponent(GlModal); + const findSplitButton = () => wrapper.findComponent(SplitButton); beforeEach(() => { - createComponent({ clusterName: 'my-test-cluster' }); - jest.spyOn(findModal(), 'show').mockReturnValue(); + createComponent({ + props: { clusterName: 'my-test-cluster' }, + stubs: { GlSprintf, GlModal: stubComponent(GlModal) }, + }); + jest.spyOn(findModal().vm, 'show').mockReturnValue(); }); - it('opens modal with "cleanup" option', () => { + it('opens modal with "cleanup" option', async () => { findSplitButton().vm.$emit('remove-cluster-and-cleanup'); - return wrapper.vm.$nextTick().then(() => { - expect(findModal().show).toHaveBeenCalled(); - expect(wrapper.vm.confirmCleanup).toEqual(true); - }); + await wrapper.vm.$nextTick(); + + expect(findModal().vm.show).toHaveBeenCalled(); + expect(wrapper.vm.confirmCleanup).toEqual(true); + expect(findModal().html()).toContain( + '<strong>To remove your integration and resources, type <code>my-test-cluster</code> to confirm:</strong>', + ); }); - it('opens modal without "cleanup" option', () => { + it('opens modal without "cleanup" option', async () => { findSplitButton().vm.$emit('remove-cluster'); - return wrapper.vm.$nextTick().then(() => { - expect(findModal().show).toHaveBeenCalled(); - expect(wrapper.vm.confirmCleanup).toEqual(false); - }); + await wrapper.vm.$nextTick(); + + expect(findModal().vm.show).toHaveBeenCalled(); + expect(wrapper.vm.confirmCleanup).toEqual(false); + expect(findModal().html()).toContain( + '<strong>To remove your integration, type <code>my-test-cluster</code> to confirm:</strong>', + ); }); describe('with cluster management project', () => { beforeEach(() => { - createComponent({ hasManagementProject: true }); + createComponent({ props: { hasManagementProject: true } }); }); it('renders regular button instead', () => { diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js index d4f05a25bd6..48dab91709e 100644 --- a/spec/frontend/content_editor/extensions/attachment_spec.js +++ b/spec/frontend/content_editor/extensions/attachment_spec.js @@ -74,10 +74,10 @@ describe('content_editor/extensions/attachment', () => { }); it.each` - eventType | propName | eventData | output - ${'paste'} | ${'handlePaste'} | ${{ clipboardData: { files: [attachmentFile] } }} | ${true} - ${'paste'} | ${'handlePaste'} | ${{ clipboardData: { files: [] } }} | ${undefined} - ${'drop'} | ${'handleDrop'} | ${{ dataTransfer: { files: [attachmentFile] } }} | ${true} + eventType | propName | eventData | output + ${'paste'} | ${'handlePaste'} | ${{ clipboardData: { getData: jest.fn(), files: [attachmentFile] } }} | ${true} + ${'paste'} | ${'handlePaste'} | ${{ clipboardData: { getData: jest.fn(), files: [] } }} | ${undefined} + ${'drop'} | ${'handleDrop'} | ${{ dataTransfer: { getData: jest.fn(), files: [attachmentFile] } }} | ${true} `('handles $eventType properly', ({ eventType, propName, eventData, output }) => { const event = Object.assign(new Event(eventType), eventData); const handled = tiptapEditor.view.someProp(propName, (eventHandler) => { diff --git a/spec/frontend/content_editor/extensions/blockquote_spec.js b/spec/frontend/content_editor/extensions/blockquote_spec.js index c5b5044352d..1644647ba69 100644 --- a/spec/frontend/content_editor/extensions/blockquote_spec.js +++ b/spec/frontend/content_editor/extensions/blockquote_spec.js @@ -1,19 +1,37 @@ -import { multilineInputRegex } from '~/content_editor/extensions/blockquote'; +import Blockquote from '~/content_editor/extensions/blockquote'; +import { createTestEditor, createDocBuilder, triggerNodeInputRule } from '../test_utils'; describe('content_editor/extensions/blockquote', () => { - describe.each` - input | matches - ${'>>> '} | ${true} - ${' >>> '} | ${true} - ${'\t>>> '} | ${true} - ${'>> '} | ${false} - ${'>>>x '} | ${false} - ${'> '} | ${false} - `('multilineInputRegex', ({ input, matches }) => { - it(`${matches ? 'matches' : 'does not match'}: "${input}"`, () => { - const match = new RegExp(multilineInputRegex).test(input); + let tiptapEditor; + let doc; + let p; + let blockquote; - expect(match).toBe(matches); - }); + beforeEach(() => { + tiptapEditor = createTestEditor({ extensions: [Blockquote] }); + + ({ + builders: { doc, p, blockquote }, + } = createDocBuilder({ + tiptapEditor, + names: { + blockquote: { nodeType: Blockquote.name }, + }, + })); + }); + + it.each` + input | insertedNode + ${'>>> '} | ${() => blockquote({ multiline: true }, p())} + ${'> '} | ${() => blockquote(p())} + ${' >>> '} | ${() => blockquote({ multiline: true }, p())} + ${'>> '} | ${() => p()} + ${'>>>x '} | ${() => p()} + `('with input=$input, then should insert a $insertedNode', ({ input, insertedNode }) => { + const expectedDoc = doc(insertedNode()); + + triggerNodeInputRule({ tiptapEditor, inputRuleText: input }); + + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); }); diff --git a/spec/frontend/content_editor/extensions/emoji_spec.js b/spec/frontend/content_editor/extensions/emoji_spec.js index c1b8dc9bdbb..939c46e991a 100644 --- a/spec/frontend/content_editor/extensions/emoji_spec.js +++ b/spec/frontend/content_editor/extensions/emoji_spec.js @@ -1,6 +1,6 @@ import { initEmojiMock } from 'helpers/emoji'; import Emoji from '~/content_editor/extensions/emoji'; -import { createTestEditor, createDocBuilder } from '../test_utils'; +import { createTestEditor, createDocBuilder, triggerNodeInputRule } from '../test_utils'; describe('content_editor/extensions/emoji', () => { let tiptapEditor; @@ -28,18 +28,16 @@ describe('content_editor/extensions/emoji', () => { describe('when typing a valid emoji input rule', () => { it('inserts an emoji node', () => { - const { view } = tiptapEditor; - const { selection } = view.state; const expectedDoc = doc( p( ' ', emoji({ moji: '❤', name: 'heart', title: 'heavy black heart', unicodeVersion: '1.1' }), ), ); - // Triggers the event handler that input rules listen to - view.someProp('handleTextInput', (f) => f(view, selection.from, selection.to, ':heart:')); - expect(eq(tiptapEditor.state.doc, expectedDoc)).toBe(true); + triggerNodeInputRule({ tiptapEditor, inputRuleText: ':heart:' }); + + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); }); diff --git a/spec/frontend/content_editor/extensions/horizontal_rule_spec.js b/spec/frontend/content_editor/extensions/horizontal_rule_spec.js index a1bc7f0e8ed..322c04a42e1 100644 --- a/spec/frontend/content_editor/extensions/horizontal_rule_spec.js +++ b/spec/frontend/content_editor/extensions/horizontal_rule_spec.js @@ -1,20 +1,39 @@ -import { hrInputRuleRegExp } from '~/content_editor/extensions/horizontal_rule'; +import HorizontalRule from '~/content_editor/extensions/horizontal_rule'; +import { createTestEditor, createDocBuilder, triggerNodeInputRule } from '../test_utils'; describe('content_editor/extensions/horizontal_rule', () => { - describe.each` - input | matches - ${'---'} | ${true} - ${'--'} | ${false} - ${'---x'} | ${false} - ${' ---x'} | ${false} - ${' --- '} | ${false} - ${'x---x'} | ${false} - ${'x---'} | ${false} - `('hrInputRuleRegExp', ({ input, matches }) => { - it(`${matches ? 'matches' : 'does not match'}: "${input}"`, () => { - const match = new RegExp(hrInputRuleRegExp).test(input); + let tiptapEditor; + let doc; + let p; + let horizontalRule; - expect(match).toBe(matches); - }); + beforeEach(() => { + tiptapEditor = createTestEditor({ extensions: [HorizontalRule] }); + + ({ + builders: { doc, p, horizontalRule }, + } = createDocBuilder({ + tiptapEditor, + names: { + horizontalRule: { nodeType: HorizontalRule.name }, + }, + })); + }); + + it.each` + input | insertedNodes + ${'---'} | ${() => [p(), horizontalRule()]} + ${'--'} | ${() => [p()]} + ${'---x'} | ${() => [p()]} + ${' ---x'} | ${() => [p()]} + ${' --- '} | ${() => [p()]} + ${'x---x'} | ${() => [p()]} + ${'x---'} | ${() => [p()]} + `('with input=$input, then should insert a $insertedNode', ({ input, insertedNodes }) => { + const expectedDoc = doc(...insertedNodes()); + + triggerNodeInputRule({ tiptapEditor, inputRuleText: input }); + + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); }); diff --git a/spec/frontend/content_editor/extensions/inline_diff_spec.js b/spec/frontend/content_editor/extensions/inline_diff_spec.js index 63cdf665e7f..99c559a20b1 100644 --- a/spec/frontend/content_editor/extensions/inline_diff_spec.js +++ b/spec/frontend/content_editor/extensions/inline_diff_spec.js @@ -1,27 +1,43 @@ -import { inputRegexAddition, inputRegexDeletion } from '~/content_editor/extensions/inline_diff'; +import InlineDiff from '~/content_editor/extensions/inline_diff'; +import { createTestEditor, createDocBuilder, triggerMarkInputRule } from '../test_utils'; describe('content_editor/extensions/inline_diff', () => { - describe.each` - inputRegex | description | input | matches - ${inputRegexAddition} | ${'inputRegexAddition'} | ${'hello{+world+}'} | ${true} - ${inputRegexAddition} | ${'inputRegexAddition'} | ${'hello{+ world +}'} | ${true} - ${inputRegexAddition} | ${'inputRegexAddition'} | ${'hello {+ world+}'} | ${true} - ${inputRegexAddition} | ${'inputRegexAddition'} | ${'{+hello world +}'} | ${true} - ${inputRegexAddition} | ${'inputRegexAddition'} | ${'{+hello with \nnewline+}'} | ${false} - ${inputRegexAddition} | ${'inputRegexAddition'} | ${'{+open only'} | ${false} - ${inputRegexAddition} | ${'inputRegexAddition'} | ${'close only+}'} | ${false} - ${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'hello{-world-}'} | ${true} - ${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'hello{- world -}'} | ${true} - ${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'hello {- world-}'} | ${true} - ${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'{-hello world -}'} | ${true} - ${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'{+hello with \nnewline+}'} | ${false} - ${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'{-open only'} | ${false} - ${inputRegexDeletion} | ${'inputRegexDeletion'} | ${'close only-}'} | ${false} - `('$description', ({ inputRegex, input, matches }) => { - it(`${matches ? 'matches' : 'does not match'}: "${input}"`, () => { - const match = new RegExp(inputRegex).test(input); + let tiptapEditor; + let doc; + let p; + let inlineDiff; - expect(match).toBe(matches); - }); + beforeEach(() => { + tiptapEditor = createTestEditor({ extensions: [InlineDiff] }); + ({ + builders: { doc, p, inlineDiff }, + } = createDocBuilder({ + tiptapEditor, + names: { + inlineDiff: { markType: InlineDiff.name }, + }, + })); + }); + + it.each` + input | insertedNode + ${'hello{+world+}'} | ${() => p('hello', inlineDiff('world'))} + ${'hello{+ world +}'} | ${() => p('hello', inlineDiff(' world '))} + ${'{+hello with \nnewline+}'} | ${() => p('{+hello with newline+}')} + ${'{+open only'} | ${() => p('{+open only')} + ${'close only+}'} | ${() => p('close only+}')} + ${'hello{-world-}'} | ${() => p('hello', inlineDiff({ type: 'deletion' }, 'world'))} + ${'hello{- world -}'} | ${() => p('hello', inlineDiff({ type: 'deletion' }, ' world '))} + ${'hello {- world-}'} | ${() => p('hello ', inlineDiff({ type: 'deletion' }, ' world'))} + ${'{-hello world -}'} | ${() => p(inlineDiff({ type: 'deletion' }, 'hello world '))} + ${'{-hello with \nnewline-}'} | ${() => p('{-hello with newline-}')} + ${'{-open only'} | ${() => p('{-open only')} + ${'close only-}'} | ${() => p('close only-}')} + `('with input=$input, then should insert a $insertedNode', ({ input, insertedNode }) => { + const expectedDoc = doc(insertedNode()); + + triggerMarkInputRule({ tiptapEditor, inputRuleText: input }); + + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); }); diff --git a/spec/frontend/content_editor/extensions/link_spec.js b/spec/frontend/content_editor/extensions/link_spec.js index 026b2a06df3..ead898554d1 100644 --- a/spec/frontend/content_editor/extensions/link_spec.js +++ b/spec/frontend/content_editor/extensions/link_spec.js @@ -1,61 +1,46 @@ -import { - markdownLinkSyntaxInputRuleRegExp, - urlSyntaxRegExp, - extractHrefFromMarkdownLink, -} from '~/content_editor/extensions/link'; +import Link from '~/content_editor/extensions/link'; +import { createTestEditor, createDocBuilder, triggerMarkInputRule } from '../test_utils'; describe('content_editor/extensions/link', () => { - describe.each` - input | matches - ${'[gitlab](https://gitlab.com)'} | ${true} - ${'[documentation](readme.md)'} | ${true} - ${'[link 123](readme.md)'} | ${true} - ${'[link 123](read me.md)'} | ${true} - ${'text'} | ${false} - ${'documentation](readme.md'} | ${false} - ${'https://www.google.com'} | ${false} - `('markdownLinkSyntaxInputRuleRegExp', ({ input, matches }) => { - it(`${matches ? 'matches' : 'does not match'} ${input}`, () => { - const match = new RegExp(markdownLinkSyntaxInputRuleRegExp).exec(input); - - expect(Boolean(match?.groups.href)).toBe(matches); - }); + let tiptapEditor; + let doc; + let p; + let link; + + beforeEach(() => { + tiptapEditor = createTestEditor({ extensions: [Link] }); + ({ + builders: { doc, p, link }, + } = createDocBuilder({ + tiptapEditor, + names: { + link: { markType: Link.name }, + }, + })); }); - describe.each` - input | matches - ${'http://example.com '} | ${true} - ${'https://example.com '} | ${true} - ${'www.example.com '} | ${true} - ${'example.com/ab.html '} | ${false} - ${'text'} | ${false} - ${' http://example.com '} | ${true} - ${'https://www.google.com '} | ${true} - `('urlSyntaxRegExp', ({ input, matches }) => { - it(`${matches ? 'matches' : 'does not match'} ${input}`, () => { - const match = new RegExp(urlSyntaxRegExp).exec(input); - - expect(Boolean(match?.groups.href)).toBe(matches); - }); + afterEach(() => { + tiptapEditor.destroy(); }); - describe('extractHrefFromMarkdownLink', () => { - const input = '[gitlab](https://gitlab.com)'; - const href = 'https://gitlab.com'; - let match; - let result; - - beforeEach(() => { - match = new RegExp(markdownLinkSyntaxInputRuleRegExp).exec(input); - result = extractHrefFromMarkdownLink(match); - }); - - it('extracts the url from a markdown link captured by markdownLinkSyntaxInputRuleRegExp', () => { - expect(result).toEqual({ href }); - }); - - it('makes sure that url text is the last capture group', () => { - expect(match[match.length - 1]).toEqual('gitlab'); - }); + it.each` + input | insertedNode + ${'[gitlab](https://gitlab.com)'} | ${() => p(link({ href: 'https://gitlab.com' }, 'gitlab'))} + ${'[documentation](readme.md)'} | ${() => p(link({ href: 'readme.md' }, 'documentation'))} + ${'[link 123](readme.md)'} | ${() => p(link({ href: 'readme.md' }, 'link 123'))} + ${'[link 123](read me.md)'} | ${() => p(link({ href: 'read me.md' }, 'link 123'))} + ${'text'} | ${() => p('text')} + ${'documentation](readme.md'} | ${() => p('documentation](readme.md')} + ${'http://example.com '} | ${() => p(link({ href: 'http://example.com' }, 'http://example.com'))} + ${'https://example.com '} | ${() => p(link({ href: 'https://example.com' }, 'https://example.com'))} + ${'www.example.com '} | ${() => p(link({ href: 'www.example.com' }, 'www.example.com'))} + ${'example.com/ab.html '} | ${() => p('example.com/ab.html')} + ${'https://www.google.com '} | ${() => p(link({ href: 'https://www.google.com' }, 'https://www.google.com'))} + `('with input=$input, then should insert a $insertedNode', ({ input, insertedNode }) => { + const expectedDoc = doc(insertedNode()); + + triggerMarkInputRule({ tiptapEditor, inputRuleText: input }); + + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); }); diff --git a/spec/frontend/content_editor/extensions/math_inline_spec.js b/spec/frontend/content_editor/extensions/math_inline_spec.js index 82eb85477de..abf10317b5a 100644 --- a/spec/frontend/content_editor/extensions/math_inline_spec.js +++ b/spec/frontend/content_editor/extensions/math_inline_spec.js @@ -1,5 +1,5 @@ import MathInline from '~/content_editor/extensions/math_inline'; -import { createTestEditor, createDocBuilder } from '../test_utils'; +import { createTestEditor, createDocBuilder, triggerMarkInputRule } from '../test_utils'; describe('content_editor/extensions/math_inline', () => { let tiptapEditor; @@ -26,16 +26,9 @@ describe('content_editor/extensions/math_inline', () => { ${'$`a^2`'} | ${() => p('$`a^2`')} ${'`a^2`$'} | ${() => p('`a^2`$')} `('with input=$input, then should insert a $insertedNode', ({ input, insertedNode }) => { - const { view } = tiptapEditor; const expectedDoc = doc(insertedNode()); - tiptapEditor.chain().setContent(input).setTextSelection(0).run(); - - const { state } = tiptapEditor; - const { selection } = state; - - // Triggers the event handler that input rules listen to - view.someProp('handleTextInput', (f) => f(view, selection.from, input.length + 1, input)); + triggerMarkInputRule({ tiptapEditor, inputRuleText: input }); expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); diff --git a/spec/frontend/content_editor/extensions/table_of_contents_spec.js b/spec/frontend/content_editor/extensions/table_of_contents_spec.js index 83818899c17..0ddd88b39fe 100644 --- a/spec/frontend/content_editor/extensions/table_of_contents_spec.js +++ b/spec/frontend/content_editor/extensions/table_of_contents_spec.js @@ -1,13 +1,17 @@ import TableOfContents from '~/content_editor/extensions/table_of_contents'; -import { createTestEditor, createDocBuilder } from '../test_utils'; +import { createTestEditor, createDocBuilder, triggerNodeInputRule } from '../test_utils'; -describe('content_editor/extensions/emoji', () => { +describe('content_editor/extensions/table_of_contents', () => { let tiptapEditor; - let builders; + let doc; + let tableOfContents; + let p; beforeEach(() => { tiptapEditor = createTestEditor({ extensions: [TableOfContents] }); - ({ builders } = createDocBuilder({ + ({ + builders: { doc, p, tableOfContents }, + } = createDocBuilder({ tiptapEditor, names: { tableOfContents: { nodeType: TableOfContents.name } }, })); @@ -15,20 +19,16 @@ describe('content_editor/extensions/emoji', () => { it.each` input | insertedNode - ${'[[_TOC_]]'} | ${'tableOfContents'} - ${'[TOC]'} | ${'tableOfContents'} - ${'[toc]'} | ${'p'} - ${'TOC'} | ${'p'} - ${'[_TOC_]'} | ${'p'} - ${'[[TOC]]'} | ${'p'} + ${'[[_TOC_]]'} | ${() => tableOfContents()} + ${'[TOC]'} | ${() => tableOfContents()} + ${'[toc]'} | ${() => p()} + ${'TOC'} | ${() => p()} + ${'[_TOC_]'} | ${() => p()} + ${'[[TOC]]'} | ${() => p()} `('with input=$input, then should insert a $insertedNode', ({ input, insertedNode }) => { - const { doc } = builders; - const { view } = tiptapEditor; - const { selection } = view.state; - const expectedDoc = doc(builders[insertedNode]()); + const expectedDoc = doc(insertedNode()); - // Triggers the event handler that input rules listen to - view.someProp('handleTextInput', (f) => f(view, selection.from, selection.to, input)); + triggerNodeInputRule({ tiptapEditor, inputRuleText: input }); expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); }); diff --git a/spec/frontend/content_editor/extensions/word_break_spec.js b/spec/frontend/content_editor/extensions/word_break_spec.js new file mode 100644 index 00000000000..23167269d7d --- /dev/null +++ b/spec/frontend/content_editor/extensions/word_break_spec.js @@ -0,0 +1,35 @@ +import WordBreak from '~/content_editor/extensions/word_break'; +import { createTestEditor, createDocBuilder, triggerNodeInputRule } from '../test_utils'; + +describe('content_editor/extensions/word_break', () => { + let tiptapEditor; + let doc; + let p; + let wordBreak; + + beforeEach(() => { + tiptapEditor = createTestEditor({ extensions: [WordBreak] }); + + ({ + builders: { doc, p, wordBreak }, + } = createDocBuilder({ + tiptapEditor, + names: { + wordBreak: { nodeType: WordBreak.name }, + }, + })); + }); + + it.each` + input | insertedNode + ${'<wbr>'} | ${() => p(wordBreak())} + ${'<wbr'} | ${() => p()} + ${'wbr>'} | ${() => p()} + `('with input=$input, then should insert a $insertedNode', ({ input, insertedNode }) => { + const expectedDoc = doc(insertedNode()); + + triggerNodeInputRule({ tiptapEditor, inputRuleText: input }); + + expect(tiptapEditor.getJSON()).toEqual(expectedDoc.toJSON()); + }); +}); diff --git a/spec/frontend/content_editor/services/track_input_rules_and_shortcuts_spec.js b/spec/frontend/content_editor/services/track_input_rules_and_shortcuts_spec.js index afe09a75f16..459780cc7cf 100644 --- a/spec/frontend/content_editor/services/track_input_rules_and_shortcuts_spec.js +++ b/spec/frontend/content_editor/services/track_input_rules_and_shortcuts_spec.js @@ -10,7 +10,7 @@ import Heading from '~/content_editor/extensions/heading'; import ListItem from '~/content_editor/extensions/list_item'; import trackInputRulesAndShortcuts from '~/content_editor/services/track_input_rules_and_shortcuts'; import { ENTER_KEY, BACKSPACE_KEY } from '~/lib/utils/keys'; -import { createTestEditor } from '../test_utils'; +import { createTestEditor, triggerNodeInputRule } from '../test_utils'; describe('content_editor/services/track_input_rules_and_shortcuts', () => { let trackingSpy; @@ -70,14 +70,7 @@ describe('content_editor/services/track_input_rules_and_shortcuts', () => { describe('when creating a heading using an input rule', () => { it('sends a tracking event indicating that a heading was created using an input rule', async () => { const nodeName = Heading.name; - const { view } = editor; - const { selection } = view.state; - - // Triggers the event handler that input rules listen to - view.someProp('handleTextInput', (f) => f(view, selection.from, selection.to, '## ')); - - editor.chain().insertContent(HEADING_TEXT).run(); - + triggerNodeInputRule({ tiptapEditor: editor, inputRuleText: '## ' }); expect(trackingSpy).toHaveBeenCalledWith(undefined, INPUT_RULE_TRACKING_ACTION, { label: CONTENT_EDITOR_TRACKING_LABEL, property: `${nodeName}`, diff --git a/spec/frontend/content_editor/test_utils.js b/spec/frontend/content_editor/test_utils.js index cf5aa3f2938..b236c630e13 100644 --- a/spec/frontend/content_editor/test_utils.js +++ b/spec/frontend/content_editor/test_utils.js @@ -119,3 +119,26 @@ export const createTestContentEditorExtension = ({ commands = [] } = {}) => { }, }; }; + +export const triggerNodeInputRule = ({ tiptapEditor, inputRuleText }) => { + const { view } = tiptapEditor; + const { state } = tiptapEditor; + const { selection } = state; + + // Triggers the event handler that input rules listen to + view.someProp('handleTextInput', (f) => f(view, selection.from, selection.to, inputRuleText)); +}; + +export const triggerMarkInputRule = ({ tiptapEditor, inputRuleText }) => { + const { view } = tiptapEditor; + + tiptapEditor.chain().setContent(inputRuleText).setTextSelection(0).run(); + + const { state } = tiptapEditor; + const { selection } = state; + + // Triggers the event handler that input rules listen to + view.someProp('handleTextInput', (f) => + f(view, selection.from, inputRuleText.length + 1, inputRuleText), + ); +}; diff --git a/spec/frontend/cycle_analytics/mock_data.js b/spec/frontend/cycle_analytics/mock_data.js index 1882457960a..077df3d52fe 100644 --- a/spec/frontend/cycle_analytics/mock_data.js +++ b/spec/frontend/cycle_analytics/mock_data.js @@ -1,6 +1,11 @@ -/* eslint-disable import/no-deprecated */ +import valueStreamAnalyticsStages from 'test_fixtures/projects/analytics/value_stream_analytics/stages.json'; +import issueStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/issue.json'; +import planStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/plan.json'; +import reviewStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/review.json'; +import codeStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/code.json'; +import testStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/test.json'; +import stagingStageFixtures from 'test_fixtures/projects/analytics/value_stream_analytics/events/staging.json'; -import { getJSONFixture } from 'helpers/fixtures'; import { TEST_HOST } from 'helpers/test_constants'; import { DEFAULT_VALUE_STREAM, @@ -20,28 +25,16 @@ export const deepCamelCase = (obj) => convertObjectPropsToCamelCase(obj, { deep: export const getStageByTitle = (stages, title) => stages.find((stage) => stage.title && stage.title.toLowerCase().trim() === title) || {}; -const fixtureEndpoints = { - customizableCycleAnalyticsStagesAndEvents: - 'projects/analytics/value_stream_analytics/stages.json', - stageEvents: (stage) => `projects/analytics/value_stream_analytics/events/${stage}.json`, - metricsData: 'projects/analytics/value_stream_analytics/summary.json', -}; - -export const metricsData = getJSONFixture(fixtureEndpoints.metricsData); - -export const customizableStagesAndEvents = getJSONFixture( - fixtureEndpoints.customizableCycleAnalyticsStagesAndEvents, -); - export const defaultStages = ['issue', 'plan', 'review', 'code', 'test', 'staging']; -const stageFixtures = defaultStages.reduce((acc, stage) => { - const events = getJSONFixture(fixtureEndpoints.stageEvents(stage)); - return { - ...acc, - [stage]: events, - }; -}, {}); +const stageFixtures = { + issue: issueStageFixtures, + plan: planStageFixtures, + review: reviewStageFixtures, + code: codeStageFixtures, + test: testStageFixtures, + staging: stagingStageFixtures, +}; export const summary = [ { value: '20', title: 'New Issues' }, @@ -260,7 +253,7 @@ export const selectedProjects = [ }, ]; -export const rawValueStreamStages = customizableStagesAndEvents.stages; +export const rawValueStreamStages = valueStreamAnalyticsStages.stages; export const valueStreamStages = rawValueStreamStages.map((s) => convertObjectPropsToCamelCase(s, { deep: true }), diff --git a/spec/frontend/cycle_analytics/utils_spec.js b/spec/frontend/cycle_analytics/utils_spec.js index 74d64cd8d71..370fe52299c 100644 --- a/spec/frontend/cycle_analytics/utils_spec.js +++ b/spec/frontend/cycle_analytics/utils_spec.js @@ -1,3 +1,4 @@ +import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json'; import { useFakeDate } from 'helpers/fake_date'; import { transformStagesForPathNavigation, @@ -14,7 +15,6 @@ import { stageMedians, pathNavIssueMetric, rawStageMedians, - metricsData, } from './mock_data'; describe('Value stream analytics utils', () => { diff --git a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js index ffdb49a828c..834401de4c6 100644 --- a/spec/frontend/cycle_analytics/value_stream_metrics_spec.js +++ b/spec/frontend/cycle_analytics/value_stream_metrics_spec.js @@ -1,11 +1,12 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import { GlSingleStat } from '@gitlab/ui/dist/charts'; import { shallowMount } from '@vue/test-utils'; +import metricsData from 'test_fixtures/projects/analytics/value_stream_analytics/summary.json'; import waitForPromises from 'helpers/wait_for_promises'; import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api'; import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue'; import createFlash from '~/flash'; -import { group, metricsData } from './mock_data'; +import { group } from './mock_data'; jest.mock('~/flash'); diff --git a/spec/frontend/related_merge_requests/components/related_merge_requests_spec.js b/spec/frontend/related_merge_requests/components/related_merge_requests_spec.js index 67f62815720..486fb699275 100644 --- a/spec/frontend/related_merge_requests/components/related_merge_requests_spec.js +++ b/spec/frontend/related_merge_requests/components/related_merge_requests_spec.js @@ -66,8 +66,8 @@ describe('RelatedMergeRequests', () => { describe('template', () => { it('should render related merge request items', () => { - expect(wrapper.find('.js-items-count').text()).toEqual('2'); - expect(wrapper.findAll(RelatedIssuableItem).length).toEqual(2); + expect(wrapper.find('[data-testid="count"]').text()).toBe('2'); + expect(wrapper.findAll(RelatedIssuableItem)).toHaveLength(2); const props = wrapper.findAll(RelatedIssuableItem).at(1).props(); const data = mockData[1]; diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js index f27f45f2b26..6b9d1288bb3 100644 --- a/spec/frontend/security_configuration/components/app_spec.js +++ b/spec/frontend/security_configuration/components/app_spec.js @@ -1,5 +1,6 @@ import { GlTab } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; +import { nextTick } from 'vue'; import { useLocalStorageSpy } from 'helpers/local_storage_helper'; import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; import stubChildren from 'helpers/stub_children'; @@ -70,6 +71,7 @@ describe('App component', () => { const findTabs = () => wrapper.findAllComponents(GlTab); const findByTestId = (id) => wrapper.findByTestId(id); const findFeatureCards = () => wrapper.findAllComponents(FeatureCard); + const findManageViaMRErrorAlert = () => wrapper.findByTestId('manage-via-mr-error-alert'); const findLink = ({ href, text, container = wrapper }) => { const selector = `a[href="${href}"]`; const link = container.find(selector); @@ -173,6 +175,43 @@ describe('App component', () => { }); }); + describe('Manage via MR Error Alert', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock, + }); + }); + + describe('on initial load', () => { + it('should not show Manage via MR Error Alert', () => { + expect(findManageViaMRErrorAlert().exists()).toBe(false); + }); + }); + + describe('when error occurs', () => { + it('should show Alert with error Message', async () => { + expect(findManageViaMRErrorAlert().exists()).toBe(false); + findFeatureCards().at(1).vm.$emit('error', 'There was a manage via MR error'); + + await nextTick(); + expect(findManageViaMRErrorAlert().exists()).toBe(true); + expect(findManageViaMRErrorAlert().text()).toEqual('There was a manage via MR error'); + }); + + it('should hide Alert when it is dismissed', async () => { + findFeatureCards().at(1).vm.$emit('error', 'There was a manage via MR error'); + + await nextTick(); + expect(findManageViaMRErrorAlert().exists()).toBe(true); + + findManageViaMRErrorAlert().vm.$emit('dismiss'); + await nextTick(); + expect(findManageViaMRErrorAlert().exists()).toBe(false); + }); + }); + }); + describe('Auto DevOps hint alert', () => { describe('given the right props', () => { beforeEach(() => { diff --git a/spec/frontend/security_configuration/components/feature_card_spec.js b/spec/frontend/security_configuration/components/feature_card_spec.js index fdb1d2f86e3..0eca2c27075 100644 --- a/spec/frontend/security_configuration/components/feature_card_spec.js +++ b/spec/frontend/security_configuration/components/feature_card_spec.js @@ -80,7 +80,11 @@ describe('FeatureCard component', () => { describe('basic structure', () => { beforeEach(() => { - feature = makeFeature(); + feature = makeFeature({ + type: 'sast', + available: true, + canEnableByMergeRequest: true, + }); createComponent({ feature }); }); @@ -97,6 +101,11 @@ describe('FeatureCard component', () => { expect(links.exists()).toBe(true); expect(links).toHaveLength(1); }); + + it('should catch and emit manage-via-mr-error', () => { + findManageViaMr().vm.$emit('error', 'There was a manage via MR error'); + expect(wrapper.emitted('error')).toEqual([['There was a manage via MR error']]); + }); }); describe('status', () => { diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 0ce95fdb5af..1ef548ab29b 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -147,6 +147,22 @@ RSpec.describe Gitlab::Middleware::Go do end end end + + context 'when a personal access token is missing' do + before do + env['REMOTE_ADDR'] = '192.168.0.1' + env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(current_user.username, 'dummy_password') + end + + it 'returns unauthorized' do + expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::MissingPersonalAccessTokenError) + response = go + + expect(response[0]).to eq(401) + expect(response[1]['Content-Length']).to be_nil + expect(response[2]).to eq(['']) + end + end end end end diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb index 3ec52f8c832..722d484609c 100644 --- a/spec/support/helpers/cycle_analytics_helpers.rb +++ b/spec/support/helpers/cycle_analytics_helpers.rb @@ -63,6 +63,10 @@ module CycleAnalyticsHelpers wait_for_requests end + def click_save_value_stream_button + click_button(_('Save value stream')) + end + def create_custom_value_stream(custom_value_stream_name) toggle_value_stream_dropdown page.find_button(_('Create new Value Stream')).click diff --git a/yarn.lock b/yarn.lock index dd18237eaa6..add2127031e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1184,10 +1184,10 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.12.tgz#431ec342a7195622f86688bbda82e3166ce8cb28" integrity sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ== -"@popperjs/core@^2.8.3": - version "2.9.2" - resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" - integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== +"@popperjs/core@^2.9.0": + version "2.10.2" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590" + integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ== "@rails/actioncable@6.1.4-1": version "6.1.4-1" @@ -1310,79 +1310,73 @@ dom-accessibility-api "^0.5.1" pretty-format "^26.4.2" -"@tiptap/core@^2.0.0-beta.118": - version "2.0.0-beta.118" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.118.tgz#17e990da525a81d957494171c0c33d17e2039823" - integrity sha512-TApketXliv2PZSTf5WP8j/svwzeK795fOf4Ff6A0gDwcVYFPHlny4ZlpawYthyqDoe1fEusyZokVgiDHnb+EzA== +"@tiptap/core@^2.0.0-beta.125": + version "2.0.0-beta.125" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.125.tgz#672a86f21727614bfe8d5bf41544ce83ea4a7c87" + integrity sha512-I3lju5hcjK6nhC29jJCdGoRdP0C5nPBHGN8y/fbFmCX0oQ/THuzVdMgaGPpkHjralPPMS3DqHD/Rx8Kbo8zClg== dependencies: "@types/prosemirror-commands" "^1.0.4" - "@types/prosemirror-inputrules" "^1.0.4" "@types/prosemirror-keymap" "^1.0.4" "@types/prosemirror-model" "^1.13.2" "@types/prosemirror-schema-list" "^1.0.3" "@types/prosemirror-state" "^1.2.7" "@types/prosemirror-transform" "^1.1.4" "@types/prosemirror-view" "^1.19.1" - prosemirror-commands "^1.1.10" - prosemirror-inputrules "^1.1.3" + prosemirror-commands "^1.1.11" prosemirror-keymap "^1.1.3" prosemirror-model "^1.14.3" prosemirror-schema-list "^1.1.6" prosemirror-state "^1.3.4" prosemirror-transform "^1.3.3" - prosemirror-view "^1.20.1" + prosemirror-view "^1.20.3" -"@tiptap/extension-blockquote@^2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.15.tgz#40be203f7db47e027ea1a5ba42bbb0e33bb6c004" - integrity sha512-Cso44KsYsqKqaNveQmx5KVaLy9krq5AzE9WhGVDBSFqWhvuIJkQYrTRBbOTfUDs/st9VuwJrbjTDD65ow50wEw== - dependencies: - prosemirror-inputrules "^1.1.3" +"@tiptap/extension-blockquote@^2.0.0-beta.19": + version "2.0.0-beta.19" + resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.19.tgz#4b1dfd0ec511f889ddb0f34cd09e57a0db877dc0" + integrity sha512-9y8keXSm4E5mdh/EocdbrJ/H71qyXv2jajRHIsXj1SaJqLaz4JbSQGrX3j2r0ia3KW3YNFFIHl/z33fU70YfGQ== -"@tiptap/extension-bold@^2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.15.tgz#cf9ddb3fc316be9707753ad4e497bfb8a3ebb0c2" - integrity sha512-jKyV6iiwhxwa0+7uuKD74jNDVNLNOS1GmU14MgaA95pY5e1fyaRBPPX8Gtt89niz2CLOY711AV17RPZTe/e60w== +"@tiptap/extension-bold@^2.0.0-beta.19": + version "2.0.0-beta.19" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.19.tgz#9426b5fcb2bfb79082f9efefbafff955d26d023e" + integrity sha512-pbYMK3Lz78XFi+1OisdjuGQcjRnzHXeYivh9A4xP1fmSOS6t/lQsu2P2uDhFNXtz45dLcLTOkqnE0j5lj3TGMg== -"@tiptap/extension-bubble-menu@^2.0.0-beta.39": - version "2.0.0-beta.39" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.39.tgz#8971feeac93e685fc38564173a83ad078b4e7f2a" - integrity sha512-hmA+ePR+MnRaTJ5MxoZ3yqOcK54cW2KQllZx16ZwSyM+yU9bXVhfMmyZwqRD7GGQFkrfnPm5QnedXDBYJD19OQ== +"@tiptap/extension-bubble-menu@^2.0.0-beta.42": + version "2.0.0-beta.42" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.42.tgz#40c2783860721e861b3eb11aa2a076d1944058c2" + integrity sha512-H+pGpSk0mK4BRm4RbevgT+ir1bEwlS1dEwi2YIPYVl+JSpuGS2P1jTWeZpCou9cewNuxxwrAarhp4aEvJrL8UQ== dependencies: prosemirror-state "^1.3.4" - prosemirror-view "^1.20.1" - tippy.js "^6.3.1" + prosemirror-view "^1.20.3" + tippy.js "^6.3.2" -"@tiptap/extension-bullet-list@^2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.15.tgz#74876851a8d227ba1a031d031631ed621c175e05" - integrity sha512-5i44JzsZOh8Ci6CuYRQy6W3jCpYgX0+VuJKeHvZ6Aomy4Qqrtc9Jk43PBmCj91lNUUtH6Io9l+kDrLCumEFnEg== - dependencies: - prosemirror-inputrules "^1.1.3" +"@tiptap/extension-bullet-list@^2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.0-beta.18.tgz#ab33426a3ea03cea11863e44aad9e48fc18d478c" + integrity sha512-dOf2Wx9bmgpBQIxhw7b+g1GhbIyIox7FIiIEkkSgqDtx8wPPYlnGwHRxopj4a57VbqRkRtspJZp52/vhP3is5w== -"@tiptap/extension-code-block-lowlight@2.0.0-beta.41": - version "2.0.0-beta.41" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.41.tgz#3ec98f509bbd5df689de6282f2a3881229262ed7" - integrity sha512-2+D/SwcRjWThJ8uFWJT/6B7R+gTUlp4h13/EZqrFMm3YCSOx+bzgTVndog6UJkWyAoNDxkTVwcOyYI4HWdvCiQ== +"@tiptap/extension-code-block-lowlight@2.0.0-beta.47": + version "2.0.0-beta.47" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.47.tgz#2a3253f778375e4a420dd77967931b4da7926913" + integrity sha512-+k0uACctl4PIrZQrZkiPapYL/Uq8Skc6gBhlvtJ3+U9+U798Rm7QZpGxEe9iXW4KC69E4LtD1JNj27Ofns35Cg== dependencies: - "@tiptap/extension-code-block" "^2.0.0-beta.18" + "@tiptap/extension-code-block" "^2.0.0-beta.24" "@types/lowlight" "^0.0.3" lowlight "^1.20.0" prosemirror-model "^1.14.3" prosemirror-state "^1.3.4" - prosemirror-view "^1.20.1" + prosemirror-view "^1.20.3" -"@tiptap/extension-code-block@^2.0.0-beta.18": - version "2.0.0-beta.18" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.18.tgz#3b43730cfca1ba26171530951b3cc324a500cb11" - integrity sha512-E2gz7ovl9nXLZzheqLyN3hi7A10fCaodDn4DvIl4wiEbKZpF7WFBNeb+FQetWNay9UWNeDO94SCX9+rT9H+yHA== +"@tiptap/extension-code-block@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block/-/extension-code-block-2.0.0-beta.24.tgz#f3d45d4ae0cdc2bf94ed5e61a5421c72b2bd3a53" + integrity sha512-Q6KxBe3FB+dMe/prlfeixXSVqGTmnOmIL2/10B1RzSj7Mj9SgzqQEHZFm3dKVqpYuMOYJ6S6edkW33E0Wq9ahQ== dependencies: - prosemirror-inputrules "^1.1.3" + prosemirror-state "^1.3.4" -"@tiptap/extension-code@^2.0.0-beta.16": - version "2.0.0-beta.16" - resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.16.tgz#b258ff90ebe703a4d36ff0c650e6b2cab634028d" - integrity sha512-Kakg/RMiVrxjzIkLVDXtbCzRh/9W8dgSG04IhMZNOI8N9vWn8Z78jdUyxEEDTcL/JyWWcMxn9AsJw2U5ajO3pA== +"@tiptap/extension-code@^2.0.0-beta.20": + version "2.0.0-beta.20" + resolved "https://registry.yarnpkg.com/@tiptap/extension-code/-/extension-code-2.0.0-beta.20.tgz#0f5ff7e827ae09c26d23ddc9e62f6375d2f1c0f8" + integrity sha512-25twg/rsg5CxTOfDgYzg1GbwrtdzNX2vCQyYsauXfPI1kbrWXdVBYWeL4iHdJk5WElfH9WUbQ4kMGh13/KCG/g== "@tiptap/extension-document@^2.0.0-beta.13": version "2.0.0-beta.13" @@ -1397,34 +1391,32 @@ "@types/prosemirror-dropcursor" "^1.0.3" prosemirror-dropcursor "^1.3.5" -"@tiptap/extension-floating-menu@^2.0.0-beta.33": - version "2.0.0-beta.33" - resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.33.tgz#11068488f10fce697df2a48f79039e9c1d10eb7b" - integrity sha512-8s8DPnHIzXg7E7S/DjuS1AAFZKVYXY0KBKaEd1f2V45YOkKwN9El46Ugk/4Ir3yrrllvnisbP9ol+BAQmI0bMg== +"@tiptap/extension-floating-menu@^2.0.0-beta.36": + version "2.0.0-beta.36" + resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.36.tgz#313082e2dd22b6b20c81aa4e98f7c9bcfd47ebe7" + integrity sha512-Pm9KK+Y7YUgMrlvqa/MgxV0WOTBiOp8d2kpt7OwGv/ahjc4amv0HFbei7glsiJ56VtOv8lsjiuBS+m2ctWHWVQ== dependencies: prosemirror-state "^1.3.4" - prosemirror-view "^1.20.1" - tippy.js "^6.3.1" + prosemirror-view "^1.20.3" + tippy.js "^6.3.2" -"@tiptap/extension-gapcursor@^2.0.0-beta.24": - version "2.0.0-beta.24" - resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.24.tgz#dc42a3610cea611755c6521e14a6995555e0ad49" - integrity sha512-/6Ru0wNLIb3fo30Ar3z/rcakoUA2EIJL9sBFiuyHWTAIujeEaBzA6oG5L4PpP+daKd31JF0I6LjeWMSU9CBSFw== +"@tiptap/extension-gapcursor@^2.0.0-beta.27": + version "2.0.0-beta.27" + resolved "https://registry.yarnpkg.com/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.0-beta.27.tgz#ced6cb7d39a388e60f8ec22889a015beb81b417a" + integrity sha512-Sk041ygN+PhAGiCnDfEtFl8sFmm+clKTHJZJwb6ixcdfgY9xkTpkrgswsAY5fvLnLxZUJvZSDOrsvysbh6M2MQ== dependencies: "@types/prosemirror-gapcursor" "^1.0.4" prosemirror-gapcursor "^1.2.0" -"@tiptap/extension-hard-break@^2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.21.tgz#3b9108c7703f23ae186c1038033f0b1354f721bf" - integrity sha512-Ukl+wjfLhE0tW7lWRpSPPo2tajjGnEaSc/Irey1JineFf+x/azA9rREzQy0r2AhORTalH7lj/KDmSdG8IT6syA== +"@tiptap/extension-hard-break@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-hard-break/-/extension-hard-break-2.0.0-beta.24.tgz#8518cf253c24a316824fdac41ffe195fe934bfb2" + integrity sha512-0oEHUlQKQZRQmrbKARFPBVVRBWdekR27ro+qg+T6nzEHYSRkJ7dWBswGSNul1v1XEp52JqniZ3el0w1xdYMR0w== -"@tiptap/extension-heading@^2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.15.tgz#d62f32a2ec8ce5a6d4e716aa7a45dfb707283848" - integrity sha512-UoXDwEdCV9KiPh0wj0jj2Jt6VDqkoTaSU3d9bmEBLwg1Gjgbuv39JDst7oxSqbf9rgbl3txbeOy35wVBKe9CqA== - dependencies: - prosemirror-inputrules "^1.1.3" +"@tiptap/extension-heading@^2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@tiptap/extension-heading/-/extension-heading-2.0.0-beta.18.tgz#ffaee32ab0286ac047a3f38a52f870538155e30c" + integrity sha512-JJtB1pNHkqC9z/z/6B+xQpDd1w5EaLp++yG8eoY9NCq3ZCRhwULda+Uq7reA9D0PdEDpASsTSS2qLu8ZAtgUeA== "@tiptap/extension-history@^2.0.0-beta.16": version "2.0.0-beta.16" @@ -1434,28 +1426,29 @@ "@types/prosemirror-history" "^1.0.3" prosemirror-history "^1.2.0" -"@tiptap/extension-horizontal-rule@^2.0.0-beta.21": - version "2.0.0-beta.21" - resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.21.tgz#1c73a8547611f53935117ed0079542d958ba37fa" - integrity sha512-fgvRGuNEGWAitbcoz6VZSR9gcVIHksTy2QpXPnQC+N9Mi7havaxreYdMZn+oePW/5kdZoZNRx+jsf5DjKomvoQ== +"@tiptap/extension-horizontal-rule@^2.0.0-beta.24": + version "2.0.0-beta.24" + resolved "https://registry.yarnpkg.com/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.0-beta.24.tgz#ffd6226a98a62c5314012b8c1cc60c90709d0b9c" + integrity sha512-kRHJySSJl6QgPvnD+MkN3rzwQgInbq5zE4oxPPbgqYkaAcVSL/q7JBQK1dXMMFaslQlYmEgM6Eh3oU5vo9gUdQ== dependencies: prosemirror-state "^1.3.4" -"@tiptap/extension-image@^2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.15.tgz#07bdc23c9804c830a394f78242648dd7783a469e" - integrity sha512-iMwQ154RPefOxgqTTHjOA735YWesyjBhYvD9fFxRDctM2vLxEYYYFOENXEBCFLNAr94gfaxefCEdkuNYHl9H7g== +"@tiptap/extension-image@^2.0.0-beta.19": + version "2.0.0-beta.19" + resolved "https://registry.yarnpkg.com/@tiptap/extension-image/-/extension-image-2.0.0-beta.19.tgz#40bd95d6d4306d28640bf60a074f20c792f375c6" + integrity sha512-17ax4H6Y+xyePfLtL5Z2V2AuWKHziukixHigA+Go4yOdEHlSDvl+x8eNYAZkxy2nH1yFW+uu7Onv8Ln/jWzqLg== -"@tiptap/extension-italic@^2.0.0-beta.15": - version "2.0.0-beta.15" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.15.tgz#9a81f686cf221110478935596f0b47a76d4c2f45" - integrity sha512-ZCz1vCysLdvOUrwODuyBP0BDaemCLh6ib7qTYoSDKdive9kfn0Vc5Fg3o8xgHrtrUfwKIJz/sWOknjDEGIc9cw== +"@tiptap/extension-italic@^2.0.0-beta.19": + version "2.0.0-beta.19" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.19.tgz#8f140e778e8cbd2281df6463d895d3632280c26f" + integrity sha512-gEVDqEz25glLuOPW1IOPJy/AIrTgsm164XSi9lnwS1uZa1bmEAKpoALN0+9VzSVaBOmrg2tycMo+iuOhYxFb7w== -"@tiptap/extension-link@^2.0.0-beta.20": - version "2.0.0-beta.20" - resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.20.tgz#dbb2aa4f01212bbd0c3fb14b563e28f31140eae2" - integrity sha512-VljjF5Pmd8xeUxN+Wbypyh8zGEMBWJTwzHUVMbkAEU8IZTdk2gDDcAOiYPyU9+j0vAVbSXi1EAsBRIIlj0OaKQ== +"@tiptap/extension-link@^2.0.0-beta.23": + version "2.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@tiptap/extension-link/-/extension-link-2.0.0-beta.23.tgz#7591dfe6eb8f65548c2b6a562c2b6ec0568d23c2" + integrity sha512-XnNdu6OyB09M4Qsru5j/GsDwj/EFjLQNmGZSQIS3GoaEcxrOImohnEZBZO9WJ11A5IT2GilpRZn2wHscdKoBdA== dependencies: + linkifyjs "^3.0.3" prosemirror-state "^1.3.4" "@tiptap/extension-list-item@^2.0.0-beta.14": @@ -1463,22 +1456,20 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-list-item/-/extension-list-item-2.0.0-beta.14.tgz#65a9ff9daa11bc9ca8bc2989a891abe68081cfbd" integrity sha512-t6xwEqP+d5443Ul2Jvqz9kXb3ro7bA7yY9HA0vskm3120WxxHW9jxgxZN+82Ot5Tm7nXOAlsN6vuqnt4idnxZQ== -"@tiptap/extension-ordered-list@^2.0.0-beta.16": - version "2.0.0-beta.16" - resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.16.tgz#3ef25a8dd8ddbd2b1aa5ce89d5a2e5a3ecafcf4e" - integrity sha512-3n0h5FBfQqBrN/zqF/Ngoyd1bZxeIRLwWI7ak4KulpvOg5V/yw3sw5CSxr2f13ZI9AgGaTq8yOsTYs9dkCCnsQ== - dependencies: - prosemirror-inputrules "^1.1.3" +"@tiptap/extension-ordered-list@^2.0.0-beta.19": + version "2.0.0-beta.19" + resolved "https://registry.yarnpkg.com/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.0-beta.19.tgz#eeab2e8488b84ba7deb93b299370dc187d37c98a" + integrity sha512-PHC5pA1gohxCJF2xMXOzruPt8XWyWLun3vhJL2AIUUzUoGJmSRhsc8Wreeozdlf8HkKrqnsIuk5tp6IEsau6Pw== "@tiptap/extension-paragraph@^2.0.0-beta.17": version "2.0.0-beta.17" resolved "https://registry.yarnpkg.com/@tiptap/extension-paragraph/-/extension-paragraph-2.0.0-beta.17.tgz#f8f0263359b95dec9c10078699697908568d9be9" integrity sha512-qCQVCf9c2hgaeIdfy22PaoZyW5Vare/1aGkOEAaZma5RjrUbV9hrRKwoW9LsDjnh1EN1fIeKdg02yEhnHWtG8A== -"@tiptap/extension-strike@^2.0.0-beta.17": - version "2.0.0-beta.17" - resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.17.tgz#2280ea4e8c50189c2729814d2ae484e58c712a36" - integrity sha512-+WRd0RuCK4+jFKNVN+4rHTa5VMqqGDO2uc+TknkqhFqWp/z96OAGlpHJOwPrnW1fLbpjEBBQIr1vVYSw6KgcZg== +"@tiptap/extension-strike@^2.0.0-beta.21": + version "2.0.0-beta.21" + resolved "https://registry.yarnpkg.com/@tiptap/extension-strike/-/extension-strike-2.0.0-beta.21.tgz#d4b7f0d52a275bc6a4ddc60fe546495d9f77f041" + integrity sha512-uYU5k05MChVtZUwWMXAl+xp3IGx/N/+8VZaeJDlIZfg0hew+ZdEGVjwzgCQc1PAuEZriHhbpPg1yOdcHjWga8Q== "@tiptap/extension-subscript@^2.0.0-beta.4": version "2.0.0-beta.4" @@ -1505,39 +1496,37 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.14.tgz#9ec98c73e309ee966b71ccd140019874d179e0c8" integrity sha512-mewdlTqgBCyzeZIZ6F08gfuzwsiYjQ7BvABo2UhDfr0+EN2UvfJj0bT3tGgeZhMxT5Js2DXL+c+ZOVJxWJ9faQ== -"@tiptap/extension-table@^2.0.0-beta.31": - version "2.0.0-beta.31" - resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.31.tgz#96987fe14017be2fd3e4dbd2ce349eec641724de" - integrity sha512-yMqnbxaq2DjaZ6EOE9FLSQSO+qHH7oE0rA+ahQkJdy9KycSboKthXBY7P9JeXxariTyD2B/My9x41cuDLWes9w== +"@tiptap/extension-table@^2.0.0-beta.34": + version "2.0.0-beta.34" + resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.34.tgz#6669d7d937b981e39f7dfa04b9adbabc18f789bc" + integrity sha512-DyY+m0PcvdVwh0XynDbsHXu/omUoTzQRQPI4z035J+b2HnLQnYjviNqblY/x8/rGX4qGGjBUmKvQZgwPGg7Dfw== dependencies: prosemirror-tables "^1.1.1" - prosemirror-view "^1.20.1" + prosemirror-view "^1.20.3" -"@tiptap/extension-task-item@^2.0.0-beta.18": - version "2.0.0-beta.18" - resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.18.tgz#f109c15c997038d8099b64dba4cfc4e219b426e4" - integrity sha512-SmXWdfpDFIBGxWH4Xhb9d6jRSK7jJqsw0GqC5KAtIQ9gNvAQS1j5FqAtWfxlb8FkunIfV6MNnxDP2ZgbKvaEug== - dependencies: - prosemirror-inputrules "^1.1.3" +"@tiptap/extension-task-item@^2.0.0-beta.21": + version "2.0.0-beta.21" + resolved "https://registry.yarnpkg.com/@tiptap/extension-task-item/-/extension-task-item-2.0.0-beta.21.tgz#37a8d30949cefd79b80f107d426d14eb9aeaf567" + integrity sha512-7pCOc+jSlveTGIUUVQO5LOGZN20vb1fAIFMUKAxG756MMI69eVhpdqrXcmovYT8qs//DcvxC/sEjus+ZzFtKVg== -"@tiptap/extension-task-list@^2.0.0-beta.17": - version "2.0.0-beta.17" - resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.17.tgz#c0f40325abf1b6a23868e72ab32f9724a8b42a7b" - integrity sha512-E17VBqW2lXF59hCQ/3/Kg0hYSDGvVC4B3W8miZwCXt5WTOl98Gk6qAiDXl+2mDKZvnsLty/YrgkD88OlZSIEbQ== +"@tiptap/extension-task-list@^2.0.0-beta.18": + version "2.0.0-beta.18" + resolved "https://registry.yarnpkg.com/@tiptap/extension-task-list/-/extension-task-list-2.0.0-beta.18.tgz#8b13593e3818995e7b5058bc400ee04c68314d69" + integrity sha512-EkPM+We5TP6MWwk+dH3FX/aizNRgVzHDTnnGyMisEievaazi11oKmz72svVCfF+BJJaRdVroCbeRoLWYH7by7w== "@tiptap/extension-text@^2.0.0-beta.13": version "2.0.0-beta.13" resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.13.tgz#da0af8d9a3f149d20076e15d88c6af21fb6d940f" integrity sha512-0EtAwuRldCAoFaL/iXgkRepEeOd55rPg5N4FQUN1xTwZT7PDofukP0DG/2jff/Uj17x4uTaJAa9qlFWuNnDvjw== -"@tiptap/vue-2@^2.0.0-beta.57": - version "2.0.0-beta.57" - resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.57.tgz#242f7aa47d3c99fdef0e66a05a193b6fef1a95a6" - integrity sha512-f8COWq84wOJeLwAmaYsHCqKVbGgMloW+1r4Rz/KhlFb1MNXYeDHibCiW/VtJe7bdae+iRyIwnfmnAp2u5s77hQ== +"@tiptap/vue-2@^2.0.0-beta.60": + version "2.0.0-beta.60" + resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.60.tgz#18b1c3e7f1a94e5a047c6e25f4e176a42b68e2d1" + integrity sha512-6V2BucVL440yPy4YqCVxnaq7ZkiR9RmOtLydcNIt9/Abc/iQTkp2Q8qyDUqbGTfGfMEEdpuHd4m9ZRk3cuZ14g== dependencies: - "@tiptap/extension-bubble-menu" "^2.0.0-beta.39" - "@tiptap/extension-floating-menu" "^2.0.0-beta.33" - prosemirror-view "^1.20.1" + "@tiptap/extension-bubble-menu" "^2.0.0-beta.42" + "@tiptap/extension-floating-menu" "^2.0.0-beta.36" + prosemirror-view "^1.20.3" "@toast-ui/editor@^2.5.2": version "2.5.2" @@ -1738,14 +1727,6 @@ "@types/prosemirror-model" "*" "@types/prosemirror-state" "*" -"@types/prosemirror-inputrules@^1.0.4": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/prosemirror-inputrules/-/prosemirror-inputrules-1.0.4.tgz#4cb75054d954aa0f6f42099be05eb6c0e6958bae" - integrity sha512-lJIMpOjO47SYozQybUkpV6QmfuQt7GZKHtVrvS+mR5UekA8NMC5HRIVMyaIauJLWhKU6oaNjpVaXdw41kh165g== - dependencies: - "@types/prosemirror-model" "*" - "@types/prosemirror-state" "*" - "@types/prosemirror-keymap@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@types/prosemirror-keymap/-/prosemirror-keymap-1.0.4.tgz#f73c79810e8d0e0a20d153d84f998f02e5afbc0c" @@ -7604,6 +7585,11 @@ linkify-it@^3.0.1: dependencies: uc.micro "^1.0.1" +linkifyjs@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-3.0.3.tgz#51ea2160b4c60c2c87c27757a1e9eacd422c6076" + integrity sha512-ba5opS5wRHSbDC8VaiDdN14nPGm6LqyRsIPQZGG4qXV4scFdrPneT/uoZOaq9QAPBf6W9I9D/6tNSzWH//815Q== + load-json-file@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" @@ -9435,10 +9421,10 @@ prosemirror-collab@^1.2.2: dependencies: prosemirror-state "^1.0.0" -prosemirror-commands@^1.1.10, prosemirror-commands@^1.1.4: - version "1.1.10" - resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.10.tgz#406a6589966e6cd80809cea2d801fb998639b37d" - integrity sha512-IWyBBXNAd44RM6NnBPljwq+/CM2oYCQJkF+YhKEAZNwzW0uFdGf4qComhjbKZzqFdu6Iub2ZhNsXgwPibA0lCQ== +prosemirror-commands@^1.1.11, prosemirror-commands@^1.1.4: + version "1.1.11" + resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.1.11.tgz#369252fcca5397ee7b011b963cc4da45b0b7cb70" + integrity sha512-uXDVkOGJbFHocdacMJihrnQCT7tHswO48ewq6ByqLxTwOrI8Y4B4aHvwUbM4epwElv/YjgC+DuqXm/gEHPym4w== dependencies: prosemirror-model "^1.0.0" prosemirror-state "^1.0.0" @@ -9503,14 +9489,14 @@ prosemirror-model@^1.0.0, prosemirror-model@^1.13.1, prosemirror-model@^1.14.3, dependencies: orderedmap "^1.1.0" -prosemirror-schema-basic@^1.1.2: +prosemirror-schema-basic@^1.0.0, prosemirror-schema-basic@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prosemirror-schema-basic/-/prosemirror-schema-basic-1.1.2.tgz#4bde5c339c845e0d08ec8fe473064e372ca51ae3" integrity sha512-G4q8WflNsR1Q33QAV4MQO0xWrHLOJ+BQcKswGXMy626wlQj6c/1n1v4eC9ns+h2y1r/fJHZEgSZnsNhm9lbrDw== dependencies: prosemirror-model "^1.2.0" -prosemirror-schema-list@^1.1.4, prosemirror-schema-list@^1.1.6: +prosemirror-schema-list@^1.0.0, prosemirror-schema-list@^1.1.4, prosemirror-schema-list@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/prosemirror-schema-list/-/prosemirror-schema-list-1.1.6.tgz#c3e13fe2f74750e4a53ff88d798dc0c4ccca6707" integrity sha512-aFGEdaCWmJzouZ8DwedmvSsL50JpRkqhQ6tcpThwJONVVmCgI36LJHtoQ4VGZbusMavaBhXXr33zyD2IVsTlkw== @@ -9537,10 +9523,14 @@ prosemirror-tables@^1.1.1: prosemirror-transform "^1.2.1" prosemirror-view "^1.13.3" -prosemirror-test-builder@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prosemirror-test-builder/-/prosemirror-test-builder-1.0.4.tgz#68d1d1cedcd90cc2fdd976d736ce87b7a5f1e873" - integrity sha512-d5ZwYcf+vd1YI0tgoB7UZ2B2iwb4jmxLGzkHz6ZbkAmPwAY48WpS1frv9BofC2m/MRIMp5SERhISNIANYPsVTg== +prosemirror-test-builder@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/prosemirror-test-builder/-/prosemirror-test-builder-1.0.5.tgz#d05e8f77484aef192d705c46fe648d0319d69000" + integrity sha512-mymXkqJlhcB4JtQpM5buHvfn7SawJ2vRwYzuIUMkky00ILBOPUnwUCAbjA7L8o4hsaQeyHljRCNdpjXKK8KsOw== + dependencies: + prosemirror-model "^1.0.0" + prosemirror-schema-basic "^1.0.0" + prosemirror-schema-list "^1.0.0" prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.2.1, prosemirror-transform@^1.2.8, prosemirror-transform@^1.3.3: version "1.3.3" @@ -9549,10 +9539,10 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor dependencies: prosemirror-model "^1.0.0" -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.20.1, prosemirror-view@^1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.20.2.tgz#fc073def4358fdbd617ea11f5cd4217c5123635d" - integrity sha512-zh67dsGCI7QKWDbtLEAdZLmadxBJYRArM8E0z2wfuNGpx4i6ObVGzHjbnblZs2n88IwqHdEA47rEhyUqZ+kAbg== +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.20.3.tgz#9781fe59cf0728e749ff4116f8a69d30d8cea943" + integrity sha512-2ImL9K/tIEk+aC2GT8shzfmT2U0Y8UQZ13L5AY0A4Tcj09o/ICGE362gKUE3Ze/Xr/nMw61Zv5JMSQUszAj9dw== dependencies: prosemirror-model "^1.14.3" prosemirror-state "^1.0.0" @@ -11180,12 +11170,12 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow== -tippy.js@^6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.1.tgz#3788a007be7015eee0fd589a66b98fb3f8f10181" - integrity sha512-JnFncCq+rF1dTURupoJ4yPie5Cof978inW6/4S6kmWV7LL9YOSEVMifED3KdrVPEG+Z/TFH2CDNJcQEfaeuQww== +tippy.js@^6.3.2: + version "6.3.2" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.2.tgz#c03a0b88f170dffeba42f569771801dddc1f6340" + integrity sha512-35XVQI7Zl/jHZ51+8eHu/vVRXBjWYGobPm5G9FxOchj4r5dWhghKGS0nm0ARUKZTF96V7pPn7EbXS191NTwldw== dependencies: - "@popperjs/core" "^2.8.3" + "@popperjs/core" "^2.9.0" tiptap-commands@^1.17.1: version "1.17.1" |