diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-04 12:09:46 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-04 12:09:46 +0000 |
commit | 72797f4a602d0061636df39df89e11896de2a524 (patch) | |
tree | fbd4397be74910e44aaafda4093ea1e6f499d445 | |
parent | 5b4eca2afd809fbfba3bdcacabe547025ebe7f43 (diff) | |
download | gitlab-ce-72797f4a602d0061636df39df89e11896de2a524.tar.gz |
Add latest changes from gitlab-org/gitlab@master
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" |