summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-04 12:09:46 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-04 12:09:46 +0000
commit72797f4a602d0061636df39df89e11896de2a524 (patch)
treefbd4397be74910e44aaafda4093ea1e6f499d445
parent5b4eca2afd809fbfba3bdcacabe547025ebe7f43 (diff)
downloadgitlab-ce-72797f4a602d0061636df39df89e11896de2a524.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--CHANGELOG.md16
-rw-r--r--app/assets/javascripts/actioncable_consumer.js3
-rw-r--r--app/assets/javascripts/ide/lib/themes/index.js10
-rw-r--r--app/assets/javascripts/ide/lib/themes/monokai.js169
-rw-r--r--app/assets/javascripts/ide/lib/themes/solarized_dark.js1110
-rw-r--r--app/assets/javascripts/issuable_sidebar/queries/issue_sidebar.query.graphql15
-rw-r--r--app/assets/javascripts/lib/utils/keycodes.js3
-rw-r--r--app/assets/javascripts/lib/utils/keys.js4
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue13
-rw-r--r--app/assets/javascripts/notes/components/discussion_counter.vue19
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue71
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue24
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js18
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js4
-rw-r--r--app/assets/stylesheets/page_bundles/themes/_dark.scss92
-rw-r--r--app/assets/stylesheets/pages/notes.scss3
-rw-r--r--app/controllers/projects/issues_controller.rb4
-rw-r--r--app/graphql/types/user_type.rb2
-rw-r--r--app/policies/design_management/design_at_version_policy.rb8
-rw-r--r--app/policies/design_management/design_collection_policy.rb7
-rw-r--r--app/policies/design_management/design_policy.rb8
-rw-r--r--app/policies/design_management/version_policy.rb8
-rw-r--r--app/policies/issue_policy.rb16
-rw-r--r--app/policies/project_policy.rb19
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml3
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml2
-rw-r--r--changelogs/unreleased/201927-solarized-dark.yml5
-rw-r--r--changelogs/unreleased/214882-esc-key-handler.yml6
-rw-r--r--changelogs/unreleased/215700-fix-incorrect-commits-number-in-commits-list.yml5
-rw-r--r--changelogs/unreleased/215902-ci-job-jwt-handle-signing-key-issues.yml5
-rw-r--r--changelogs/unreleased/mw-cr-title-margin.yml5
-rw-r--r--changelogs/unreleased/mw-ia-add-title.yml5
-rw-r--r--changelogs/unreleased/mw-insights-add-title.yml5
-rw-r--r--changelogs/unreleased/mw-vsa-title-cleanup.yml5
-rw-r--r--changelogs/unreleased/pedroms-threads-counter-wording.yml5
-rw-r--r--changelogs/unreleased/ph-215917-escapeRef.yml5
-rw-r--r--changelogs/unreleased/sh-clean-up-public-visiblity-level-check.yml5
-rw-r--r--changelogs/unreleased/sh-disable-schema-dump-prod.yml5
-rw-r--r--changelogs/unreleased/sh-fix-create-projects-with-prometheus-service.yml5
-rw-r--r--changelogs/unreleased/sh-fix-lfs-badge-feature-flag.yml5
-rw-r--r--doc/administration/logs.md6
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json18
-rw-r--r--doc/api/graphql/reference/index.md1
-rw-r--r--doc/development/fe_guide/style/javascript.md3
-rw-r--r--locale/gitlab.pot23
-rw-r--r--package.json1
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb54
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js29
-rw-r--r--spec/frontend/notes/components/discussion_counter_spec.js9
-rw-r--r--spec/frontend/sidebar/assignees_realtime_spec.js100
-rw-r--r--spec/frontend/sidebar/sidebar_assignees_spec.js46
-rw-r--r--spec/graphql/types/user_type_spec.rb2
-rw-r--r--spec/policies/design_management/design_policy_spec.rb174
-rw-r--r--yarn.lock5
56 files changed, 2105 insertions, 94 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc8c035dc46..c12ef304111 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,22 +2,6 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
-## 12.10.3 (2020-05-04)
-
-### Fixed (6 changes)
-
-- Fix errors creating project with active Prometheus service template. !30340
-- Fix incorrect commits number in commits list. !30412
-- Fix second 500 error with NULL restricted visibility levels. !30414
-- Add LFS badge feature flag to RefsController#logs_tree. !30442
-- Disable schema dumping after migrations in production. !30812
-- Fixes branch name not getting escaped correctly on frontend.
-
-### Changed (1 change)
-
-- Handle possible RSA key exceptions when generating CI_JOB_JWT. !30702
-
-
## 12.10.2 (2020-04-30)
### Security (8 changes)
diff --git a/app/assets/javascripts/actioncable_consumer.js b/app/assets/javascripts/actioncable_consumer.js
new file mode 100644
index 00000000000..5658ffc1a38
--- /dev/null
+++ b/app/assets/javascripts/actioncable_consumer.js
@@ -0,0 +1,3 @@
+import { createConsumer } from '@rails/actioncable';
+
+export default createConsumer();
diff --git a/app/assets/javascripts/ide/lib/themes/index.js b/app/assets/javascripts/ide/lib/themes/index.js
index 6ed9f6679a4..47d1bddc4b2 100644
--- a/app/assets/javascripts/ide/lib/themes/index.js
+++ b/app/assets/javascripts/ide/lib/themes/index.js
@@ -1,5 +1,7 @@
import white from './white';
import dark from './dark';
+import monokai from './monokai';
+import solarizedDark from './solarized_dark';
export const themes = [
{
@@ -10,6 +12,14 @@ export const themes = [
name: 'dark',
data: dark,
},
+ {
+ name: 'solarized-dark',
+ data: solarizedDark,
+ },
+ {
+ name: 'monokai',
+ data: monokai,
+ },
];
export const DEFAULT_THEME = 'white';
diff --git a/app/assets/javascripts/ide/lib/themes/monokai.js b/app/assets/javascripts/ide/lib/themes/monokai.js
new file mode 100644
index 00000000000..d7636574754
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/themes/monokai.js
@@ -0,0 +1,169 @@
+/*
+
+https://github.com/brijeshb42/monaco-themes/blob/master/themes/Tomorrow-Night.json
+
+The MIT License (MIT)
+
+Copyright (c) Brijesh Bittu
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+
+export default {
+ base: 'vs-dark',
+ inherit: true,
+ rules: [
+ {
+ foreground: '75715e',
+ token: 'comment',
+ },
+ {
+ foreground: 'e6db74',
+ token: 'string',
+ },
+ {
+ foreground: 'ae81ff',
+ token: 'constant.numeric',
+ },
+ {
+ foreground: 'ae81ff',
+ token: 'constant.language',
+ },
+ {
+ foreground: 'ae81ff',
+ token: 'constant.character',
+ },
+ {
+ foreground: 'ae81ff',
+ token: 'constant.other',
+ },
+ {
+ foreground: 'f92672',
+ token: 'keyword',
+ },
+ {
+ foreground: 'f92672',
+ token: 'storage',
+ },
+ {
+ foreground: '66d9ef',
+ fontStyle: 'italic',
+ token: 'storage.type',
+ },
+ {
+ foreground: 'a6e22e',
+ fontStyle: 'underline',
+ token: 'entity.name.class',
+ },
+ {
+ foreground: 'a6e22e',
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ fontStyle: 'italic underline',
+ token: 'entity.other.inherited-class',
+ },
+ {
+ foreground: 'a6e22e',
+ token: 'entity.name.function',
+ },
+ {
+ foreground: 'fd971f',
+ fontStyle: 'italic',
+ token: 'variable.parameter',
+ },
+ {
+ foreground: 'f92672',
+ token: 'entity.name.tag',
+ },
+ {
+ foreground: 'a6e22e',
+ token: 'entity.other.attribute-name',
+ },
+ {
+ foreground: '66d9ef',
+ token: 'support.function',
+ },
+ {
+ foreground: '66d9ef',
+ token: 'support.constant',
+ },
+ {
+ foreground: '66d9ef',
+ fontStyle: 'italic',
+ token: 'support.type',
+ },
+ {
+ foreground: '66d9ef',
+ fontStyle: 'italic',
+ token: 'support.class',
+ },
+ {
+ foreground: 'f8f8f0',
+ background: 'f92672',
+ token: 'invalid',
+ },
+ {
+ foreground: 'f8f8f0',
+ background: 'ae81ff',
+ token: 'invalid.deprecated',
+ },
+ {
+ foreground: 'cfcfc2',
+ token: 'meta.structure.dictionary.json string.quoted.double.json',
+ },
+ {
+ foreground: '75715e',
+ token: 'meta.diff',
+ },
+ {
+ foreground: '75715e',
+ token: 'meta.diff.header',
+ },
+ {
+ foreground: 'f92672',
+ token: 'markup.deleted',
+ },
+ {
+ foreground: 'a6e22e',
+ token: 'markup.inserted',
+ },
+ {
+ foreground: 'e6db74',
+ token: 'markup.changed',
+ },
+ {
+ foreground: 'ae81ffa0',
+ token: 'constant.numeric.line-number.find-in-files - match',
+ },
+ {
+ foreground: 'e6db74',
+ token: 'entity.name.filename.find-in-files',
+ },
+ ],
+ colors: {
+ 'editor.foreground': '#F8F8F2',
+ 'editor.background': '#272822',
+ 'editor.selectionBackground': '#49483E',
+ 'editor.lineHighlightBackground': '#3E3D32',
+ 'editorCursor.foreground': '#F8F8F0',
+ 'editorWhitespace.foreground': '#3B3A32',
+ 'editorIndentGuide.activeBackground': '#9D550FB0',
+ 'editor.selectionHighlightBorder': '#222218',
+ },
+};
diff --git a/app/assets/javascripts/ide/lib/themes/solarized_dark.js b/app/assets/javascripts/ide/lib/themes/solarized_dark.js
new file mode 100644
index 00000000000..3c9414b9dc9
--- /dev/null
+++ b/app/assets/javascripts/ide/lib/themes/solarized_dark.js
@@ -0,0 +1,1110 @@
+/*
+
+https://github.com/brijeshb42/monaco-themes/blob/master/themes/Solarized-dark.json
+
+The MIT License (MIT)
+
+Copyright (c) Brijesh Bittu
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+*/
+export default {
+ base: 'vs-dark',
+ inherit: true,
+ rules: [
+ {
+ foreground: '586e75',
+ token: 'comment',
+ },
+ {
+ foreground: '2aa198',
+ token: 'string',
+ },
+ {
+ foreground: '586e75',
+ token: 'string',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'string.regexp',
+ },
+ {
+ foreground: 'd33682',
+ token: 'constant.numeric',
+ },
+ {
+ foreground: '268bd2',
+ token: 'variable.language',
+ },
+ {
+ foreground: '268bd2',
+ token: 'variable.other',
+ },
+ {
+ foreground: '859900',
+ token: 'keyword',
+ },
+ {
+ foreground: '859900',
+ token: 'storage',
+ },
+ {
+ foreground: '268bd2',
+ token: 'entity.name.class',
+ },
+ {
+ foreground: '268bd2',
+ token: 'entity.name.type.class',
+ },
+ {
+ foreground: '268bd2',
+ token: 'entity.name.function',
+ },
+ {
+ foreground: '859900',
+ token: 'punctuation.definition.variable',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.section.embedded.begin',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.section.embedded.end',
+ },
+ {
+ foreground: 'b58900',
+ token: 'constant.language',
+ },
+ {
+ foreground: 'b58900',
+ token: 'meta.preprocessor',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'support.function.construct',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'keyword.other.new',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'constant.character',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'constant.other',
+ },
+ {
+ foreground: '268bd2',
+ fontStyle: 'bold',
+ token: 'entity.name.tag',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.definition.tag.html',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.definition.tag.begin',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.definition.tag.end',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'entity.other.attribute-name',
+ },
+ {
+ foreground: '268bd2',
+ token: 'support.function',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.separator.continuation',
+ },
+ {
+ foreground: '859900',
+ token: 'support.type',
+ },
+ {
+ foreground: '859900',
+ token: 'support.class',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'support.type.exception',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.other.special-method',
+ },
+ {
+ foreground: '2aa198',
+ token: 'string.quoted.double',
+ },
+ {
+ foreground: '2aa198',
+ token: 'string.quoted.single',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.string.begin',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.string.end',
+ },
+ {
+ foreground: 'b58900',
+ token: 'entity.name.tag.css',
+ },
+ {
+ foreground: 'b58900',
+ token: 'support.type.property-name.css',
+ },
+ {
+ foreground: 'b58900',
+ token: 'meta.property-name.css',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'source.css',
+ },
+ {
+ foreground: '586e75',
+ token: 'meta.selector.css',
+ },
+ {
+ foreground: '6c71c4',
+ token: 'punctuation.section.property-list.css',
+ },
+ {
+ foreground: '2aa198',
+ token: 'meta.property-value.css constant.numeric.css',
+ },
+ {
+ foreground: '2aa198',
+ token: 'keyword.other.unit.css',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.other.color.rgb-value.css',
+ },
+ {
+ foreground: '2aa198',
+ token: 'meta.property-value.css',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'keyword.other.important.css',
+ },
+ {
+ foreground: '2aa198',
+ token: 'support.constant.color',
+ },
+ {
+ foreground: '859900',
+ token: 'entity.name.tag.css',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.separator.key-value.css',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.terminator.rule.css',
+ },
+ {
+ foreground: '268bd2',
+ token: 'entity.other.attribute-name.class.css',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'entity.other.attribute-name.pseudo-element.css',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'entity.other.attribute-name.pseudo-class.css',
+ },
+ {
+ foreground: '268bd2',
+ token: 'entity.other.attribute-name.id.css',
+ },
+ {
+ foreground: 'b58900',
+ token: 'meta.function.js',
+ },
+ {
+ foreground: 'b58900',
+ token: 'entity.name.function.js',
+ },
+ {
+ foreground: 'b58900',
+ token: 'support.function.dom.js',
+ },
+ {
+ foreground: 'b58900',
+ token: 'text.html.basic source.js.embedded.html',
+ },
+ {
+ foreground: '268bd2',
+ token: 'storage.type.function.js',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.numeric.js',
+ },
+ {
+ foreground: '268bd2',
+ token: 'meta.brace.square.js',
+ },
+ {
+ foreground: '268bd2',
+ token: 'storage.type.js',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'meta.brace.round',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'punctuation.definition.parameters.begin.js',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'punctuation.definition.parameters.end.js',
+ },
+ {
+ foreground: '268bd2',
+ token: 'meta.brace.curly.js',
+ },
+ {
+ foreground: '93a1a1',
+ fontStyle: 'italic',
+ token: 'entity.name.tag.doctype.html',
+ },
+ {
+ foreground: '93a1a1',
+ fontStyle: 'italic',
+ token: 'meta.tag.sgml.html',
+ },
+ {
+ foreground: '93a1a1',
+ fontStyle: 'italic',
+ token: 'string.quoted.double.doctype.identifiers-and-DTDs.html',
+ },
+ {
+ foreground: '839496',
+ fontStyle: 'italic',
+ token: 'comment.block.html',
+ },
+ {
+ fontStyle: 'italic',
+ token: 'entity.name.tag.script.html',
+ },
+ {
+ foreground: '2aa198',
+ token: 'source.css.embedded.html string.quoted.double.html',
+ },
+ {
+ foreground: 'cb4b16',
+ fontStyle: 'bold',
+ token: 'text.html.ruby',
+ },
+ {
+ foreground: '657b83',
+ token: 'text.html.basic meta.tag.other.html',
+ },
+ {
+ foreground: '657b83',
+ token: 'text.html.basic meta.tag.any.html',
+ },
+ {
+ foreground: '657b83',
+ token: 'text.html.basic meta.tag.block.any',
+ },
+ {
+ foreground: '657b83',
+ token: 'text.html.basic meta.tag.inline.any',
+ },
+ {
+ foreground: '657b83',
+ token: 'text.html.basic meta.tag.structure.any.html',
+ },
+ {
+ foreground: '657b83',
+ token: 'text.html.basic source.js.embedded.html',
+ },
+ {
+ foreground: '657b83',
+ token: 'punctuation.separator.key-value.html',
+ },
+ {
+ foreground: '657b83',
+ token: 'text.html.basic entity.other.attribute-name.html',
+ },
+ {
+ foreground: '2aa198',
+ token: 'text.html.basic meta.tag.structure.any.html punctuation.definition.string.begin.html',
+ },
+ {
+ foreground: '2aa198',
+ token: 'punctuation.definition.string.begin.html',
+ },
+ {
+ foreground: '2aa198',
+ token: 'punctuation.definition.string.end.html',
+ },
+ {
+ foreground: '268bd2',
+ fontStyle: 'bold',
+ token: 'entity.name.tag.block.any.html',
+ },
+ {
+ fontStyle: 'italic',
+ token: 'source.css.embedded.html entity.name.tag.style.html',
+ },
+ {
+ foreground: '839496',
+ fontStyle: 'italic',
+ token: 'source.css.embedded.html',
+ },
+ {
+ foreground: '839496',
+ fontStyle: 'italic',
+ token: 'comment.block.html',
+ },
+ {
+ foreground: '268bd2',
+ token: 'punctuation.definition.variable.ruby',
+ },
+ {
+ foreground: '657b83',
+ token: 'meta.function.method.with-arguments.ruby',
+ },
+ {
+ foreground: '2aa198',
+ token: 'variable.language.ruby',
+ },
+ {
+ foreground: '268bd2',
+ token: 'entity.name.function.ruby',
+ },
+ {
+ foreground: '859900',
+ fontStyle: 'bold',
+ token: 'keyword.control.ruby',
+ },
+ {
+ foreground: '859900',
+ fontStyle: 'bold',
+ token: 'keyword.control.def.ruby',
+ },
+ {
+ foreground: '859900',
+ token: 'keyword.control.class.ruby',
+ },
+ {
+ foreground: '859900',
+ token: 'meta.class.ruby',
+ },
+ {
+ foreground: 'b58900',
+ token: 'entity.name.type.class.ruby',
+ },
+ {
+ foreground: '859900',
+ token: 'keyword.control.ruby',
+ },
+ {
+ foreground: 'b58900',
+ token: 'support.class.ruby',
+ },
+ {
+ foreground: '859900',
+ token: 'keyword.other.special-method.ruby',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.language.ruby',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.numeric.ruby',
+ },
+ {
+ foreground: 'b58900',
+ token: 'variable.other.constant.ruby',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.other.symbol.ruby',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.section.embedded.ruby',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.string.begin.ruby',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.string.end.ruby',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.other.special-method.ruby',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.control.import.include.php',
+ },
+ {
+ foreground: '839496',
+ token: 'text.html.ruby meta.tag.inline.any.html',
+ },
+ {
+ foreground: '2aa198',
+ token: 'text.html.ruby punctuation.definition.string.begin',
+ },
+ {
+ foreground: '2aa198',
+ token: 'text.html.ruby punctuation.definition.string.end',
+ },
+ {
+ foreground: '839496',
+ token: 'punctuation.definition.string.begin',
+ },
+ {
+ foreground: '839496',
+ token: 'punctuation.definition.string.end',
+ },
+ {
+ foreground: '839496',
+ token: 'support.class.php',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'keyword.operator.index-start.php',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'keyword.operator.index-end.php',
+ },
+ {
+ foreground: '586e75',
+ token: 'meta.array.php',
+ },
+ {
+ foreground: 'b58900',
+ token: 'meta.array.php support.function.construct.php',
+ },
+ {
+ foreground: 'b58900',
+ token: 'meta.array.empty.php support.function.construct.php',
+ },
+ {
+ foreground: 'b58900',
+ token: 'support.function.construct.php',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.array.begin',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.array.end',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.numeric.php',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.other.new.php',
+ },
+ {
+ foreground: '839496',
+ token: 'keyword.operator.class',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'variable.other.property.php',
+ },
+ {
+ foreground: 'b58900',
+ token: 'storage.modifier.extends.php',
+ },
+ {
+ foreground: 'b58900',
+ token: 'storage.type.class.php',
+ },
+ {
+ foreground: 'b58900',
+ token: 'keyword.operator.class.php',
+ },
+ {
+ foreground: '839496',
+ token: 'punctuation.terminator.expression.php',
+ },
+ {
+ foreground: '586e75',
+ token: 'meta.other.inherited-class.php',
+ },
+ {
+ foreground: '859900',
+ token: 'storage.type.php',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'entity.name.function.php',
+ },
+ {
+ foreground: '859900',
+ token: 'support.function.construct.php',
+ },
+ {
+ foreground: '839496',
+ token: 'entity.name.type.class.php',
+ },
+ {
+ foreground: '839496',
+ token: 'meta.function-call.php',
+ },
+ {
+ foreground: '839496',
+ token: 'meta.function-call.static.php',
+ },
+ {
+ foreground: '839496',
+ token: 'meta.function-call.object.php',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'keyword.other.phpdoc',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'source.php.embedded.block.html',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'storage.type.function.php',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.numeric.c',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'meta.preprocessor.c.include',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'meta.preprocessor.macro.c',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.control.import.define.c',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.control.import.include.c',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'entity.name.function.preprocessor.c',
+ },
+ {
+ foreground: '2aa198',
+ token: 'meta.preprocessor.c.include string.quoted.other.lt-gt.include.c',
+ },
+ {
+ foreground: '2aa198',
+ token: 'meta.preprocessor.c.include punctuation.definition.string.begin.c',
+ },
+ {
+ foreground: '2aa198',
+ token: 'meta.preprocessor.c.include punctuation.definition.string.end.c',
+ },
+ {
+ foreground: '586e75',
+ token: 'support.function.C99.c',
+ },
+ {
+ foreground: '586e75',
+ token: 'support.function.any-method.c',
+ },
+ {
+ foreground: '586e75',
+ token: 'entity.name.function.c',
+ },
+ {
+ foreground: '2aa198',
+ token: 'punctuation.definition.string.begin.c',
+ },
+ {
+ foreground: '2aa198',
+ token: 'punctuation.definition.string.end.c',
+ },
+ {
+ foreground: 'b58900',
+ token: 'storage.type.c',
+ },
+ {
+ foreground: 'e0eddd',
+ background: 'b58900',
+ fontStyle: 'italic',
+ token: 'meta.diff',
+ },
+ {
+ foreground: 'e0eddd',
+ background: 'b58900',
+ fontStyle: 'italic',
+ token: 'meta.diff.header',
+ },
+ {
+ foreground: 'dc322f',
+ background: 'eee8d5',
+ token: 'markup.deleted',
+ },
+ {
+ foreground: 'cb4b16',
+ background: 'eee8d5',
+ token: 'markup.changed',
+ },
+ {
+ foreground: '219186',
+ background: 'eee8d5',
+ token: 'markup.inserted',
+ },
+ {
+ foreground: 'e0eddd',
+ background: 'b58900',
+ token: 'text.html.markdown meta.dummy.line-break',
+ },
+ {
+ foreground: '2aa198',
+ token: 'text.html.markdown markup.raw.inline',
+ },
+ {
+ foreground: '2aa198',
+ token: 'text.restructuredtext markup.raw',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'other.package.exclude',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'other.remove',
+ },
+ {
+ foreground: '2aa198',
+ token: 'other.add',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.section.group.tex',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.arguments.begin.latex',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.arguments.end.latex',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.arguments.latex',
+ },
+ {
+ foreground: 'b58900',
+ token: 'meta.group.braces.tex',
+ },
+ {
+ foreground: 'b58900',
+ token: 'string.other.math.tex',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'variable.parameter.function.latex',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.constant.math.tex',
+ },
+ {
+ foreground: '2aa198',
+ token: 'text.tex.latex constant.other.math.tex',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.other.general.math.tex',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.other.general.math.tex',
+ },
+ {
+ foreground: '2aa198',
+ token: 'constant.character.math.tex',
+ },
+ {
+ foreground: 'b58900',
+ token: 'string.other.math.tex',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.string.begin.tex',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.string.end.tex',
+ },
+ {
+ foreground: '2aa198',
+ token: 'keyword.control.label.latex',
+ },
+ {
+ foreground: '2aa198',
+ token: 'text.tex.latex constant.other.general.math.tex',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'variable.parameter.definition.label.latex',
+ },
+ {
+ foreground: '859900',
+ token: 'support.function.be.latex',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'support.function.section.latex',
+ },
+ {
+ foreground: '2aa198',
+ token: 'support.function.general.tex',
+ },
+ {
+ fontStyle: 'italic',
+ token: 'punctuation.definition.comment.tex',
+ },
+ {
+ fontStyle: 'italic',
+ token: 'comment.line.percentage.tex',
+ },
+ {
+ foreground: '2aa198',
+ token: 'keyword.control.ref.latex',
+ },
+ {
+ foreground: '586e75',
+ token: 'string.quoted.double.block.python',
+ },
+ {
+ foreground: '859900',
+ token: 'storage.type.class.python',
+ },
+ {
+ foreground: '859900',
+ token: 'storage.type.function.python',
+ },
+ {
+ foreground: '859900',
+ token: 'storage.modifier.global.python',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.control.import.python',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.control.import.from.python',
+ },
+ {
+ foreground: 'b58900',
+ token: 'support.type.exception.python',
+ },
+ {
+ foreground: '859900',
+ token: 'support.function.builtin.shell',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'variable.other.normal.shell',
+ },
+ {
+ foreground: '268bd2',
+ token: 'source.shell',
+ },
+ {
+ foreground: '586e75',
+ token: 'meta.scope.for-in-loop.shell',
+ },
+ {
+ foreground: '586e75',
+ token: 'variable.other.loop.shell',
+ },
+ {
+ foreground: '859900',
+ token: 'punctuation.definition.string.end.shell',
+ },
+ {
+ foreground: '859900',
+ token: 'punctuation.definition.string.begin.shell',
+ },
+ {
+ foreground: '586e75',
+ token: 'meta.scope.case-block.shell',
+ },
+ {
+ foreground: '586e75',
+ token: 'meta.scope.case-body.shell',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.logical-expression.shell',
+ },
+ {
+ fontStyle: 'italic',
+ token: 'comment.line.number-sign.shell',
+ },
+ {
+ foreground: 'cb4b16',
+ token: 'keyword.other.import.java',
+ },
+ {
+ foreground: '586e75',
+ token: 'storage.modifier.import.java',
+ },
+ {
+ foreground: 'b58900',
+ token: 'meta.class.java storage.modifier.java',
+ },
+ {
+ foreground: '586e75',
+ token: 'source.java comment.block',
+ },
+ {
+ foreground: '586e75',
+ token:
+ 'comment.block meta.documentation.tag.param.javadoc keyword.other.documentation.param.javadoc',
+ },
+ {
+ foreground: 'b58900',
+ token: 'punctuation.definition.variable.perl',
+ },
+ {
+ foreground: 'b58900',
+ token: 'variable.other.readwrite.global.perl',
+ },
+ {
+ foreground: 'b58900',
+ token: 'variable.other.predefined.perl',
+ },
+ {
+ foreground: 'b58900',
+ token: 'keyword.operator.comparison.perl',
+ },
+ {
+ foreground: '859900',
+ token: 'support.function.perl',
+ },
+ {
+ foreground: '586e75',
+ fontStyle: 'italic',
+ token: 'comment.line.number-sign.perl',
+ },
+ {
+ foreground: '2aa198',
+ token: 'punctuation.definition.string.begin.perl',
+ },
+ {
+ foreground: '2aa198',
+ token: 'punctuation.definition.string.end.perl',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'constant.character.escape.perl',
+ },
+ {
+ foreground: '268bd2',
+ token: 'markup.heading.markdown',
+ },
+ {
+ foreground: '268bd2',
+ token: 'markup.heading.1.markdown',
+ },
+ {
+ foreground: '268bd2',
+ token: 'markup.heading.2.markdown',
+ },
+ {
+ foreground: '268bd2',
+ token: 'markup.heading.3.markdown',
+ },
+ {
+ foreground: '268bd2',
+ token: 'markup.heading.4.markdown',
+ },
+ {
+ foreground: '268bd2',
+ token: 'markup.heading.5.markdown',
+ },
+ {
+ foreground: '268bd2',
+ token: 'markup.heading.6.markdown',
+ },
+ {
+ foreground: '839496',
+ fontStyle: 'bold',
+ token: 'markup.bold.markdown',
+ },
+ {
+ foreground: '839496',
+ fontStyle: 'italic',
+ token: 'markup.italic.markdown',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.bold.markdown',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.italic.markdown',
+ },
+ {
+ foreground: 'dc322f',
+ token: 'punctuation.definition.raw.markdown',
+ },
+ {
+ foreground: 'b58900',
+ token: 'markup.list.unnumbered.markdown',
+ },
+ {
+ foreground: '859900',
+ token: 'markup.list.numbered.markdown',
+ },
+ {
+ foreground: '2aa198',
+ token: 'markup.raw.block.markdown',
+ },
+ {
+ foreground: '2aa198',
+ token: 'markup.raw.inline.markdown',
+ },
+ {
+ foreground: '6c71c4',
+ token: 'markup.quote.markdown',
+ },
+ {
+ foreground: '6c71c4',
+ token: 'punctuation.definition.blockquote.markdown',
+ },
+ {
+ foreground: 'd33682',
+ token: 'meta.separator.markdown',
+ },
+ {
+ foreground: '586e75',
+ fontStyle: 'italic',
+ token: 'meta.image.inline.markdown',
+ },
+ {
+ foreground: '586e75',
+ fontStyle: 'italic',
+ token: 'markup.underline.link.markdown',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'string.other.link.title.markdown',
+ },
+ {
+ foreground: '93a1a1',
+ token: 'string.other.link.description.markdown',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.definition.link.markdown',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.definition.metadata.markdown',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.definition.string.begin.markdown',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.definition.string.end.markdown',
+ },
+ {
+ foreground: '586e75',
+ token: 'punctuation.definition.constant.markdown',
+ },
+ {
+ foreground: 'eee8d5',
+ background: 'eee8d5',
+ token: 'sublimelinter.notes',
+ },
+ {
+ foreground: '93a1a1',
+ background: '93a1a1',
+ token: 'sublimelinter.outline.illegal',
+ },
+ {
+ background: 'dc322f',
+ token: 'sublimelinter.underline.illegal',
+ },
+ {
+ foreground: '839496',
+ background: '839496',
+ token: 'sublimelinter.outline.warning',
+ },
+ {
+ background: 'b58900',
+ token: 'sublimelinter.underline.warning',
+ },
+ {
+ foreground: '657b83',
+ background: '657b83',
+ token: 'sublimelinter.outline.violation',
+ },
+ {
+ background: 'cb4b16',
+ token: 'sublimelinter.underline.violation',
+ },
+ ],
+ colors: {
+ 'editor.foreground': '#839496',
+ 'editor.background': '#002B36',
+ 'editor.selectionBackground': '#073642',
+ 'editor.lineHighlightBackground': '#073642',
+ 'editorCursor.foreground': '#819090',
+ 'editorWhitespace.foreground': '#073642',
+ },
+};
diff --git a/app/assets/javascripts/issuable_sidebar/queries/issue_sidebar.query.graphql b/app/assets/javascripts/issuable_sidebar/queries/issue_sidebar.query.graphql
new file mode 100644
index 00000000000..fe01d2c2e78
--- /dev/null
+++ b/app/assets/javascripts/issuable_sidebar/queries/issue_sidebar.query.graphql
@@ -0,0 +1,15 @@
+#import "~/graphql_shared/fragments/author.fragment.graphql"
+
+query getProjectIssue($iid: String!, $fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ issue(iid: $iid) {
+ assignees {
+ nodes {
+ ...Author
+ id
+ state
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/lib/utils/keycodes.js b/app/assets/javascripts/lib/utils/keycodes.js
index 16bffc5c2cf..618266f7a09 100644
--- a/app/assets/javascripts/lib/utils/keycodes.js
+++ b/app/assets/javascripts/lib/utils/keycodes.js
@@ -1,3 +1,6 @@
+// `e.keyCode` is deprecated, these values should be migrated
+// See: https://gitlab.com/gitlab-org/gitlab/-/issues/216102
+
export const BACKSPACE_KEY_CODE = 8;
export const ENTER_KEY_CODE = 13;
export const ESC_KEY_CODE = 27;
diff --git a/app/assets/javascripts/lib/utils/keys.js b/app/assets/javascripts/lib/utils/keys.js
new file mode 100644
index 00000000000..8e5420e87ea
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/keys.js
@@ -0,0 +1,4 @@
+/* eslint-disable @gitlab/require-i18n-strings */
+
+export const ESC_KEY = 'Escape';
+export const ESC_KEY_IE11 = 'Esc'; // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 1877f58ed96..67835a5e356 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -19,6 +19,7 @@ import {
import DashboardPanel from './dashboard_panel.vue';
import { s__ } from '~/locale';
import createFlash from '~/flash';
+import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys';
import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import { mergeUrlParams, redirectTo, updateHistory } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
@@ -248,6 +249,10 @@ export default {
logsPath: this.logsPath,
currentEnvironmentName: this.currentEnvironmentName,
});
+ window.addEventListener('keyup', this.onKeyup);
+ },
+ destroyed() {
+ window.removeEventListener('keyup', this.onKeyup);
},
mounted() {
if (!this.hasMetrics) {
@@ -371,13 +376,19 @@ export default {
onGoBack() {
this.clearExpandedPanel();
},
+ onKeyup(event) {
+ const { key } = event;
+ if (key === ESC_KEY || key === ESC_KEY_IE11) {
+ this.clearExpandedPanel();
+ }
+ },
},
addMetric: {
title: s__('Metrics|Add metric'),
modalId: 'add-metric',
},
i18n: {
- goBackLabel: s__('Metrics|Go back'),
+ goBackLabel: s__('Metrics|Go back (Esc)'),
},
};
</script>
diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue
index 07952f9edd9..4a1a1086329 100644
--- a/app/assets/javascripts/notes/components/discussion_counter.vue
+++ b/app/assets/javascripts/notes/components/discussion_counter.vue
@@ -29,9 +29,6 @@ export default {
resolveAllDiscussionsIssuePath() {
return this.getNoteableData.create_issue_to_resolve_discussions_path;
},
- resolvedDiscussionsCount() {
- return this.resolvableDiscussionsCount - this.unresolvedDiscussionsCount;
- },
toggeableDiscussions() {
return this.discussions.filter(discussion => !discussion.individual_note);
},
@@ -60,15 +57,15 @@ export default {
<div class="full-width-mobile d-flex d-sm-flex">
<div class="line-resolve-all">
<span
- :class="{ 'is-active': allResolved }"
- class="line-resolve-btn is-disabled"
- type="button"
+ :class="{ 'line-resolve-btn is-active': allResolved, 'line-resolve-text': !allResolved }"
>
- <icon :name="allResolved ? 'check-circle-filled' : 'check-circle'" />
- </span>
- <span class="line-resolve-text">
- {{ resolvedDiscussionsCount }}/{{ resolvableDiscussionsCount }}
- {{ n__('thread resolved', 'threads resolved', resolvableDiscussionsCount) }}
+ <template v-if="allResolved">
+ <icon name="check-circle-filled" />
+ {{ __('All threads resolved') }}
+ </template>
+ <template v-else>
+ {{ n__('%d unresolved thread', '%d unresolved threads', unresolvedDiscussionsCount) }}
+ </template>
</span>
</div>
<div
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue b/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
new file mode 100644
index 00000000000..f6646823c5c
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
@@ -0,0 +1,71 @@
+<script>
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
+import actionCable from '~/actioncable_consumer';
+
+export default {
+ name: 'AssigneesRealtime',
+ props: {
+ mediator: {
+ type: Object,
+ required: true,
+ },
+ issuableIid: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ },
+ apollo: {
+ project: {
+ query,
+ variables() {
+ return {
+ iid: this.issuableIid,
+ fullPath: this.projectPath,
+ };
+ },
+ result(data) {
+ this.handleFetchResult(data);
+ },
+ },
+ },
+ mounted() {
+ this.initActionCablePolling();
+ },
+ methods: {
+ received(data) {
+ if (data.event === 'updated') {
+ this.$apollo.queries.project.refetch();
+ }
+ },
+ initActionCablePolling() {
+ actionCable.subscriptions.create(
+ {
+ channel: 'IssuesChannel',
+ project_path: this.projectPath,
+ iid: this.issuableIid,
+ },
+ { received: this.received },
+ );
+ },
+ handleFetchResult({ data }) {
+ const { nodes } = data.project.issue.assignees;
+
+ const assignees = nodes.map(n => ({
+ ...n,
+ avatar_url: n.avatarUrl,
+ id: getIdFromGraphQLId(n.id),
+ }));
+
+ this.mediator.store.setAssigneesFromRealtime(assignees);
+ },
+ },
+ render() {
+ return this.$slots.default;
+ },
+};
+</script>
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index ce592720531..0906d5abec3 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -3,8 +3,10 @@ import Flash from '~/flash';
import eventHub from '~/sidebar/event_hub';
import Store from '~/sidebar/stores/sidebar_store';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import AssigneeTitle from './assignee_title.vue';
import Assignees from './assignees.vue';
+import AssigneesRealtime from './assignees_realtime.vue';
import { __ } from '~/locale';
export default {
@@ -12,7 +14,9 @@ export default {
components: {
AssigneeTitle,
Assignees,
+ AssigneesRealtime,
},
+ mixins: [glFeatureFlagsMixin()],
props: {
mediator: {
type: Object,
@@ -32,6 +36,14 @@ export default {
required: false,
default: 'issue',
},
+ issuableIid: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -39,6 +51,12 @@ export default {
loading: false,
};
},
+ computed: {
+ shouldEnableRealtime() {
+ // Note: Realtime is only available on issues right now, future support for MR wil be built later.
+ return this.glFeatures.realTimeIssueSidebar && this.issuableType === 'issue';
+ },
+ },
created() {
this.removeAssignee = this.store.removeAssignee.bind(this.store);
this.addAssignee = this.store.addAssignee.bind(this.store);
@@ -84,6 +102,12 @@ export default {
<template>
<div>
+ <assignees-realtime
+ v-if="shouldEnableRealtime"
+ :issuable-iid="issuableIid"
+ :project-path="projectPath"
+ :mediator="mediator"
+ />
<assignee-title
:number-of-assignees="store.assignees.length"
:loading="loading || store.isFetching.assignees"
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index 6f8214b18ee..05d9ce240f3 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Vue from 'vue';
+import VueApollo from 'vue-apollo';
import SidebarTimeTracking from './components/time_tracking/sidebar_time_tracking.vue';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import ConfidentialIssueSidebar from './components/confidential/confidential_issue_sidebar.vue';
@@ -8,17 +9,28 @@ import LockIssueSidebar from './components/lock/lock_issue_sidebar.vue';
import sidebarParticipants from './components/participants/sidebar_participants.vue';
import sidebarSubscriptions from './components/subscriptions/sidebar_subscriptions.vue';
import Translate from '../vue_shared/translate';
+import createDefaultClient from '~/lib/graphql';
Vue.use(Translate);
+Vue.use(VueApollo);
+
+function getSidebarOptions() {
+ return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
+}
function mountAssigneesComponent(mediator) {
const el = document.getElementById('js-vue-sidebar-assignees');
+ const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(),
+ });
if (!el) return;
+ const { iid, fullPath } = getSidebarOptions();
// eslint-disable-next-line no-new
new Vue({
el,
+ apolloProvider,
components: {
SidebarAssignees,
},
@@ -26,6 +38,8 @@ function mountAssigneesComponent(mediator) {
createElement('sidebar-assignees', {
props: {
mediator,
+ issuableIid: String(iid),
+ projectPath: fullPath,
field: el.dataset.field,
signedIn: el.hasAttribute('data-signed-in'),
issuableType: gl.utils.isInIssuePage() ? 'issue' : 'merge_request',
@@ -144,6 +158,4 @@ export function mountSidebar(mediator) {
mountTimeTrackingComponent();
}
-export function getSidebarOptions() {
- return JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
-}
+export { getSidebarOptions };
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 66f7f9e3c66..095f93b72a9 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -89,6 +89,10 @@ export default class SidebarStore {
this.assignees = [];
}
+ setAssigneesFromRealtime(data) {
+ this.assignees = data;
+ }
+
setAutocompleteProjects(projects) {
this.autocompleteProjects = projects;
}
diff --git a/app/assets/stylesheets/page_bundles/themes/_dark.scss b/app/assets/stylesheets/page_bundles/themes/_dark.scss
index ef9a648a93e..66b4b745532 100644
--- a/app/assets/stylesheets/page_bundles/themes/_dark.scss
+++ b/app/assets/stylesheets/page_bundles/themes/_dark.scss
@@ -27,6 +27,9 @@
$btn-disabled-border: rgba(223, 223, 223, 0.24);
$btn-disabled-color: rgba(145, 145, 145, 0.48);
+ $dropdown-background: #404040;
+ $dropdown-hover-background: #525252;
+
$diff-insert: rgba(155, 185, 85, 0.2);
$diff-remove: rgba(255, 0, 0, 0.2);
@@ -54,7 +57,12 @@
textarea,
.md-area.is-focused,
.ide-entry-dropdown-toggle,
- .nav-links:not(.quick-links) li:not(.md-header-toolbar) a:hover {
+ .nav-links:not(.quick-links) li:not(.md-header-toolbar) a:hover,
+ .dropdown-menu li button,
+ .ide-merge-request-project-path,
+ .dropdown-menu-selectable li a.is-active,
+ .dropdown-menu-inner-title,
+ .dropdown-menu-inner-content {
color: $text-color;
}
@@ -82,11 +90,17 @@
color: $text-color !important;
}
+ input[type='search']::placeholder,
input[type='text']::placeholder,
- textarea::placeholder {
+ textarea::placeholder,
+ .dropdown-input .fa {
color: $input-border;
}
+ .ide-nav-form .input-icon {
+ fill: $input-border;
+ }
+
.ide-staged-action-btn {
background-color: transparent;
}
@@ -112,7 +126,8 @@
background-color: inherit;
}
- .ide-sidebar-link:hover {
+ .ide-sidebar-link:hover,
+ .multi-file-tabs li {
background-color: $background-hover;
}
@@ -204,21 +219,40 @@
background-color: $footer-background;
}
- input[type='text'] {
+ input[type='text'],
+ input[type='search'],
+ .filtered-search-box {
border-color: $input-border;
- background: $input-background;
+ background-color: $input-background;
}
input[type='text'],
+ input[type='search'],
+ .filtered-search-box,
textarea {
color: $input-color !important;
}
+ .filtered-search-box input[type='search'] {
+ border-color: transparent;
+ }
+
+ .filtered-search-token .value-container,
+ .filtered-search-term .value-container {
+ background-color: $dropdown-hover-background;
+
+ color: $text-color;
+
+ &:hover {
+ background-color: $input-border;
+ }
+ }
+
.ide-entry-dropdown-toggle:hover {
background: $gray-800;
}
- .btn:hover {
+ .btn:not(.btn-link):hover {
border-width: 2px;
padding: 5px 9px;
}
@@ -257,6 +291,48 @@
}
}
+ .dropdown-menu {
+ color: $text-color;
+ border-color: $background;
+ background-color: $dropdown-background;
+
+ .divider,
+ .nav-links:not(.quick-links) {
+ background-color: $dropdown-hover-background;
+ border-color: $dropdown-hover-background;
+ }
+
+ .nav-links li a.active {
+ border-color: $highlight-accent;
+ }
+
+ .ide-nav-form .nav-links li a:not(.active) {
+ background-color: $dropdown-background;
+ }
+
+ .nav-links:not(.quick-links) li:not(.md-header-toolbar) a {
+ color: $text-color;
+
+ &.active {
+ color: $text-color;
+ }
+ }
+
+ li > a:not(.disable-hover):hover,
+ li > a:not(.disable-hover):focus,
+ li button:not(.disable-hover):hover,
+ li button:not(.disable-hover):focus,
+ li button.is-focused {
+ background-color: $dropdown-hover-background;
+ color: $text-color;
+ }
+ }
+
+ .dropdown-title,
+ .dropdown-input {
+ border-color: $dropdown-hover-background !important;
+ }
+
.btn-primary {
background-color: $btn-primary-background;
border-color: $btn-primary-border !important;
@@ -320,3 +396,7 @@
.navbar.theme-dark {
border-bottom-color: transparent;
}
+
+.theme-dark ~ .popover {
+ box-shadow: none;
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index c60e3c6b2b1..6445695bc6b 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -908,11 +908,10 @@ $note-form-margin-left: 72px;
border-right: 0;
.line-resolve-btn {
- margin-right: 5px;
color: $gray-700;
svg {
- vertical-align: middle;
+ vertical-align: text-top;
}
}
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 95b4d5e5658..da5401ffa00 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -50,6 +50,10 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:save_issuable_health_status, project.group, default_enabled: true)
end
+ before_action only: :show do
+ push_frontend_feature_flag(:real_time_issue_sidebar, @project)
+ end
+
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
respond_to :html
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index 5cee0c2cf8f..29a3f5d452f 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -14,6 +14,8 @@ module Types
description: 'ID of the user'
field :name, GraphQL::STRING_TYPE, null: false,
description: 'Human-readable name of the user'
+ field :state, GraphQL::STRING_TYPE, null: false,
+ description: 'State of the issue'
field :username, GraphQL::STRING_TYPE, null: false,
description: 'Username of the user. Unique within this instance of GitLab'
field :avatar_url, GraphQL::STRING_TYPE, null: true,
diff --git a/app/policies/design_management/design_at_version_policy.rb b/app/policies/design_management/design_at_version_policy.rb
new file mode 100644
index 00000000000..9decbc0c4b2
--- /dev/null
+++ b/app/policies/design_management/design_at_version_policy.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module DesignManagement
+ class DesignAtVersionPolicy < ::BasePolicy
+ delegate { @subject.version }
+ delegate { @subject.design }
+ end
+end
diff --git a/app/policies/design_management/design_collection_policy.rb b/app/policies/design_management/design_collection_policy.rb
new file mode 100644
index 00000000000..6a833da27cc
--- /dev/null
+++ b/app/policies/design_management/design_collection_policy.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module DesignManagement
+ class DesignCollectionPolicy < DesignPolicy
+ # Delegates everything to the `issue` just like the `DesignPolicy`
+ end
+end
diff --git a/app/policies/design_management/design_policy.rb b/app/policies/design_management/design_policy.rb
new file mode 100644
index 00000000000..57846095f80
--- /dev/null
+++ b/app/policies/design_management/design_policy.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module DesignManagement
+ class DesignPolicy < ::BasePolicy
+ # The IssuePolicy will delegate to the ProjectPolicy
+ delegate { @subject.issue }
+ end
+end
diff --git a/app/policies/design_management/version_policy.rb b/app/policies/design_management/version_policy.rb
new file mode 100644
index 00000000000..1c59ceaea98
--- /dev/null
+++ b/app/policies/design_management/version_policy.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module DesignManagement
+ class VersionPolicy < ::BasePolicy
+ # The IssuePolicy will delegate to the ProjectPolicy
+ delegate { @subject.issue }
+ end
+end
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index 20df823c737..28baa0d8338 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -15,6 +15,9 @@ class IssuePolicy < IssuablePolicy
desc "Issue is confidential"
condition(:confidential, scope: :subject) { @subject.confidential? }
+ desc "Issue has moved"
+ condition(:moved) { @subject.moved? }
+
rule { confidential & ~can_read_confidential }.policy do
prevent(*create_read_update_admin_destroy(:issue))
prevent :read_issue_iid
@@ -25,6 +28,15 @@ class IssuePolicy < IssuablePolicy
rule { locked }.policy do
prevent :reopen_issue
end
-end
-IssuePolicy.prepend_if_ee('::EE::IssuePolicy')
+ rule { ~can?(:read_issue) }.policy do
+ prevent :read_design
+ prevent :create_design
+ prevent :destroy_design
+ end
+
+ rule { locked | moved }.policy do
+ prevent :create_design
+ prevent :destroy_design
+ end
+end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 52099bbd0b8..4e0bcfe0985 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -11,6 +11,7 @@ class ProjectPolicy < BasePolicy
milestone
snippet
wiki
+ design
note
pipeline
pipeline_schedule
@@ -107,6 +108,11 @@ class ProjectPolicy < BasePolicy
)
end
+ with_scope :subject
+ condition(:design_management_disabled) do
+ !@subject.design_management_enabled?
+ end
+
# We aren't checking `:read_issue` or `:read_merge_request` in this case
# because it could be possible for a user to see an issuable-iid
# (`:read_issue_iid` or `:read_merge_request_iid`) but then wouldn't be
@@ -299,6 +305,8 @@ class ProjectPolicy < BasePolicy
enable :create_metrics_dashboard_annotation
enable :delete_metrics_dashboard_annotation
enable :update_metrics_dashboard_annotation
+ enable :create_design
+ enable :destroy_design
end
rule { can?(:developer_access) & user_confirmed? }.policy do
@@ -511,6 +519,17 @@ class ProjectPolicy < BasePolicy
rule { admin }.enable :change_repository_storage
+ rule { can?(:read_issue) }.policy do
+ enable :read_design
+ end
+
+ # Design abilities could also be prevented in the issue policy.
+ rule { design_management_disabled }.policy do
+ prevent :read_design
+ prevent :create_design
+ prevent :destroy_design
+ end
+
private
def team_member?
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 8c272a73d40..99c4fc0d1b6 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -68,6 +68,7 @@
= csrf_meta_tags
= csp_meta_tag
+ = action_cable_meta_tag
- unless browser.safari?
%meta{ name: 'referrer', content: 'origin-when-cross-origin' }
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index da20fee227a..e912198be3f 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -5,6 +5,9 @@
%banner{ "v-if" => "!isOverviewDialogDismissed",
"documentation-link": help_page_path('user/analytics/value_stream_analytics.md'),
"v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" }
+ .mb-3
+ %h3
+ = _("Value Stream Analytics")
%gl-loading-icon{ "v-show" => "isLoading", "size" => "lg" }
.wrapper{ "v-show" => "!isLoading && !hasError" }
.card
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index b5a27f2f17d..4192ecd2238 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -1,7 +1,7 @@
- issuable_type = issuable_sidebar[:type]
- signed_in = !!issuable_sidebar.dig(:current_user, :id)
-#js-vue-sidebar-assignees{ data: { field: "#{issuable_type}", signed_in: signed_in } }
+#js-vue-sidebar-assignees{ data: { field: issuable_type, signed_in: signed_in } }
.title.hide-collapsed
= _('Assignee')
.spinner.spinner-sm.align-bottom
diff --git a/changelogs/unreleased/201927-solarized-dark.yml b/changelogs/unreleased/201927-solarized-dark.yml
new file mode 100644
index 00000000000..54939ccadd4
--- /dev/null
+++ b/changelogs/unreleased/201927-solarized-dark.yml
@@ -0,0 +1,5 @@
+---
+title: Monokai and Solarized Dark syntax highlighting theme for Web IDE
+merge_request: 30931
+author:
+type: added
diff --git a/changelogs/unreleased/214882-esc-key-handler.yml b/changelogs/unreleased/214882-esc-key-handler.yml
new file mode 100644
index 00000000000..de593921806
--- /dev/null
+++ b/changelogs/unreleased/214882-esc-key-handler.yml
@@ -0,0 +1,6 @@
+---
+title: When viewing a single panel, return to a full dashboard by pressing the Escape
+ key
+merge_request: 30126
+author:
+type: added
diff --git a/changelogs/unreleased/215700-fix-incorrect-commits-number-in-commits-list.yml b/changelogs/unreleased/215700-fix-incorrect-commits-number-in-commits-list.yml
new file mode 100644
index 00000000000..4da7a43050c
--- /dev/null
+++ b/changelogs/unreleased/215700-fix-incorrect-commits-number-in-commits-list.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incorrect commits number in commits list
+merge_request: 30412
+author:
+type: fixed
diff --git a/changelogs/unreleased/215902-ci-job-jwt-handle-signing-key-issues.yml b/changelogs/unreleased/215902-ci-job-jwt-handle-signing-key-issues.yml
new file mode 100644
index 00000000000..8d4f48bffa7
--- /dev/null
+++ b/changelogs/unreleased/215902-ci-job-jwt-handle-signing-key-issues.yml
@@ -0,0 +1,5 @@
+---
+title: Handle possible RSA key exceptions when generating CI_JOB_JWT
+merge_request: 30702
+author:
+type: changed
diff --git a/changelogs/unreleased/mw-cr-title-margin.yml b/changelogs/unreleased/mw-cr-title-margin.yml
new file mode 100644
index 00000000000..6edc6c1bf9b
--- /dev/null
+++ b/changelogs/unreleased/mw-cr-title-margin.yml
@@ -0,0 +1,5 @@
+---
+title: 'Code review analytics: Change margin between title and description'
+merge_request: 30834
+author:
+type: changed
diff --git a/changelogs/unreleased/mw-ia-add-title.yml b/changelogs/unreleased/mw-ia-add-title.yml
new file mode 100644
index 00000000000..cc337ca9b46
--- /dev/null
+++ b/changelogs/unreleased/mw-ia-add-title.yml
@@ -0,0 +1,5 @@
+---
+title: 'Issues Analytics: Add title to page'
+merge_request: 30836
+author:
+type: added
diff --git a/changelogs/unreleased/mw-insights-add-title.yml b/changelogs/unreleased/mw-insights-add-title.yml
new file mode 100644
index 00000000000..8832c5ff84f
--- /dev/null
+++ b/changelogs/unreleased/mw-insights-add-title.yml
@@ -0,0 +1,5 @@
+---
+title: 'Insights Analytics: Add title to page'
+merge_request: 30853
+author:
+type: added
diff --git a/changelogs/unreleased/mw-vsa-title-cleanup.yml b/changelogs/unreleased/mw-vsa-title-cleanup.yml
new file mode 100644
index 00000000000..1a1b115f558
--- /dev/null
+++ b/changelogs/unreleased/mw-vsa-title-cleanup.yml
@@ -0,0 +1,5 @@
+---
+title: 'Value Stream Analytics: Add title and remove separator'
+merge_request: 30841
+author:
+type: other
diff --git a/changelogs/unreleased/pedroms-threads-counter-wording.yml b/changelogs/unreleased/pedroms-threads-counter-wording.yml
new file mode 100644
index 00000000000..6098a75609f
--- /dev/null
+++ b/changelogs/unreleased/pedroms-threads-counter-wording.yml
@@ -0,0 +1,5 @@
+---
+title: Change wording of merge request threads counter
+merge_request: 30217
+author:
+type: changed
diff --git a/changelogs/unreleased/ph-215917-escapeRef.yml b/changelogs/unreleased/ph-215917-escapeRef.yml
new file mode 100644
index 00000000000..d7c62250aa2
--- /dev/null
+++ b/changelogs/unreleased/ph-215917-escapeRef.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes branch name not getting escaped correctly on frontend
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-clean-up-public-visiblity-level-check.yml b/changelogs/unreleased/sh-clean-up-public-visiblity-level-check.yml
new file mode 100644
index 00000000000..88f737a49a1
--- /dev/null
+++ b/changelogs/unreleased/sh-clean-up-public-visiblity-level-check.yml
@@ -0,0 +1,5 @@
+---
+title: Fix second 500 error with NULL restricted visibility levels
+merge_request: 30414
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-disable-schema-dump-prod.yml b/changelogs/unreleased/sh-disable-schema-dump-prod.yml
new file mode 100644
index 00000000000..145b1c3d4c6
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-schema-dump-prod.yml
@@ -0,0 +1,5 @@
+---
+title: Disable schema dumping after migrations in production
+merge_request: 30812
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-create-projects-with-prometheus-service.yml b/changelogs/unreleased/sh-fix-create-projects-with-prometheus-service.yml
new file mode 100644
index 00000000000..2105d10e964
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-create-projects-with-prometheus-service.yml
@@ -0,0 +1,5 @@
+---
+title: Fix errors creating project with active Prometheus service template
+merge_request: 30340
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-lfs-badge-feature-flag.yml b/changelogs/unreleased/sh-fix-lfs-badge-feature-flag.yml
new file mode 100644
index 00000000000..37bd675434f
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-lfs-badge-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Add LFS badge feature flag to RefsController#logs_tree
+merge_request: 30442
+author:
+type: fixed
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 66f6942da01..a67674de41a 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -608,7 +608,7 @@ installations from source.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19186) in GitLab 12.6.
-This file lives in `/var/log/gitlab/mail_room/mail_room_json.log` for
+This file lives in `/var/log/gitlab/mailroom/mail_room_json.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/mail_room_json.log` for
installations from source.
@@ -648,7 +648,7 @@ It's stored at:
- `/var/log/gitlab/gitlab-rails/database_load_balancing.log` for Omnibus GitLab packages.
- `/home/git/gitlab/log/database_load_balancing.log` for installations from source.
-## `elasticsearch.log`
+## `elasticsearch.log` **(STARTER ONLY)**
> Introduced in GitLab 12.6.
@@ -718,7 +718,7 @@ Each line contains a JSON line that can be ingested by Elasticsearch. For exampl
}
```
-## `geo.log`
+## `geo.log` **(PREMIUM ONLY)**
> Introduced in 9.5.
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 72dc13c51ae..2919cc59712 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -10092,6 +10092,11 @@ type User {
): SnippetConnection
"""
+ State of the issue
+ """
+ state: String!
+
+ """
Todos of the user
"""
todos(
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 2a99cf388b2..3e39e418909 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -30222,6 +30222,24 @@
"deprecationReason": null
},
{
+ "name": "state",
+ "description": "State of the issue",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "todos",
"description": "Todos of the user",
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 4bf97ede2ed..444e6358d6b 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1567,6 +1567,7 @@ Autogenerated return type of UpdateSnippet
| `avatarUrl` | String | URL of the user's avatar |
| `id` | ID! | ID of the user |
| `name` | String! | Human-readable name of the user |
+| `state` | String! | State of the issue |
| `userPermissions` | UserPermissions! | Permissions for the current user on the resource |
| `username` | String! | Username of the user. Unique within this instance of GitLab |
| `webUrl` | String! | Web URL of the user |
diff --git a/doc/development/fe_guide/style/javascript.md b/doc/development/fe_guide/style/javascript.md
index 2f0ed3c8918..b69a6f1941c 100644
--- a/doc/development/fe_guide/style/javascript.md
+++ b/doc/development/fe_guide/style/javascript.md
@@ -184,6 +184,9 @@ This can help to quickly understand the control flow.
// bad
if (isThingNull) return '';
+if (isThingNull)
+ return '';
+
// good
if (isThingNull) {
return '';
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 900adb03a03..29cc31b6cb5 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -214,6 +214,11 @@ msgid_plural "%d tags"
msgstr[0] ""
msgstr[1] ""
+msgid "%d unresolved thread"
+msgid_plural "%d unresolved threads"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d vulnerability dismissed"
msgid_plural "%d vulnerabilities dismissed"
msgstr[0] ""
@@ -1827,6 +1832,9 @@ msgstr ""
msgid "All security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
msgstr ""
+msgid "All threads resolved"
+msgstr ""
+
msgid "All users"
msgstr ""
@@ -13174,7 +13182,7 @@ msgstr ""
msgid "Metrics|For grouping similar metrics"
msgstr ""
-msgid "Metrics|Go back"
+msgid "Metrics|Go back (Esc)"
msgstr ""
msgid "Metrics|Invalid time range, please verify."
@@ -23434,6 +23442,9 @@ msgstr ""
msgid "VulnerabilityStatusTypes|Resolved"
msgstr ""
+msgid "Vulnerability|%{scannerName} (version %{scannerVersion})"
+msgstr ""
+
msgid "Vulnerability|Class"
msgstr ""
@@ -23467,7 +23478,10 @@ msgstr ""
msgid "Vulnerability|Project"
msgstr ""
-msgid "Vulnerability|Report Type"
+msgid "Vulnerability|Scanner Provider"
+msgstr ""
+
+msgid "Vulnerability|Scanner Type"
msgstr ""
msgid "Vulnerability|Severity"
@@ -25699,11 +25713,6 @@ msgstr ""
msgid "this document"
msgstr ""
-msgid "thread resolved"
-msgid_plural "threads resolved"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "to help your contributors communicate effectively!"
msgstr ""
diff --git a/package.json b/package.json
index f74e9e32f71..490b844c6ae 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"@gitlab/svgs": "1.121.0",
"@gitlab/ui": "13.6.1",
"@gitlab/visual-review-tools": "1.6.1",
+ "@rails/actioncable": "^6.0.2-2",
"@sentry/browser": "^5.10.2",
"@sourcegraph/code-host-integration": "0.0.37",
"@toast-ui/editor": "^2.0.1",
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index b8a5a4036a5..0e30df518d7 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -43,7 +43,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
context 'single thread' do
it 'shows text with how many threads' do
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
@@ -60,7 +60,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -77,7 +77,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -89,7 +89,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
@@ -162,7 +162,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
end
end
@@ -174,7 +174,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
expect(page).not_to have_selector('.line-resolve-btn.is-active')
end
end
@@ -189,7 +189,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
end
@@ -203,7 +203,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -218,7 +218,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -275,7 +275,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
expect(page).to have_content('Last updated')
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
@@ -292,7 +292,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
end
end
end
@@ -305,7 +305,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
it 'shows text with how many threads' do
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/2 threads resolved')
+ expect(page).to have_content('2 unresolved threads')
end
end
@@ -313,7 +313,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
click_button('Resolve thread', match: :first)
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/2 threads resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
@@ -323,7 +323,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('2/2 threads resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -336,7 +336,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('2/2 threads resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -392,7 +392,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
context 'changes tab' do
it 'shows text with how many threads' do
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
@@ -408,7 +408,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -423,7 +423,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -435,7 +435,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
@@ -449,7 +449,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -466,7 +466,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
end
@@ -489,7 +489,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
@@ -519,7 +519,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('1/1 thread resolved')
+ expect(page).to have_content('All threads resolved')
expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
@@ -538,7 +538,7 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
page.within '.line-resolve-all-container' do
- expect(page).to have_content('0/1 thread resolved')
+ expect(page).to have_content('1 unresolved thread')
end
end
end
@@ -550,17 +550,17 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
it 'shows resolved icon' do
- expect(page).to have_content '1/1 thread resolved'
+ expect(page).to have_content 'All threads resolved'
click_button 'Toggle thread'
expect(page).to have_selector('.line-resolve-btn.is-active')
end
it 'does not allow user to click resolve button' do
- expect(page).to have_selector('.line-resolve-btn.is-disabled')
+ expect(page).to have_selector('.line-resolve-btn.is-active')
click_button 'Toggle thread'
- expect(page).to have_selector('.line-resolve-btn.is-disabled')
+ expect(page).to have_selector('.line-resolve-btn.is-active')
end
end
end
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 6ac5248759b..24883e9055e 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -1,5 +1,6 @@
import { shallowMount, mount } from '@vue/test-utils';
import Tracking from '~/tracking';
+import { ESC_KEY, ESC_KEY_IE11 } from '~/lib/utils/keys';
import { GlModal, GlDropdownItem, GlDeprecatedButton } from '@gitlab/ui';
import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
@@ -248,6 +249,8 @@ describe('Dashboard', () => {
let group;
let panel;
+ const mockKeyup = key => window.dispatchEvent(new KeyboardEvent('keyup', { key }));
+
const MockPanel = {
template: `<div><slot name="topLeft"/></div>`,
};
@@ -265,6 +268,9 @@ describe('Dashboard', () => {
group,
panel,
});
+
+ jest.spyOn(store, 'dispatch');
+
return wrapper.vm.$nextTick();
});
@@ -289,17 +295,30 @@ describe('Dashboard', () => {
});
it('restores full dashboard by clicking `back`', () => {
- const backBtn = wrapper.find({ ref: 'goBackBtn' });
- expect(backBtn.exists()).toBe(true);
-
- jest.spyOn(store, 'dispatch');
- backBtn.vm.$emit('click');
+ wrapper.find({ ref: 'goBackBtn' }).vm.$emit('click');
expect(store.dispatch).toHaveBeenCalledWith(
'monitoringDashboard/clearExpandedPanel',
undefined,
);
});
+
+ it('restores dashboard from full screen by typing the Escape key', () => {
+ mockKeyup(ESC_KEY);
+ expect(store.dispatch).toHaveBeenCalledWith(
+ `monitoringDashboard/clearExpandedPanel`,
+ undefined,
+ );
+ });
+
+ it('restores dashboard from full screen by typing the Escape key on IE11', () => {
+ mockKeyup(ESC_KEY_IE11);
+
+ expect(store.dispatch).toHaveBeenCalledWith(
+ `monitoringDashboard/clearExpandedPanel`,
+ undefined,
+ );
+ });
});
});
diff --git a/spec/frontend/notes/components/discussion_counter_spec.js b/spec/frontend/notes/components/discussion_counter_spec.js
index 77603c16f82..04535aa17c5 100644
--- a/spec/frontend/notes/components/discussion_counter_spec.js
+++ b/spec/frontend/notes/components/discussion_counter_spec.js
@@ -75,15 +75,14 @@ describe('DiscussionCounter component', () => {
});
it.each`
- title | resolved | isActive | icon | groupLength
- ${'not allResolved'} | ${false} | ${false} | ${'check-circle'} | ${3}
- ${'allResolved'} | ${true} | ${true} | ${'check-circle-filled'} | ${1}
- `('renders correctly if $title', ({ resolved, isActive, icon, groupLength }) => {
+ title | resolved | isActive | groupLength
+ ${'not allResolved'} | ${false} | ${false} | ${3}
+ ${'allResolved'} | ${true} | ${true} | ${1}
+ `('renders correctly if $title', ({ resolved, isActive, groupLength }) => {
updateStore({ resolvable: true, resolved });
wrapper = shallowMount(DiscussionCounter, { store, localVue });
expect(wrapper.find(`.is-active`).exists()).toBe(isActive);
- expect(wrapper.find({ name: icon }).exists()).toBe(true);
expect(wrapper.findAll('[role="group"').length).toBe(groupLength);
});
});
diff --git a/spec/frontend/sidebar/assignees_realtime_spec.js b/spec/frontend/sidebar/assignees_realtime_spec.js
new file mode 100644
index 00000000000..d6a6ca18fe8
--- /dev/null
+++ b/spec/frontend/sidebar/assignees_realtime_spec.js
@@ -0,0 +1,100 @@
+import { shallowMount } from '@vue/test-utils';
+import ActionCable from '@rails/actioncable';
+import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
+import SidebarMediator from '~/sidebar/sidebar_mediator';
+import Mock from './mock_data';
+import query from '~/issuable_sidebar/queries/issue_sidebar.query.graphql';
+
+jest.mock('@rails/actioncable', () => {
+ const mockConsumer = { subscriptions: { create: jest.fn() } };
+ return {
+ createConsumer: jest.fn().mockReturnValue(mockConsumer),
+ };
+});
+
+describe('Assignees Realtime', () => {
+ let wrapper;
+ let mediator;
+
+ const createComponent = () => {
+ wrapper = shallowMount(AssigneesRealtime, {
+ propsData: {
+ issuableIid: '1',
+ mediator,
+ projectPath: 'path/to/project',
+ },
+ mocks: {
+ $apollo: {
+ query,
+ queries: {
+ project: {
+ refetch: jest.fn(),
+ },
+ },
+ },
+ },
+ });
+ };
+
+ beforeEach(() => {
+ mediator = new SidebarMediator(Mock.mediator);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ SidebarMediator.singleton = null;
+ });
+
+ describe('when handleFetchResult is called from smart query', () => {
+ it('sets assignees to the store', () => {
+ const data = {
+ project: {
+ issue: {
+ assignees: {
+ nodes: [{ id: 'gid://gitlab/Environments/123', avatarUrl: 'url' }],
+ },
+ },
+ },
+ };
+ const expected = [{ id: 123, avatar_url: 'url', avatarUrl: 'url' }];
+ createComponent();
+
+ wrapper.vm.handleFetchResult({ data });
+
+ expect(mediator.store.assignees).toEqual(expected);
+ });
+ });
+
+ describe('when mounted', () => {
+ it('calls create subscription', () => {
+ const cable = ActionCable.createConsumer();
+
+ createComponent();
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(cable.subscriptions.create).toHaveBeenCalledTimes(1);
+ expect(cable.subscriptions.create).toHaveBeenCalledWith(
+ {
+ channel: 'IssuesChannel',
+ iid: wrapper.props('issuableIid'),
+ project_path: wrapper.props('projectPath'),
+ },
+ { received: wrapper.vm.received },
+ );
+ });
+ });
+ });
+
+ describe('when subscription is recieved', () => {
+ it('refetches the GraphQL project query', () => {
+ createComponent();
+
+ wrapper.vm.received({ event: 'updated' });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(wrapper.vm.$apollo.queries.project.refetch).toHaveBeenCalledTimes(1);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/sidebar_assignees_spec.js b/spec/frontend/sidebar/sidebar_assignees_spec.js
index c1876066a21..88e2d2c9514 100644
--- a/spec/frontend/sidebar/sidebar_assignees_spec.js
+++ b/spec/frontend/sidebar/sidebar_assignees_spec.js
@@ -3,6 +3,7 @@ import AxiosMockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import SidebarAssignees from '~/sidebar/components/assignees/sidebar_assignees.vue';
import Assigness from '~/sidebar/components/assignees/assignees.vue';
+import AssigneesRealtime from '~/sidebar/components/assignees/assignees_realtime.vue';
import SidebarMediator from '~/sidebar/sidebar_mediator';
import SidebarService from '~/sidebar/services/sidebar_service';
import SidebarStore from '~/sidebar/stores/sidebar_store';
@@ -12,12 +13,19 @@ describe('sidebar assignees', () => {
let wrapper;
let mediator;
let axiosMock;
-
- const createComponent = () => {
+ const createComponent = (realTimeIssueSidebar = false, props) => {
wrapper = shallowMount(SidebarAssignees, {
propsData: {
+ issuableIid: '1',
mediator,
field: '',
+ projectPath: 'projectPath',
+ ...props,
+ },
+ provide: {
+ glFeatures: {
+ realTimeIssueSidebar,
+ },
},
// Attaching to document is required because this component emits something from the parent element :/
attachToDocument: true,
@@ -30,8 +38,6 @@ describe('sidebar assignees', () => {
jest.spyOn(mediator, 'saveAssignees');
jest.spyOn(mediator, 'assignYourself');
-
- createComponent();
});
afterEach(() => {
@@ -45,6 +51,8 @@ describe('sidebar assignees', () => {
});
it('calls the mediator when saves the assignees', () => {
+ createComponent();
+
expect(mediator.saveAssignees).not.toHaveBeenCalled();
wrapper.vm.saveAssignees();
@@ -53,6 +61,8 @@ describe('sidebar assignees', () => {
});
it('calls the mediator when "assignSelf" method is called', () => {
+ createComponent();
+
expect(mediator.assignYourself).not.toHaveBeenCalled();
expect(mediator.store.assignees.length).toBe(0);
@@ -63,6 +73,8 @@ describe('sidebar assignees', () => {
});
it('hides assignees until fetched', () => {
+ createComponent();
+
expect(wrapper.find(Assigness).exists()).toBe(false);
wrapper.vm.store.isFetching.assignees = false;
@@ -71,4 +83,30 @@ describe('sidebar assignees', () => {
expect(wrapper.find(Assigness).exists()).toBe(true);
});
});
+
+ describe('when realTimeIssueSidebar is turned on', () => {
+ describe('when issuableType is issue', () => {
+ it('finds AssigneesRealtime componeont', () => {
+ createComponent(true);
+
+ expect(wrapper.find(AssigneesRealtime).exists()).toBe(true);
+ });
+ });
+
+ describe('when issuableType is MR', () => {
+ it('does not find AssigneesRealtime componeont', () => {
+ createComponent(true, { issuableType: 'MR' });
+
+ expect(wrapper.find(AssigneesRealtime).exists()).toBe(false);
+ });
+ });
+ });
+
+ describe('when realTimeIssueSidebar is turned off', () => {
+ it('does not find AssigneesRealtime', () => {
+ createComponent(false, { issuableType: 'issue' });
+
+ expect(wrapper.find(AssigneesRealtime).exists()).toBe(false);
+ });
+ });
});
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 8c9fad86a2d..2b0b8844df5 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -9,7 +9,7 @@ describe GitlabSchema.types['User'] do
it 'has the expected fields' do
expected_fields = %w[
- id user_permissions snippets name username avatarUrl webUrl todos
+ id user_permissions snippets name username avatarUrl webUrl todos state
]
expect(described_class).to have_graphql_fields(*expected_fields)
diff --git a/spec/policies/design_management/design_policy_spec.rb b/spec/policies/design_management/design_policy_spec.rb
new file mode 100644
index 00000000000..154a9f5ad6b
--- /dev/null
+++ b/spec/policies/design_management/design_policy_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe DesignManagement::DesignPolicy do
+ include DesignManagementTestHelpers
+
+ include_context 'ProjectPolicy context'
+
+ let(:guest_design_abilities) { %i[read_design] }
+ let(:developer_design_abilities) do
+ %i[create_design destroy_design]
+ end
+ let(:design_abilities) { guest_design_abilities + developer_design_abilities }
+
+ let(:issue) { create(:issue, project: project) }
+ let(:design) { create(:design, issue: issue) }
+
+ subject(:design_policy) { described_class.new(current_user, design) }
+
+ shared_examples_for "design abilities not available" do
+ context "for owners" do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+
+ context "for admins" do
+ let(:current_user) { admin }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+
+ context "for maintainers" do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+
+ context "for developers" do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+
+ context "for reporters" do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+
+ context "for guests" do
+ let(:current_user) { guest }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+
+ context "for anonymous users" do
+ let(:current_user) { nil }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+ end
+
+ shared_examples_for "design abilities available for members" do
+ context "for owners" do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(*design_abilities) }
+ end
+
+ context "for admins" do
+ let(:current_user) { admin }
+
+ it { is_expected.to be_allowed(*design_abilities) }
+ end
+
+ context "for maintainers" do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(*design_abilities) }
+ end
+
+ context "for developers" do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_allowed(*design_abilities) }
+ end
+
+ context "for reporters" do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_allowed(*guest_design_abilities) }
+ it { is_expected.to be_disallowed(*developer_design_abilities) }
+ end
+ end
+
+ shared_examples_for "read-only design abilities" do
+ it { is_expected.to be_allowed(:read_design) }
+ it { is_expected.to be_disallowed(:create_design, :destroy_design) }
+ end
+
+ context "when DesignManagement is not enabled" do
+ before do
+ enable_design_management(false)
+ end
+
+ it_behaves_like "design abilities not available"
+ end
+
+ context "when the feature is available" do
+ before do
+ enable_design_management
+ end
+
+ it_behaves_like "design abilities available for members"
+
+ context "for guests in private projects" do
+ let(:project) { create(:project, :private) }
+ let(:current_user) { guest }
+
+ it { is_expected.to be_allowed(*guest_design_abilities) }
+ it { is_expected.to be_disallowed(*developer_design_abilities) }
+ end
+
+ context "for anonymous users in public projects" do
+ let(:current_user) { nil }
+
+ it { is_expected.to be_allowed(*guest_design_abilities) }
+ it { is_expected.to be_disallowed(*developer_design_abilities) }
+ end
+
+ context "when the issue is confidential" do
+ let(:issue) { create(:issue, :confidential, project: project) }
+
+ it_behaves_like "design abilities available for members"
+
+ context "for guests" do
+ let(:current_user) { guest }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+
+ context "for anonymous users" do
+ let(:current_user) { nil }
+
+ it { is_expected.to be_disallowed(*design_abilities) }
+ end
+ end
+
+ context "when the issue is locked" do
+ let(:current_user) { owner }
+ let(:issue) { create(:issue, :locked, project: project) }
+
+ it_behaves_like "read-only design abilities"
+ end
+
+ context "when the issue has moved" do
+ let(:current_user) { owner }
+ let(:issue) { create(:issue, project: project, moved_to: create(:issue)) }
+
+ it_behaves_like "read-only design abilities"
+ end
+
+ context "when the project is archived" do
+ let(:current_user) { owner }
+
+ before do
+ project.update!(archived: true)
+ end
+
+ it_behaves_like "read-only design abilities"
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 7e6a576f322..3712e805c11 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -985,6 +985,11 @@
consola "^2.10.1"
node-fetch "^2.6.0"
+"@rails/actioncable@^6.0.2-2":
+ version "6.0.2-2"
+ resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-6.0.2-2.tgz#237907f8111707950381387c273b19ac25958408"
+ integrity sha512-0sKStf8hnberH1TKup10PJ92JT2dVqf3gf+OT4lJ7DiYSBEuDcvICHxWsyML2oWTpjUhC4kLvUJ3pXL2JJrJuQ==
+
"@sentry/browser@^5.10.2":
version "5.10.2"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.10.2.tgz#0bbb05505c58ea998c833cffec3f922fe4b4fa58"