diff options
753 files changed, 8795 insertions, 3382 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index a954bb4ff37..d04a10a9127 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,9 +1,9 @@ ---- env: browser: true es6: true extends: - airbnb-base + - prettier - plugin:vue/recommended globals: __webpack_public_path__: true @@ -19,34 +19,31 @@ plugins: - promise settings: html/html-extensions: - - ".html" - - ".html.raw" + - '.html' + - '.html.raw' import/resolver: webpack: - config: "./config/webpack.config.js" + config: './config/webpack.config.js' rules: filenames/match-regex: - error - - "^[a-z0-9_]+$" + - '^[a-z0-9_]+$' import/no-commonjs: error - no-multiple-empty-lines: - - error - - max: 1 promise/catch-or-return: error no-param-reassign: - error - props: true ignorePropertyModificationsFor: - - "acc" # for reduce accumulators - - "accumulator" # for reduce accumulators - - "el" # for DOM elements - - "element" # for DOM elements - - "state" # for Vuex mutations + - 'acc' # for reduce accumulators + - 'accumulator' # for reduce accumulators + - 'el' # for DOM elements + - 'element' # for DOM elements + - 'state' # for Vuex mutations no-underscore-dangle: - error - allow: - - __ - - _links + - __ + - _links no-mixed-operators: off vue/html-self-closing: - error @@ -60,31 +57,7 @@ rules: - error - properties: never ignoreDestructuring: true - ## Conflicting rules with prettier: - space-before-function-paren: off - curly: off - arrow-parens: off - function-paren-newline: off - object-curly-newline: off - padded-blocks: off - # Disabled for now, to make the eslint 3 -> eslint 5 update smoother - ## Indent rule. We are using the old for now: https://eslint.org/docs/user-guide/migrating-to-4.0.0#indent-rewrite - indent: off - indent-legacy: - - error - - 2 - - SwitchCase: 1 - VariableDeclarator: 1 - outerIIFEBody: 1 - FunctionDeclaration: - parameters: 1 - body: 1 - FunctionExpression: - parameters: 1 - body: 1 # Disabled for now, to make the airbnb-base 12.1.0 -> 13.1.0 update smoother - operator-linebreak: off - implicit-arrow-linebreak: off no-else-return: - error - allowElseIf: true diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2a299ea79ef..c652b6c75e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -606,7 +606,7 @@ static-analysis: docs lint: <<: *dedicated-runner <<: *except-qa - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint" stage: test cache: {} dependencies: [] @@ -614,8 +614,8 @@ docs lint: script: - scripts/lint-doc.sh - scripts/lint-changelog-yaml - - mv doc/ /nanoc/content/ - - cd /nanoc + - mv doc/ /tmp/gitlab-docs/content/ + - cd /tmp/gitlab-docs # Build HTML from Markdown - bundle exec nanoc # Check the internal links diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 96a157648fb..a4b773b15a9 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -16,3 +16,5 @@ db/ @abrandl @NikolayS /ee/lib/gitlab/code_owners/ @reprazent /ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono /lib/gitlab/auth/ldap/ @dblessing @mkozono +/lib/gitlab/ci/templates/ @nolith @zj +/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d4f7615c80e..571df7534cb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -10,24 +10,6 @@ Capybara/CurrentPathExpectation: Enabled: false -# Offense count: 23 -FactoryBot/DynamicAttributeDefinedStatically: - Exclude: - - 'spec/factories/broadcast_messages.rb' - - 'spec/factories/ci/builds.rb' - - 'spec/factories/ci/runners.rb' - - 'spec/factories/clusters/applications/helm.rb' - - 'spec/factories/clusters/platforms/kubernetes.rb' - - 'spec/factories/emails.rb' - - 'spec/factories/gpg_keys.rb' - - 'spec/factories/group_members.rb' - - 'spec/factories/merge_requests.rb' - - 'spec/factories/notes.rb' - - 'spec/factories/oauth_access_grants.rb' - - 'spec/factories/project_members.rb' - - 'spec/factories/todos.rb' - - 'spec/factories/uploads.rb' - # Offense count: 167 # Cop supports --auto-correct. Layout/EmptyLinesAroundArguments: @@ -53,20 +35,6 @@ Layout/IndentArray: Layout/IndentHash: Enabled: false -# Offense count: 11 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment. -Layout/SpaceBeforeFirstArg: - Exclude: - - 'config/routes/project.rb' - - 'db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb' - - 'features/steps/project/source/browse_files.rb' - - 'features/steps/project/source/markdown_render.rb' - - 'lib/api/runners.rb' - - 'spec/features/search/user_uses_search_filters_spec.rb' - - 'spec/routing/project_routing_spec.rb' - - 'spec/services/system_note_service_spec.rb' - # Offense count: 93 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. @@ -74,15 +42,6 @@ Layout/SpaceBeforeFirstArg: Layout/SpaceInLambdaLiteral: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBrackets: space, no_space -Layout/SpaceInsideArrayLiteralBrackets: - Exclude: - - 'spec/lib/gitlab/import_export/relation_factory_spec.rb' - # Offense count: 327 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. @@ -96,14 +55,6 @@ Layout/SpaceInsideBlockBraces: Layout/SpaceInsideParens: Enabled: false -# Offense count: 14 -# Cop supports --auto-correct. -Layout/SpaceInsidePercentLiteralDelimiters: - Exclude: - - 'lib/gitlab/git_access.rb' - - 'lib/gitlab/health_checks/fs_shards_check.rb' - - 'spec/lib/gitlab/health_checks/fs_shards_check_spec.rb' - # Offense count: 26 Lint/DuplicateMethods: Exclude: @@ -135,31 +86,11 @@ Lint/InterpolationCheck: Lint/MissingCopEnableDirective: Enabled: false -# Offense count: 2 -Lint/NestedPercentLiteral: - Exclude: - - 'lib/gitlab/git/repository.rb' - - 'spec/support/shared_examples/email_format_shared_examples.rb' - # Offense count: 1 Lint/ReturnInVoidContext: Exclude: - 'app/models/project.rb' -# Offense count: 1 -# Configuration parameters: IgnoreImplicitReferences. -Lint/ShadowedArgument: - Exclude: - - 'lib/gitlab/database/sha_attribute.rb' - -# Offense count: 3 -# Cop supports --auto-correct. -Lint/UnneededRequireStatement: - Exclude: - - 'db/post_migrate/20161221153951_rename_reserved_project_names.rb' - - 'db/post_migrate/20170313133418_rename_more_reserved_project_names.rb' - - 'lib/declarative_policy.rb' - # Offense count: 9 Lint/UriEscapeUnescape: Exclude: @@ -199,16 +130,6 @@ Naming/HeredocDelimiterCase: Naming/HeredocDelimiterNaming: Enabled: false -# Offense count: 1 -Performance/UnfreezeString: - Exclude: - - 'features/steps/project/commits/commits.rb' - -# Offense count: 1 -# Cop supports --auto-correct. -Performance/UriDefaultParser: - Exclude: - - 'lib/gitlab/url_sanitizer.rb' # Offense count: 3821 # Configuration parameters: Prefixes. diff --git a/CHANGELOG.md b/CHANGELOG.md index be204a76645..dcc2c01931d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.3.3 (2018-10-04) + +- No changes. + ## 11.3.2 (2018-10-03) ### Fixed (4 changes) @@ -275,6 +279,15 @@ entry. - Creates Vue component for artifacts block on job page. +## 11.2.5 (2018-10-05) + +### Security (3 changes) + +- Filter user sensitive data from discussions JSON. !2538 +- Properly filter private references from system notes. +- Markdown API no longer displays confidential title references unless authorized. + + ## 11.2.4 (2018-09-26) ### Security (6 changes) @@ -554,6 +567,15 @@ entry. - Moves help_popover component to a common location. +## 11.1.8 (2018-10-05) + +### Security (3 changes) + +- Filter user sensitive data from discussions JSON. !2539 +- Properly filter private references from system notes. +- Markdown API no longer displays confidential title references unless authorized. + + ## 11.1.7 (2018-09-26) ### Security (6 changes) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81fc46c2b6f..d2d385dff8f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,35 +92,79 @@ Please report suspected security vulnerabilities in private to Please do **NOT** create publicly viewable issues for suspected security vulnerabilities. -## Code of conduct +## Code of Conduct -As contributors and maintainers of this project, we pledge to respect all -people who contribute through reporting issues, posting feature requests, -updating documentation, submitting pull requests or patches, and other -activities. +### Our Pledge -We are committed to making participation in this project a harassment-free -experience for everyone, regardless of level of experience, gender, gender -identity and expression, sexual orientation, disability, personal appearance, -body size, race, ethnicity, age, or religion. +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. -Examples of unacceptable behavior by participants include the use of sexual -language or imagery, derogatory comments or personal attacks, trolling, public -or private harassment, insults, or other unprofessional conduct. +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct. Project maintainers who do not -follow the Code of Conduct may be removed from the project team. +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at conduct@gitlab.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. -This code of conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. +### Attribution -Instances of abusive, harassing, or otherwise unacceptable behavior can be -reported by emailing `contact@gitlab.com`. +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0, -available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/). +[homepage]: https://www.contributor-covenant.org ## Closing policy for issues and merge requests diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 3ba7bd5ba83..b1fa68e5df9 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.123.0 +0.125.0 diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 9084fa2f716..26aaba0e866 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -1.1.0 +1.2.0 @@ -295,6 +295,7 @@ gem 'peek-mysql2', '~> 1.1.0', group: :mysql gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-redis', '~> 1.2.0' +gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch' # Metrics group :metrics do diff --git a/Gemfile.lock b/Gemfile.lock index 301f4132476..f995d92abf0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -301,6 +301,8 @@ GEM mime-types (>= 1.16) posix-spawn (~> 0.3) gitlab-markup (1.6.4) + gitlab-sidekiq-fetcher (0.3.0) + sidekiq (~> 5) gitlab-styles (2.4.1) rubocop (~> 0.54.0) rubocop-gitlab-security (~> 0.1.0) @@ -795,7 +797,7 @@ GEM rubyzip (1.2.2) rufus-scheduler (3.4.0) et-orbi (~> 1.0) - rugged (0.27.4) + rugged (0.27.5) safe_yaml (1.0.4) sanitize (4.6.6) crass (~> 1.0.2) @@ -1031,6 +1033,7 @@ DEPENDENCIES gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) gitlab-markup (~> 1.6.4) + gitlab-sidekiq-fetcher gitlab-styles (~> 2.4) gitlab_omniauth-ldap (~> 2.0.4) gon (~> 6.2) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 1e129bc2b54..52f9b0ccd55 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -304,6 +304,8 @@ GEM mime-types (>= 1.16) posix-spawn (~> 0.3) gitlab-markup (1.6.4) + gitlab-sidekiq-fetcher (0.3.0) + sidekiq (~> 5) gitlab-styles (2.4.1) rubocop (~> 0.54.0) rubocop-gitlab-security (~> 0.1.0) @@ -803,7 +805,7 @@ GEM rubyzip (1.2.2) rufus-scheduler (3.4.0) et-orbi (~> 1.0) - rugged (0.27.4) + rugged (0.27.5) safe_yaml (1.0.4) sanitize (4.6.6) crass (~> 1.0.2) @@ -1040,6 +1042,7 @@ DEPENDENCIES gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) gitlab-markup (~> 1.6.4) + gitlab-sidekiq-fetcher gitlab-styles (~> 2.4) gitlab_omniauth-ldap (~> 2.0.4) gon (~> 6.2) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index cd800d75f7a..3f7a1ef1bfc 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -15,13 +15,11 @@ const Api = { mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes', mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions', groupLabelsPath: '/groups/:namespace_path/-/labels', - templatesPath: '/api/:version/templates/:key', - licensePath: '/api/:version/templates/licenses/:key', - gitignorePath: '/api/:version/templates/gitignores/:key', - gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key', - dockerfilePath: '/api/:version/templates/dockerfiles/:key', issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', + projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key', + projectTemplatesPath: '/api/:version/projects/:id/templates/:type', usersPath: '/api/:version/users.json', + userStatusPath: '/api/:version/user/status', commitPath: '/api/:version/projects/:id/repository/commits', commitPipelinesPath: '/:project_id/commit/:sha/pipelines', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', @@ -195,29 +193,29 @@ const Api = { return axios.get(url); }, - // Return text for a specific license - licenseText(key, data, callback) { - const url = Api.buildUrl(Api.licensePath).replace(':key', key); - return axios - .get(url, { - params: data, - }) - .then(res => callback(res.data)); - }, + projectTemplate(id, type, key, options, callback) { + const url = Api.buildUrl(this.projectTemplatePath) + .replace(':id', encodeURIComponent(id)) + .replace(':type', type) + .replace(':key', encodeURIComponent(key)); - gitignoreText(key, callback) { - const url = Api.buildUrl(Api.gitignorePath).replace(':key', key); - return axios.get(url).then(({ data }) => callback(data)); - }, + return axios.get(url, { params: options }).then(res => { + if (callback) callback(res.data); - gitlabCiYml(key, callback) { - const url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key); - return axios.get(url).then(({ data }) => callback(data)); + return res; + }); }, - dockerfileYml(key, callback) { - const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key); - return axios.get(url).then(({ data }) => callback(data)); + projectTemplates(id, type, params = {}, callback) { + const url = Api.buildUrl(this.projectTemplatesPath) + .replace(':id', encodeURIComponent(id)) + .replace(':type', type); + + return axios.get(url, { params }).then(res => { + if (callback) callback(res.data); + + return res; + }); }, issueTemplate(namespacePath, projectPath, key, type, callback) { @@ -266,10 +264,13 @@ const Api = { }); }, - templates(key, params = {}) { - const url = Api.buildUrl(this.templatesPath).replace(':key', key); + postUserStatus({ emoji, message }) { + const url = Api.buildUrl(this.userStatusPath); - return axios.get(url, { params }); + return axios.put(url, { + emoji, + message, + }); }, buildUrl(url) { diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 5b0c4285339..cace8bb9dba 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -42,10 +42,11 @@ export class AwardsHandler { } bindEvents() { + const $parentEl = this.targetContainerEl ? $(this.targetContainerEl) : $(document); // If the user shows intent let's pre-build the menu this.registerEventListener( 'one', - $(document), + $parentEl, 'mouseenter focus', this.toggleButtonSelector, 'mouseenter focus', @@ -58,7 +59,7 @@ export class AwardsHandler { } }, ); - this.registerEventListener('on', $(document), 'click', this.toggleButtonSelector, e => { + this.registerEventListener('on', $parentEl, 'click', this.toggleButtonSelector, e => { e.stopPropagation(); e.preventDefault(); this.showEmojiMenu($(e.currentTarget)); @@ -76,7 +77,7 @@ export class AwardsHandler { }); const emojiButtonSelector = `.js-awards-block .js-emoji-btn, .${this.menuClass} .js-emoji-btn`; - this.registerEventListener('on', $(document), 'click', emojiButtonSelector, e => { + this.registerEventListener('on', $parentEl, 'click', emojiButtonSelector, e => { e.preventDefault(); const $target = $(e.currentTarget); const $glEmojiElement = $target.find('gl-emoji'); @@ -168,7 +169,8 @@ export class AwardsHandler { </div> `; - document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup); + const targetEl = this.targetContainerEl ? this.targetContainerEl : document.body; + targetEl.insertAdjacentHTML('beforeend', emojiMenuMarkup); this.addRemainingEmojiMenuCategories(); this.setupSearch(); @@ -250,6 +252,12 @@ export class AwardsHandler { } positionMenu($menu, $addBtn) { + if (this.targetContainerEl) { + return $menu.css({ + top: `${$addBtn.outerHeight()}px`, + }); + } + const position = $addBtn.data('position'); // The menu could potentially be off-screen or in a hidden overflow element // So we position the element absolute in the body @@ -424,9 +432,7 @@ export class AwardsHandler { users = origTitle.trim().split(FROM_SENTENCE_REGEX); } users.unshift('You'); - return awardBlock - .attr('title', this.toSentence(users)) - .tooltip('_fixTitle'); + return awardBlock.attr('title', this.toSentence(users)).tooltip('_fixTitle'); } createAwardButtonForVotesBlock(votesBlock, emojiName) { @@ -609,13 +615,11 @@ export class AwardsHandler { let awardsHandlerPromise = null; export default function loadAwardsHandler(reload = false) { if (!awardsHandlerPromise || reload) { - awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then( - Emoji => { - const awardsHandler = new AwardsHandler(Emoji); - awardsHandler.bindEvents(); - return awardsHandler; - }, - ); + awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => { + const awardsHandler = new AwardsHandler(Emoji); + awardsHandler.bindEvents(); + return awardsHandler; + }); } return awardsHandlerPromise; } diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js index 5d7a3bed301..0d7e8a5a3cb 100644 --- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js @@ -1,4 +1,4 @@ -/* eslint-disable object-shorthand, no-unused-vars, no-use-before-define, max-len, no-restricted-syntax, guard-for-in, no-continue */ +/* eslint-disable object-shorthand, no-unused-vars, no-use-before-define, no-restricted-syntax, guard-for-in, no-continue */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js index ff1cbcad145..addacf29f1e 100644 --- a/app/assets/javascripts/blob/file_template_mediator.js +++ b/app/assets/javascripts/blob/file_template_mediator.js @@ -1,4 +1,4 @@ -/* eslint-disable class-methods-use-this */ +import Api from '~/api'; import $ from 'jquery'; import Flash from '../flash'; @@ -9,9 +9,10 @@ import GitignoreSelector from './template_selectors/gitignore_selector'; import LicenseSelector from './template_selectors/license_selector'; export default class FileTemplateMediator { - constructor({ editor, currentAction }) { + constructor({ editor, currentAction, projectId }) { this.editor = editor; this.currentAction = currentAction; + this.projectId = projectId; this.initTemplateSelectors(); this.initTemplateTypeSelector(); @@ -33,15 +34,14 @@ export default class FileTemplateMediator { initTemplateTypeSelector() { this.typeSelector = new FileTemplateTypeSelector({ mediator: this, - dropdownData: this.templateSelectors - .map((templateSelector) => { - const cfg = templateSelector.config; - - return { - name: cfg.name, - key: cfg.key, - }; - }), + dropdownData: this.templateSelectors.map(templateSelector => { + const cfg = templateSelector.config; + + return { + name: cfg.name, + key: cfg.key, + }; + }), }); } @@ -89,7 +89,7 @@ export default class FileTemplateMediator { } listenForPreviewMode() { - this.$navLinks.on('click', 'a', (e) => { + this.$navLinks.on('click', 'a', e => { const urlPieces = e.target.href.split('#'); const hash = urlPieces[1]; if (hash === 'preview') { @@ -105,7 +105,7 @@ export default class FileTemplateMediator { e.preventDefault(); } - this.templateSelectors.forEach((selector) => { + this.templateSelectors.forEach(selector => { if (selector.config.key === item.key) { selector.show(); } else { @@ -126,8 +126,8 @@ export default class FileTemplateMediator { selector.renderLoading(); // in case undo menu is already already there this.destroyUndoMenu(); - this.fetchFileTemplate(selector.config.endpoint, query, data) - .then((file) => { + this.fetchFileTemplate(selector.config.type, query, data) + .then(file => { this.showUndoMenu(); this.setEditorContent(file); this.setFilename(selector.config.name); @@ -138,7 +138,7 @@ export default class FileTemplateMediator { displayMatchedTemplateSelector() { const currentInput = this.getFilename(); - this.templateSelectors.forEach((selector) => { + this.templateSelectors.forEach(selector => { const match = selector.config.pattern.test(currentInput); if (match) { @@ -149,15 +149,11 @@ export default class FileTemplateMediator { }); } - fetchFileTemplate(apiCall, query, data) { - return new Promise((resolve) => { + fetchFileTemplate(type, query, data = {}) { + return new Promise(resolve => { const resolveFile = file => resolve(file); - if (!data) { - apiCall(query, resolveFile); - } else { - apiCall(query, data, resolveFile); - } + Api.projectTemplate(this.projectId, type, query, data, resolveFile); }); } diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index 9dfdb06007d..9db1fa70ffb 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -66,9 +66,6 @@ export default class TemplateSelector { // be added by all subclasses. } - // To be implemented on the extending class - // e.g. Api.gitlabCiYml(query.name, file => this.setEditorContent(file)); - setEditorContent(file, { skipFocus } = {}) { if (!file) return; diff --git a/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js index 9c41e429c8d..43f7aead8b9 100644 --- a/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js +++ b/app/assets/javascripts/blob/template_selectors/ci_yaml_selector.js @@ -1,5 +1,3 @@ -import Api from '../../api'; - import FileTemplateSelector from '../file_template_selector'; export default class BlobCiYamlSelector extends FileTemplateSelector { @@ -9,7 +7,7 @@ export default class BlobCiYamlSelector extends FileTemplateSelector { key: 'gitlab-ci-yaml', name: '.gitlab-ci.yml', pattern: /(.gitlab-ci.yml)/, - endpoint: Api.gitlabCiYml, + type: 'gitlab_ci_ymls', dropdown: '.js-gitlab-ci-yml-selector', wrapper: '.js-gitlab-ci-yml-selector-wrap', }; diff --git a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js index 45fb614fe00..4718b642617 100644 --- a/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js +++ b/app/assets/javascripts/blob/template_selectors/dockerfile_selector.js @@ -1,5 +1,3 @@ -import Api from '../../api'; - import FileTemplateSelector from '../file_template_selector'; export default class DockerfileSelector extends FileTemplateSelector { @@ -9,7 +7,7 @@ export default class DockerfileSelector extends FileTemplateSelector { key: 'dockerfile', name: 'Dockerfile', pattern: /(Dockerfile)/, - endpoint: Api.dockerfileYml, + type: 'dockerfiles', dropdown: '.js-dockerfile-selector', wrapper: '.js-dockerfile-selector-wrap', }; diff --git a/app/assets/javascripts/blob/template_selectors/gitignore_selector.js b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js index a894953cc86..a8067ec5c84 100644 --- a/app/assets/javascripts/blob/template_selectors/gitignore_selector.js +++ b/app/assets/javascripts/blob/template_selectors/gitignore_selector.js @@ -1,5 +1,3 @@ -import Api from '../../api'; - import FileTemplateSelector from '../file_template_selector'; export default class BlobGitignoreSelector extends FileTemplateSelector { @@ -9,7 +7,7 @@ export default class BlobGitignoreSelector extends FileTemplateSelector { key: 'gitignore', name: '.gitignore', pattern: /(.gitignore)/, - endpoint: Api.gitignoreText, + type: 'gitignores', dropdown: '.js-gitignore-selector', wrapper: '.js-gitignore-selector-wrap', }; diff --git a/app/assets/javascripts/blob/template_selectors/license_selector.js b/app/assets/javascripts/blob/template_selectors/license_selector.js index b7c4da0f62e..ac1fe95eee5 100644 --- a/app/assets/javascripts/blob/template_selectors/license_selector.js +++ b/app/assets/javascripts/blob/template_selectors/license_selector.js @@ -1,5 +1,3 @@ -import Api from '../../api'; - import FileTemplateSelector from '../file_template_selector'; export default class BlobLicenseSelector extends FileTemplateSelector { @@ -9,7 +7,7 @@ export default class BlobLicenseSelector extends FileTemplateSelector { key: 'license', name: 'LICENSE', pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i, - endpoint: Api.licenseText, + type: 'licenses', dropdown: '.js-license-selector', wrapper: '.js-license-selector-wrap', }; diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index a603d89b84a..4e4598870fa 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -15,8 +15,9 @@ export default () => { const assetsPath = editBlobForm.data('assetsPrefix'); const blobLanguage = editBlobForm.data('blobLanguage'); const currentAction = $('.js-file-title').data('currentAction'); + const projectId = editBlobForm.data('project-id'); - new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage, currentAction); + new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage, currentAction, projectId); new NewCommitForm(editBlobForm); } diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 82a3d494b67..ec2b130ab7d 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -7,11 +7,11 @@ import { __ } from '~/locale'; import TemplateSelectorMediator from '../blob/file_template_mediator'; export default class EditBlob { - constructor(assetsPath, aceMode, currentAction) { + constructor(assetsPath, aceMode, currentAction, projectId) { this.configureAceEditor(aceMode, assetsPath); this.initModePanesAndLinks(); this.initSoftWrap(); - this.initFileSelectors(currentAction); + this.initFileSelectors(currentAction, projectId); } configureAceEditor(aceMode, assetsPath) { @@ -30,10 +30,11 @@ export default class EditBlob { } } - initFileSelectors(currentAction) { + initFileSelectors(currentAction, projectId) { this.fileTemplateMediator = new TemplateSelectorMediator({ currentAction, editor: this.editor, + projectId, }); } @@ -60,14 +61,15 @@ export default class EditBlob { if (paneId === '#preview') { this.$toggleButton.hide(); - axios.post(currentLink.data('previewUrl'), { - content: this.editor.getValue(), - }) - .then(({ data }) => { - currentPane.empty().append(data); - currentPane.renderGFM(); - }) - .catch(() => createFlash(__('An error occurred previewing the blob'))); + axios + .post(currentLink.data('previewUrl'), { + content: this.editor.getValue(), + }) + .then(({ data }) => { + currentPane.empty().append(data); + currentPane.renderGFM(); + }) + .catch(() => createFlash(__('An error occurred previewing the blob'))); } this.$toggleButton.show(); diff --git a/app/assets/javascripts/boards/components/board.js b/app/assets/javascripts/boards/components/board.js index 9ad451fa375..1fc7a29f785 100644 --- a/app/assets/javascripts/boards/components/board.js +++ b/app/assets/javascripts/boards/components/board.js @@ -1,5 +1,3 @@ -/* eslint-disable comma-dangle */ - import Sortable from 'sortablejs'; import Vue from 'vue'; import { n__ } from '~/locale'; diff --git a/app/assets/javascripts/boards/components/board_delete.js b/app/assets/javascripts/boards/components/board_delete.js index c5945e8098d..240d0911a31 100644 --- a/app/assets/javascripts/boards/components/board_delete.js +++ b/app/assets/javascripts/boards/components/board_delete.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, no-alert */ +/* eslint-disable no-alert */ import $ from 'jquery'; import Vue from 'vue'; diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 109e60cbde2..df7efd3fa5c 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, no-new */ +/* eslint-disable no-new */ import $ from 'jquery'; import Vue from 'vue'; diff --git a/app/assets/javascripts/boards/models/issue.js b/app/assets/javascripts/boards/models/issue.js index c7cfb72067c..609659bdf93 100644 --- a/app/assets/javascripts/boards/models/issue.js +++ b/app/assets/javascripts/boards/models/issue.js @@ -1,4 +1,4 @@ -/* eslint-disable no-unused-vars, comma-dangle */ +/* eslint-disable no-unused-vars */ /* global ListLabel */ /* global ListMilestone */ /* global ListAssignee */ diff --git a/app/assets/javascripts/boards/models/list.js b/app/assets/javascripts/boards/models/list.js index d416b76f0f4..58e423fbd44 100644 --- a/app/assets/javascripts/boards/models/list.js +++ b/app/assets/javascripts/boards/models/list.js @@ -1,4 +1,4 @@ -/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len */ +/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign */ /* global ListIssue */ import { __ } from '~/locale'; diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 957114cf420..bd181807e1f 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, no-shadow */ +/* eslint-disable no-shadow */ /* global List */ import $ from 'jquery'; diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index ebf76af5966..65e7cee7039 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -1,6 +1,6 @@ import Visibility from 'visibilityjs'; import Vue from 'vue'; -import initDismissableCallout from '~/dismissable_callout'; +import PersistentUserCallout from '../persistent_user_callout'; import { s__, sprintf } from '../locale'; import Flash from '../flash'; import Poll from '../lib/utils/poll'; @@ -62,7 +62,7 @@ export default class Clusters { this.showTokenButton = document.querySelector('.js-show-cluster-token'); this.tokenField = document.querySelector('.js-cluster-token'); - initDismissableCallout('.js-cluster-security-warning'); + Clusters.initDismissableCallout(); initSettingsPanels(); setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area')); this.initApplications(); @@ -105,6 +105,12 @@ export default class Clusters { }); } + static initDismissableCallout() { + const callout = document.querySelector('.js-cluster-security-warning'); + + if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new + } + addListeners() { if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken); eventHub.$on('installApplication', this.installApplication); diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js index 789c8360124..e32d507d1f7 100644 --- a/app/assets/javascripts/clusters/clusters_index.js +++ b/app/assets/javascripts/clusters/clusters_index.js @@ -1,14 +1,15 @@ import createFlash from '~/flash'; import { __ } from '~/locale'; import setupToggleButtons from '~/toggle_buttons'; -import initDismissableCallout from '~/dismissable_callout'; +import PersistentUserCallout from '../persistent_user_callout'; import ClustersService from './services/clusters_service'; export default () => { const clusterList = document.querySelector('.js-clusters-list'); - initDismissableCallout('.gcp-signup-offer'); + const callout = document.querySelector('.gcp-signup-offer'); + if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new // The empty state won't have a clusterList if (clusterList) { diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 410580b4c25..30d9b656fec 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, no-var, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, max-len */ +/* eslint-disable func-names, no-var, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, one-var, no-unused-vars, no-return-assign, no-unused-expressions, no-sequences */ import $ from 'jquery'; diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js index a252036d657..852d71f4e84 100644 --- a/app/assets/javascripts/compare_autocomplete.js +++ b/app/assets/javascripts/compare_autocomplete.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, one-var, no-var, one-var-declaration-per-line, object-shorthand, no-else-return, max-len */ +/* eslint-disable func-names, one-var, no-var, object-shorthand, no-else-return */ import $ from 'jquery'; import { __ } from './locale'; diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js index ed24d1775f4..87621761500 100644 --- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, quotes, no-lonely-if, max-len */ +/* eslint-disable object-shorthand, func-names, no-else-return, no-lonely-if */ /* global CommentsStore */ import $ from 'jquery'; diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 2b893e35b6d..2b78bb58735 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, no-lonely-if, no-continue, brace-style, max-len, quotes */ +/* eslint-disable object-shorthand, func-names, no-else-return, guard-for-in, no-restricted-syntax, no-lonely-if, no-continue */ /* global CommentsStore */ import $ from 'jquery'; diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js b/app/assets/javascripts/diff_notes/components/resolve_count.js index e2683e09f40..eb539c6b348 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_count.js +++ b/app/assets/javascripts/diff_notes/components/resolve_count.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, func-names */ +/* eslint-disable object-shorthand, func-names */ /* global CommentsStore */ import Vue from 'vue'; diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js b/app/assets/javascripts/diff_notes/mixins/discussion.js index ef35b589e58..7589f9dd6e0 100644 --- a/app/assets/javascripts/diff_notes/mixins/discussion.js +++ b/app/assets/javascripts/diff_notes/mixins/discussion.js @@ -1,4 +1,4 @@ -/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, */ +/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, */ const DiscussionMixins = { computed: { diff --git a/app/assets/javascripts/diff_notes/stores/comments.js b/app/assets/javascripts/diff_notes/stores/comments.js index d7da7d974f3..d012cd02d10 100644 --- a/app/assets/javascripts/diff_notes/stores/comments.js +++ b/app/assets/javascripts/diff_notes/stores/comments.js @@ -1,4 +1,4 @@ -/* eslint-disable object-shorthand, func-names, camelcase, no-restricted-syntax, guard-for-in, comma-dangle, max-len */ +/* eslint-disable object-shorthand, func-names, camelcase, no-restricted-syntax, guard-for-in */ /* global DiscussionModel */ import Vue from 'vue'; diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index e60c53338fe..edca45f22f9 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -127,7 +127,6 @@ export default { 'startRenderDiffsQueue', 'assignDiscussionsToDiff', ]), - fetchData() { this.fetchDiffFiles() .then(() => { diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index 5758588e82e..993206b2e73 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import CIIcon from '~/vue_shared/components/ci_icon.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; /** * CommitItem @@ -29,6 +30,7 @@ export default { ClipboardButton, CIIcon, TimeAgoTooltip, + CommitPipelineStatus, }, props: { commit: { @@ -102,6 +104,14 @@ export default { ></pre> </div> <div class="commit-actions flex-row d-none d-sm-flex"> + <div + v-if="commit.signatureHtml" + v-html="commit.signatureHtml" + ></div> + <commit-pipeline-status + v-if="commit.pipelineStatusPath" + :endpoint="commit.pipelineStatusPath" + /> <div class="commit-sha-group"> <div class="label label-monospace" diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue index 02d5be1821b..fb5556e3cd7 100644 --- a/app/assets/javascripts/diffs/components/diff_content.vue +++ b/app/assets/javascripts/diffs/components/diff_content.vue @@ -28,7 +28,7 @@ export default { return diffModes[diffModeKey] || diffModes.replaced; }, isTextFile() { - return this.diffFile.text; + return this.diffFile.viewer.name === 'text'; }, }, }; diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 4ae588042e4..a482a2b82c0 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -25,7 +25,7 @@ export const getReversePosition = linePosition => { return LINE_POSITION_RIGHT; }; -export function getNoteFormData(params) { +export function getFormData(params) { const { note, noteableType, @@ -70,9 +70,15 @@ export function getNoteFormData(params) { }, }; + return postData; +} + +export function getNoteFormData(params) { + const data = getFormData(params); + return { - endpoint: noteableData.create_note_path, - data: postData, + endpoint: params.noteableData.create_note_path, + data, }; } @@ -244,6 +250,7 @@ export function getDiffPositionByLineCode(diffFiles) { oldLine, newLine, lineCode, + positionType: 'text', }; } }); @@ -259,8 +266,8 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD const { lineCode, ...diffPositionCopy } = diffPosition; if (discussion.original_position && discussion.position) { - const originalRefs = convertObjectPropsToCamelCase(discussion.original_position.formatter); - const refs = convertObjectPropsToCamelCase(discussion.position.formatter); + const originalRefs = convertObjectPropsToCamelCase(discussion.original_position); + const refs = convertObjectPropsToCamelCase(discussion.position); return _.isEqual(refs, diffPositionCopy) || _.isEqual(originalRefs, diffPositionCopy); } diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js deleted file mode 100644 index 5185b019376..00000000000 --- a/app/assets/javascripts/dismissable_callout.js +++ /dev/null @@ -1,27 +0,0 @@ -import $ from 'jquery'; -import axios from '~/lib/utils/axios_utils'; -import { __ } from '~/locale'; -import Flash from '~/flash'; - -export default function initDismissableCallout(alertSelector) { - const alertEl = document.querySelector(alertSelector); - if (!alertEl) { - return; - } - - const closeButtonEl = alertEl.getElementsByClassName('close')[0]; - const { dismissEndpoint, featureId } = closeButtonEl.dataset; - - closeButtonEl.addEventListener('click', () => { - axios - .post(dismissEndpoint, { - feature_name: featureId, - }) - .then(() => { - $(alertEl).alert('close'); - }) - .catch(() => { - Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.')); - }); - }); -} diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index ff50e75dbda..c16931521a7 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -453,7 +453,9 @@ export default { class="gl-responsive-table-row" role="row"> <div - class="table-section section-wrap section-15" + v-tooltip + :title="model.name" + class="table-section section-wrap section-15 text-truncate" role="gridcell" > <div @@ -467,9 +469,8 @@ export default { v-if="!model.isFolder" class="environment-name table-mobile-content"> <a - v-tooltip + class="qa-environment-link" :href="environmentPath" - :title="model.name" > {{ model.name }} </a> diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 73b2cd0b2c7..95636a9ccdd 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -15,6 +15,7 @@ export const defaultAutocompleteConfig = { epics: true, milestones: true, labels: true, + snippets: true, }; class GfmAutoComplete { @@ -50,6 +51,7 @@ class GfmAutoComplete { if (this.enableMap.milestones) this.setupMilestones($input); if (this.enableMap.mergeRequests) this.setupMergeRequests($input); if (this.enableMap.labels) this.setupLabels($input); + if (this.enableMap.snippets) this.setupSnippets($input); // We don't instantiate the quick actions autocomplete for note and issue/MR edit forms $input.filter('[data-supports-quick-actions="true"]').atwho({ @@ -360,6 +362,39 @@ class GfmAutoComplete { }); } + setupSnippets($input) { + $input.atwho({ + at: '$', + alias: 'snippets', + searchKey: 'search', + displayTpl(value) { + let tmpl = GfmAutoComplete.Loading.template; + if (value.title != null) { + tmpl = GfmAutoComplete.Issues.template; + } + return tmpl; + }, + data: GfmAutoComplete.defaultLoadingData, + // eslint-disable-next-line no-template-curly-in-string + insertTpl: '${atwho-at}${id}', + callbacks: { + ...this.getDefaultCallbacks(), + beforeSave(snippets) { + return $.map(snippets, (m) => { + if (m.title == null) { + return m; + } + return { + id: m.id, + title: sanitize(m.title), + search: `${m.id} ${m.title}`, + }; + }); + }, + }, + }); + } + getDefaultCallbacks() { const fetchData = this.fetchData.bind(this); @@ -470,7 +505,7 @@ class GfmAutoComplete { // The below is taken from At.js source // Tweaked to commands to start without a space only if char before is a non-word character // https://github.com/ichord/At.js - const atSymbolsWithBar = Object.keys(controllers).join('|'); + const atSymbolsWithBar = Object.keys(controllers).join('|').replace(/[$]/, '\\$&'); const atSymbolsWithoutBar = Object.keys(controllers).join(''); const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop(); const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&'); @@ -497,6 +532,7 @@ GfmAutoComplete.atTypeMap = { '~': 'labels', '%': 'milestones', '/': 'commands', + $: 'snippets', }; // Emoji @@ -519,7 +555,7 @@ GfmAutoComplete.Labels = { // eslint-disable-next-line no-template-curly-in-string template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>', }; -// Issues and MergeRequests +// Issues, MergeRequests and Snippets GfmAutoComplete.Issues = { // eslint-disable-next-line no-template-curly-in-string template: '<li><small>${id}</small> ${title}</li>', diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index c3959ef3e9e..a8ac2f510a4 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, one-var-declaration-per-line, max-len, vars-on-top, wrap-iife, no-unused-vars, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */ +/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-unused-vars, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */ /* global fuzzaldrinPlus */ import $ from 'jquery'; diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index 4ae3a714bee..175d0b8498b 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -1,5 +1,9 @@ import $ from 'jquery'; +import Vue from 'vue'; +import Translate from '~/vue_shared/translate'; import { highCountTrim } from '~/lib/utils/text_utility'; +import SetStatusModalTrigger from './set_status_modal/set_status_modal_trigger.vue'; +import SetStatusModalWrapper from './set_status_modal/set_status_modal_wrapper.vue'; /** * Updates todo counter when todos are toggled. @@ -17,3 +21,54 @@ export default function initTodoToggle() { $todoPendingCount.toggleClass('hidden', parsedCount === 0); }); } + +document.addEventListener('DOMContentLoaded', () => { + const setStatusModalTriggerEl = document.querySelector('.js-set-status-modal-trigger'); + const setStatusModalWrapperEl = document.querySelector('.js-set-status-modal-wrapper'); + + if (setStatusModalTriggerEl || setStatusModalWrapperEl) { + Vue.use(Translate); + + // eslint-disable-next-line no-new + new Vue({ + el: setStatusModalTriggerEl, + data() { + const { hasStatus } = this.$options.el.dataset; + + return { + hasStatus: hasStatus === 'true', + }; + }, + render(createElement) { + return createElement(SetStatusModalTrigger, { + props: { + hasStatus: this.hasStatus, + }, + }); + }, + }); + + // eslint-disable-next-line no-new + new Vue({ + el: setStatusModalWrapperEl, + data() { + const { currentEmoji, currentMessage } = this.$options.el.dataset; + + return { + currentEmoji, + currentMessage, + }; + }, + render(createElement) { + const { currentEmoji, currentMessage } = this; + + return createElement(SetStatusModalWrapper, { + props: { + currentEmoji, + currentMessage, + }, + }); + }, + }); + } +}); diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js index 462ca45db9b..d97a950a8b2 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/actions.js +++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js @@ -30,9 +30,9 @@ export const setLastCommitMessage = ({ rootState, commit }, data) => { const currentProject = rootState.projects[rootState.currentProjectId]; const commitStats = data.stats ? sprintf(__('with %{additions} additions, %{deletions} deletions.'), { - additions: data.stats.additions, // eslint-disable-line indent-legacy - deletions: data.stats.deletions, // eslint-disable-line indent-legacy - }) // eslint-disable-line indent-legacy + additions: data.stats.additions, + deletions: data.stats.deletions, + }) : ''; const commitMsg = sprintf( __('Your changes have been committed. Commit %{commitId} %{commitStats}'), diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js index cc9f6c8638c..b7090e09daf 100644 --- a/app/assets/javascripts/ide/stores/modules/file_templates/actions.js +++ b/app/assets/javascripts/ide/stores/modules/file_templates/actions.js @@ -23,12 +23,12 @@ export const receiveTemplateTypesError = ({ commit, dispatch }) => { export const receiveTemplateTypesSuccess = ({ commit }, templates) => commit(types.RECEIVE_TEMPLATE_TYPES_SUCCESS, templates); -export const fetchTemplateTypes = ({ dispatch, state }, page = 1) => { +export const fetchTemplateTypes = ({ dispatch, state, rootState }, page = 1) => { if (!Object.keys(state.selectedTemplateType).length) return Promise.reject(); dispatch('requestTemplateTypes'); - return Api.templates(state.selectedTemplateType.key, { page }) + return Api.projectTemplates(rootState.currentProjectId, state.selectedTemplateType.key, { page }) .then(({ data, headers }) => { const nextPage = parseInt(normalizeHeaders(headers)['X-NEXT-PAGE'], 10); @@ -74,12 +74,16 @@ export const receiveTemplateError = ({ dispatch }, template) => { ); }; -export const fetchTemplate = ({ dispatch, state }, template) => { +export const fetchTemplate = ({ dispatch, state, rootState }, template) => { if (template.content) { return dispatch('setFileTemplate', template); } - return Api.templates(`${state.selectedTemplateType.key}/${template.key || template.name}`) + return Api.projectTemplate( + rootState.currentProjectId, + state.selectedTemplateType.key, + template.key || template.name, + ) .then(({ data }) => { dispatch('setFileTemplate', data); }) diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index 9e848699163..9848bcc2e64 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, quotes, consistent-return, func-names, array-callback-return, prefer-arrow-callback, max-len, no-unused-vars */ +/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback, no-unused-vars */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 8c225cd7d91..4b4e9aa48ab 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, one-var, one-var-declaration-per-line, no-unused-vars, consistent-return, quotes, max-len */ +/* eslint-disable no-var, one-var, no-unused-vars, consistent-return */ import $ from 'jquery'; import axios from './lib/utils/axios_utils'; diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index bcf8686afcc..c3d39082714 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -66,7 +66,7 @@ <button :class="{ disabled: formState.updateLoading || !isSubmitEnabled }" :disabled="formState.updateLoading || !isSubmitEnabled" - class="btn btn-success float-left" + class="btn btn-success float-left qa-save-button" type="submit" @click.prevent="updateIssuable"> Save changes @@ -86,7 +86,7 @@ v-if="shouldShowDeleteButton" :class="{ disabled: deleteLoading }" :disabled="deleteLoading" - class="btn btn-danger float-right append-right-default" + class="btn btn-danger float-right append-right-default qa-delete-button" type="button" @click="deleteIssuable"> Delete diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue index 97acc5ba385..1a78c59d715 100644 --- a/app/assets/javascripts/issue_show/components/fields/description.vue +++ b/app/assets/javascripts/issue_show/components/fields/description.vue @@ -61,7 +61,8 @@ ref="textarea" slot="textarea" v-model="formState.description" - class="note-textarea js-gfm-input js-autosize markdown-area" + class="note-textarea js-gfm-input js-autosize markdown-area + qa-description-textarea" data-supports-quick-actions="false" aria-label="Description" placeholder="Write a comment or drag your files here…" diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue index 7d1526a64b4..b7f2b1a6050 100644 --- a/app/assets/javascripts/issue_show/components/fields/title.vue +++ b/app/assets/javascripts/issue_show/components/fields/title.vue @@ -22,7 +22,7 @@ <input id="issuable-title" v-model="formState.title" - class="form-control" + class="form-control qa-title-input" type="text" placeholder="Title" aria-label="Title" diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue index cf99e9a9cd8..ed26e53ac0e 100644 --- a/app/assets/javascripts/issue_show/components/title.vue +++ b/app/assets/javascripts/issue_show/components/title.vue @@ -79,7 +79,8 @@ export default { v-if="showInlineEditButton && canUpdate" v-tooltip type="button" - class="btn btn-default btn-edit btn-svg js-issuable-edit" + class="btn btn-default btn-edit btn-svg js-issuable-edit + qa-edit-button" title="Edit title and description" data-placement="bottom" data-container="body" diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue index 39a4ff159e2..4b1788a1c16 100644 --- a/app/assets/javascripts/jobs/components/commit_block.vue +++ b/app/assets/javascripts/jobs/components/commit_block.vue @@ -46,7 +46,7 @@ v-if="mergeRequest" :href="mergeRequest.path" class="js-link-commit link-commit" - >{{ mergeRequest.iid }}</a> + >!{{ mergeRequest.iid }}</a> </p> <p class="build-light-text append-bottom-0"> diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue index ff45a5b05f8..ff500eddddb 100644 --- a/app/assets/javascripts/jobs/components/empty_state.vue +++ b/app/assets/javascripts/jobs/components/empty_state.vue @@ -27,7 +27,7 @@ value === null || (Object.prototype.hasOwnProperty.call(value, 'path') && Object.prototype.hasOwnProperty.call(value, 'method') && - Object.prototype.hasOwnProperty.call(value, 'title')) + Object.prototype.hasOwnProperty.call(value, 'button_title')) ); }, }, @@ -67,7 +67,7 @@ :data-method="action.method" class="js-job-empty-state-action btn btn-primary" > - {{ action.title }} + {{ action.button_title }} </a> </div> </div> diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index bac8bd71d64..047e55866ce 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -2,6 +2,7 @@ import { mapGetters, mapState } from 'vuex'; import CiHeader from '~/vue_shared/components/header_ci_component.vue'; import Callout from '~/vue_shared/components/callout.vue'; + import EmptyState from './empty_state.vue'; import EnvironmentsBlock from './environments_block.vue'; import ErasedBlock from './erased_block.vue'; import StuckBlock from './stuck_block.vue'; @@ -11,6 +12,7 @@ components: { CiHeader, Callout, + EmptyState, EnvironmentsBlock, ErasedBlock, StuckBlock, @@ -31,6 +33,8 @@ 'jobHasStarted', 'hasEnvironment', 'isJobStuck', + 'hasTrace', + 'emptyStateIllustration', ]), }, }; @@ -77,12 +81,14 @@ <environments-block v-if="hasEnvironment" + class="js-job-environment" :deployment-status="job.deployment_status" :icon-status="job.status" /> <erased-block v-if="job.erased" + class="js-job-erased" :user="job.erased_by" :erased-at="job.erased_at" /> @@ -91,6 +97,15 @@ <!-- EO job log --> <!--empty state --> + <empty-state + v-if="!hasTrace" + class="js-job-empty-state" + :illustration-path="emptyStateIllustration.image" + :illustration-size-class="emptyStateIllustration.size" + :title="emptyStateIllustration.title" + :content="emptyStateIllustration.content" + :action="job.status.action" + /> <!-- EO empty state --> <!-- EO Body Section --> diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js index 62d154ff584..afe5f88b292 100644 --- a/app/assets/javascripts/jobs/store/getters.js +++ b/app/assets/javascripts/jobs/store/getters.js @@ -30,13 +30,24 @@ export const jobHasStarted = state => !(state.job.started === false); export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status); /** + * Checks if it the job has trace. + * Used to check if it should render the job log or the empty state + * @returns {Boolean} + */ +export const hasTrace = state => state.job.has_trace || state.job.status.group === 'running'; + +export const emptyStateIllustration = state => + (state.job && state.job.status && state.job.status.illustration) || {}; + +/** * When the job is pending and there are no available runners * we need to render the stuck block; * * @returns {Boolean} */ export const isJobStuck = state => - state.job.status.group === 'pending' && state.job.runners && state.job.runners.available === false; + state.job.status.group === 'pending' && + (!_.isEmpty(state.job.runners) && state.job.runners.available === false); // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index c10b1a2b233..2c995c5902f 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -1,4 +1,4 @@ -/* eslint-disable class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, func-names, max-len */ +/* eslint-disable class-methods-use-this, no-underscore-dangle, no-param-reassign, no-unused-vars, func-names */ import $ from 'jquery'; import Sortable from 'sortablejs'; diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 1c7bca78df3..68ae1ca6842 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -1,4 +1,4 @@ -/* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, dot-notation, no-empty */ +/* eslint-disable no-useless-return, func-names, no-var, no-underscore-dangle, prefer-arrow-callback, one-var, no-unused-vars, prefer-template, no-new, consistent-return, object-shorthand, no-shadow, no-param-reassign, vars-on-top, no-lonely-if, no-else-return, dot-notation, no-empty */ /* global Issuable */ /* global ListLabel */ diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index abfcf1eaf3f..833dbefd3dc 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -373,6 +373,7 @@ window.gl.utils = { /** * Formats milliseconds as timestamp (e.g. 01:02:03). + * This takes durations longer than a day into account (e.g. two days would be 48:00:00). * * @param milliseconds * @returns {string} diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js index 305ad3e5e26..f4eb652a41a 100644 --- a/app/assets/javascripts/lib/utils/notify.js +++ b/app/assets/javascripts/lib/utils/notify.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, max-len */ +/* eslint-disable func-names, no-var, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand */ function notificationGranted(message, opts, onclick) { var notification; diff --git a/app/assets/javascripts/lib/utils/text_markdown.js b/app/assets/javascripts/lib/utils/text_markdown.js index f7429601afa..df20785b178 100644 --- a/app/assets/javascripts/lib/utils/text_markdown.js +++ b/app/assets/javascripts/lib/utils/text_markdown.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, no-param-reassign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, max-len, consistent-return, no-unused-vars, max-len */ +/* eslint-disable func-names, no-var, no-param-reassign, one-var, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, consistent-return, no-unused-vars */ import $ from 'jquery'; import { insertText } from '~/lib/utils/common_utils'; diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index 291655235d5..d58fd63bb33 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, no-underscore-dangle, no-param-reassign, prefer-template, quotes, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, no-else-return, max-len */ +/* eslint-disable func-names, no-var, no-underscore-dangle, no-param-reassign, prefer-template, consistent-return, one-var, no-else-return */ import $ from 'jquery'; diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js index 81950515ab4..c1832d034ef 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, quote-props, no-useless-computed-key, object-shorthand, no-param-reassign, max-len */ +/* eslint-disable no-useless-computed-key, object-shorthand, no-param-reassign */ /* global ace */ import Vue from 'vue'; diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js index 1501296ac4f..2cd70247bc6 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue, max-len */ +/* eslint-disable object-shorthand, no-param-reassign, camelcase, no-nested-ternary, no-continue */ import $ from 'jquery'; import Vue from 'vue'; diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index 7bf2c56dd5d..9b6d7d1772f 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, comma-dangle, max-len, prefer-arrow-callback */ +/* eslint-disable func-names, no-var, no-underscore-dangle, one-var, consistent-return, prefer-arrow-callback */ import $ from 'jquery'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 78f56ab57ff..03f3bb42193 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -18,7 +18,6 @@ import syntaxHighlight from './syntax_highlight'; import Notes from './notes'; import { polyfillSticky } from './lib/utils/sticky'; -/* eslint-disable max-len */ // MergeRequestTabs // // Handles persisting and restoring the current tab selection and lazily-loading @@ -62,7 +61,6 @@ import { polyfillSticky } from './lib/utils/sticky'; // </div> // </div> // -/* eslint-enable max-len */ // Store the `location` object, allowing for easier stubbing in tests let { location } = window; diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 640a4c8260f..67c2d7909a2 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -1,4 +1,4 @@ -/* eslint-disable max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ +/* eslint-disable one-var, no-unused-vars, object-shorthand, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ /* global Issuable */ /* global ListMilestone */ diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index ff44f51b8f8..3cccaf72ed7 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -82,6 +82,7 @@ export default { showFlag: false, showFlagContent: false, timeSeries: [], + graphDrawData: {}, realPixelRatio: 1, seriesUnderMouse: [], }; @@ -180,12 +181,12 @@ export default { }); }, renderAxesPaths() { - this.timeSeries = createTimeSeries( + ({ timeSeries: this.timeSeries, graphDrawData: this.graphDrawData } = createTimeSeries( this.graphData.queries, this.graphWidth, this.graphHeight, this.graphHeightOffset, - ); + )); if (_.findWhere(this.timeSeries, { renderCanary: true })) { this.timeSeries = this.timeSeries.map(series => ({ ...series, renderCanary: true })); @@ -288,6 +289,10 @@ export default { :viewBox="innerViewBox" class="graph-data" > + <slot + name="additionalSvgContent" + :graphDrawData="graphDrawData" + /> <graph-path v-for="(path, index) in timeSeries" :key="index" diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index eff0d7325cd..d5971730e31 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -30,7 +30,7 @@ const defaultColorOrder = ['blue', 'orange', 'red', 'green', 'purple']; const defaultStyleOrder = ['solid', 'dashed', 'dotted']; -function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle) { +function queryTimeSeries(query, graphDrawData, lineStyle) { let usedColors = []; let renderCanary = false; const timeSeriesParsed = []; @@ -64,7 +64,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom // but we need a regularly-spaced set of time/value pairs // this gives us a complete range of one minute intervals // offset the same amount as the original data - const [minX, maxX] = xDom; + const [minX, maxX] = graphDrawData.xDom; const offset = d3.timeMinute(minX) - Number(minX); const datesWithoutGaps = d3.timeSecond.every(60) .range(d3.timeMinute.offset(minX, -1), maxX) @@ -84,31 +84,6 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom renderCanary = true; } - const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]); - - const timeSeriesScaleY = d3.scaleLinear().range([graphHeight - graphHeightOffset, 0]); - - timeSeriesScaleX.domain(xDom); - timeSeriesScaleX.ticks(d3.timeMinute, 60); - timeSeriesScaleY.domain(yDom); - - const defined = d => !Number.isNaN(d.value) && d.value != null; - - const lineFunction = d3 - .line() - .defined(defined) - .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate - .x(d => timeSeriesScaleX(d.time)) - .y(d => timeSeriesScaleY(d.value)); - - const areaFunction = d3 - .area() - .defined(defined) - .curve(d3.curveLinear) - .x(d => timeSeriesScaleX(d.time)) - .y0(graphHeight - graphHeightOffset) - .y1(d => timeSeriesScaleY(d.value)); - const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]]; const seriesCustomizationData = query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel }); @@ -144,10 +119,10 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom })); timeSeriesParsed.push({ - linePath: lineFunction(values), - areaPath: areaFunction(values), - timeSeriesScaleX, - timeSeriesScaleY, + linePath: graphDrawData.lineFunction(values), + areaPath: graphDrawData.areaBelowLine(values), + timeSeriesScaleX: graphDrawData.timeSeriesScaleX, + timeSeriesScaleY: graphDrawData.timeSeriesScaleY, values: timeSeries.values, max: maximumValue, average: accum / timeSeries.values.length, @@ -164,7 +139,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom return timeSeriesParsed; } -export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { +function xyDomain(queries) { const allValues = queries.reduce( (allQueryResults, query) => allQueryResults.concat( @@ -176,10 +151,70 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph const xDom = d3.extent(allValues, d => d.time); const yDom = [0, d3.max(allValues.map(d => d.value))]; - return queries.reduce((series, query, index) => { + return { + xDom, + yDom, + }; +} + +export function generateGraphDrawData(queries, graphWidth, graphHeight, graphHeightOffset) { + const { xDom, yDom } = xyDomain(queries); + + const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]); + const timeSeriesScaleY = d3.scaleLinear().range([graphHeight - graphHeightOffset, 0]); + + timeSeriesScaleX.domain(xDom); + timeSeriesScaleX.ticks(d3.timeMinute, 60); + timeSeriesScaleY.domain(yDom); + + const defined = d => !Number.isNaN(d.value) && d.value != null; + + const lineFunction = d3 + .line() + .defined(defined) + .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate + .x(d => timeSeriesScaleX(d.time)) + .y(d => timeSeriesScaleY(d.value)); + + const areaBelowLine = d3 + .area() + .defined(defined) + .curve(d3.curveLinear) + .x(d => timeSeriesScaleX(d.time)) + .y0(graphHeight - graphHeightOffset) + .y1(d => timeSeriesScaleY(d.value)); + + const areaAboveLine = d3 + .area() + .defined(defined) + .curve(d3.curveLinear) + .x(d => timeSeriesScaleX(d.time)) + .y0(0) + .y1(d => timeSeriesScaleY(d.value)); + + return { + lineFunction, + areaBelowLine, + areaAboveLine, + xDom, + yDom, + timeSeriesScaleX, + timeSeriesScaleY, + }; +} + +export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { + const graphDrawData = generateGraphDrawData(queries, graphWidth, graphHeight, graphHeightOffset); + + const timeSeries = queries.reduce((series, query, index) => { const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length]; return series.concat( - queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle), + queryTimeSeries(query, graphDrawData, lineStyle), ); }, []); + + return { + timeSeries, + graphDrawData, + }; } diff --git a/app/assets/javascripts/mr_notes/stores/index.js b/app/assets/javascripts/mr_notes/stores/index.js index 446eb477efc..c4225c8ec08 100644 --- a/app/assets/javascripts/mr_notes/stores/index.js +++ b/app/assets/javascripts/mr_notes/stores/index.js @@ -6,10 +6,13 @@ import mrPageModule from './modules'; Vue.use(Vuex); -export default new Vuex.Store({ - modules: { - page: mrPageModule, - notes: notesModule(), - diffs: diffsModule(), - }, -}); +export const createStore = () => + new Vuex.Store({ + modules: { + page: mrPageModule, + notes: notesModule(), + diffs: diffsModule(), + }, + }); + +export default createStore(); diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 17370edeb0c..ec4c0910e92 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */ +/* eslint-disable func-names, object-shorthand, no-else-return, prefer-template, prefer-arrow-callback */ import $ from 'jquery'; import Api from './api'; diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index 94da1be4066..ad7136adb8c 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */ +/* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase */ import $ from 'jquery'; import { __ } from '../locale'; diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js index 205d9766656..8f6ea9e61c1 100644 --- a/app/assets/javascripts/new_branch_form.js +++ b/app/assets/javascripts/new_branch_form.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, one-var, max-len, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len */ +/* eslint-disable func-names, no-var, one-var, consistent-return, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return */ import $ from 'jquery'; import RefSelectDropdown from './ref_select_dropdown'; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index f301f093ef4..1369b5820d5 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,5 +1,5 @@ -/* eslint-disable no-restricted-properties, func-names, no-var, wrap-iife, camelcase, -no-unused-expressions, max-len, one-var, one-var-declaration-per-line, default-case, +/* eslint-disable no-restricted-properties, func-names, no-var, camelcase, +no-unused-expressions, one-var, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, vars-on-top, no-unused-vars, no-shadow, no-useless-escape, class-methods-use-this */ diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 7735133c470..b980e43b898 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -7,7 +7,11 @@ import { __, sprintf } from '~/locale'; import Flash from '../../flash'; import Autosave from '../../autosave'; import TaskList from '../../task_list'; -import { capitalizeFirstCharacter, convertToCamelCase, splitCamelCase } from '../../lib/utils/text_utility'; +import { + capitalizeFirstCharacter, + convertToCamelCase, + splitCamelCase, +} from '../../lib/utils/text_utility'; import * as constants from '../constants'; import eventHub from '../event_hub'; import issueWarning from '../../vue_shared/components/issue/issue_warning.vue'; @@ -122,7 +126,9 @@ export default { return this.getNoteableData.create_note_path; }, issuableTypeTitle() { - return this.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE ? 'merge request' : 'issue'; + return this.noteableType === constants.MERGE_REQUEST_NOTEABLE_TYPE + ? 'merge request' + : 'issue'; }, }, watch: { @@ -359,7 +365,7 @@ Please check your network connection and try again.`; :disabled="isSubmitting" name="note[note]" class="note-textarea js-vue-comment-form js-note-text -js-gfm-input js-autosize markdown-area js-vue-textarea" +js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input" data-supports-quick-actions="true" aria-label="Description" placeholder="Write a comment or drag your files here…" @@ -374,7 +380,8 @@ js-gfm-input js-autosize markdown-area js-vue-textarea" append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"> <button :disabled="isSubmitButtonDisabled" - class="btn btn-success comment-btn js-comment-button js-comment-submit-button" + class="btn btn-create comment-btn js-comment-button js-comment-submit-button + qa-comment-button" type="submit" @click.prevent="handleSave()"> {{ __(commentButtonTitle) }} diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index beb53da0e6d..e075f94b82b 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -7,10 +7,14 @@ import editSvg from 'icons/_icon_pencil.svg'; import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg'; import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg'; import ellipsisSvg from 'icons/_ellipsis_v.svg'; +import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; export default { name: 'NoteActions', + components: { + Icon, + }, directives: { tooltip, }, @@ -20,7 +24,7 @@ export default { required: true, }, noteId: { - type: String, + type: [String, Number], required: true, }, noteUrl: { @@ -35,7 +39,8 @@ export default { }, reportAbusePath: { type: String, - required: true, + required: false, + default: null, }, canEdit: { type: Boolean, @@ -84,6 +89,9 @@ export default { shouldShowActionsDropdown() { return this.currentUserId && (this.canEdit || this.canReportAsAbuse); }, + showDeleteAction() { + return this.canDelete && !this.canReportAsAbuse && !this.noteUrl; + }, isAuthoredByCurrentUser() { return this.authorId === this.currentUserId; }, @@ -201,7 +209,26 @@ export default { </button> </div> <div - v-if="shouldShowActionsDropdown" + v-if="showDeleteAction" + class="note-actions-item" + > + <button + v-tooltip + type="button" + title="Delete comment" + class="note-action-button js-note-delete btn btn-transparent" + data-container="body" + data-placement="bottom" + @click="onDelete" + > + <icon + name="remove" + class="link-highlight" + /> + </button> + </div> + <div + v-else-if="shouldShowActionsDropdown" class="dropdown more-actions note-actions-item"> <button v-tooltip diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index 6f4a0709825..cf4c35de42c 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -109,7 +109,7 @@ export default { class="note_edited_ago" /> <note-awards-list - v-if="note.award_emoji.length" + v-if="note.award_emoji && note.award_emoji.length" :note-id="note.id" :note-author-id="note.author.id" :awards="note.award_emoji" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index 2d47d55f33c..33998394a69 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -20,7 +20,7 @@ export default { default: '', }, noteId: { - type: String, + type: [String, Number], required: false, default: '', }, diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue index d669d12a39b..7b6e7b72caf 100644 --- a/app/assets/javascripts/notes/components/note_header.vue +++ b/app/assets/javascripts/notes/components/note_header.vue @@ -14,7 +14,8 @@ export default { }, createdAt: { type: String, - required: true, + required: false, + default: null, }, actionText: { type: String, @@ -22,8 +23,9 @@ export default { default: '', }, noteId: { - type: String, - required: true, + type: [String, Number], + required: false, + default: null, }, includeToggle: { type: Boolean, @@ -96,18 +98,22 @@ export default { <span class="system-note-message"> <slot></slot> </span> - <span class="system-note-separator"> - · - </span> - <a - :href="noteTimestampLink" - class="note-timestamp system-note-separator" - @click="updateTargetNoteHash"> - <time-ago-tooltip - :time="createdAt" - tooltip-placement="bottom" - /> - </a> + <template + v-if="createdAt" + > + <span class="system-note-separator"> + · + </span> + <a + :href="noteTimestampLink" + class="note-timestamp system-note-separator" + @click="updateTargetNoteHash"> + <time-ago-tooltip + :time="createdAt" + tooltip-placement="bottom" + /> + </a> + </template> <i class="fa fa-spinner fa-spin editing-spinner" aria-label="Comment is being updated" diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index e9218723149..c5fdfa1d47c 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -379,7 +379,7 @@ Please check your network connection and try again.`; role="group"> <button type="button" - class="btn btn-default mr-2" + class="btn btn-default" @click="resolveHandler()" > <i diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 7579fc852c6..f391ed848a4 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -52,7 +52,7 @@ export default { return this.note.resolvable && !!this.getUserData.id; }, canReportAsAbuse() { - return this.note.report_abuse_path && this.author.id !== this.getUserData.id; + return !!this.note.report_abuse_path && this.author.id !== this.getUserData.id; }, noteAnchorId() { return `note_${this.note.id}`; @@ -81,13 +81,17 @@ export default { ...mapActions(['deleteNote', 'updateNote', 'toggleResolveNote', 'scrollToNoteIfNeeded']), editHandler() { this.isEditing = true; + this.$emit('handleEdit'); }, deleteHandler() { + const typeOfComment = this.note.isDraft ? 'pending comment' : 'comment'; // eslint-disable-next-line no-alert - if (window.confirm('Are you sure you want to delete this comment?')) { + if (window.confirm(`Are you sure you want to delete this ${typeOfComment}?`)) { this.isDeleting = true; this.$emit('handleDeleteNote', this.note); + if (this.note.isDraft) return; + this.deleteNote(this.note) .then(() => { this.isDeleting = false; @@ -98,7 +102,20 @@ export default { }); } }, + updateSuccess() { + this.isEditing = false; + this.isRequesting = false; + this.oldContent = null; + $(this.$refs.noteBody.$el).renderGFM(); + this.$refs.noteBody.resetAutoSave(); + this.$emit('updateSuccess'); + }, formUpdateHandler(noteText, parentElement, callback) { + this.$emit('handleUpdateNote', { + note: this.note, + noteText, + callback: () => this.updateSuccess(), + }); const data = { endpoint: this.note.path, note: { @@ -113,11 +130,7 @@ export default { this.updateNote(data) .then(() => { - this.isEditing = false; - this.isRequesting = false; - this.oldContent = null; - $(this.$refs.noteBody.$el).renderGFM(); - this.$refs.noteBody.resetAutoSave(); + this.updateSuccess(); callback(); }) .catch(() => { @@ -142,6 +155,7 @@ export default { this.oldContent = null; } this.isEditing = false; + this.$emit('cancelForm'); }, recoverNoteContent(noteText) { // we need to do this to prevent noteForm inconsistent content warning diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 320dfa47d5a..7ab7e5a9abb 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -150,11 +150,24 @@ export const toggleIssueLocalState = ({ commit }, newState) => { export const saveNote = ({ commit, dispatch }, noteData) => { // For MR discussuions we need to post as `note[note]` and issue we use `note.note`. - const note = noteData.data['note[note]'] || noteData.data.note.note; + // For batch comments, we use draft_note + const note = noteData.data.draft_note || noteData.data['note[note]'] || noteData.data.note.note; let placeholderText = note; const hasQuickActions = utils.hasQuickActions(placeholderText); const replyId = noteData.data.in_reply_to_discussion_id; - const methodToDispatch = replyId ? 'replyToDiscussion' : 'createNewNote'; + let methodToDispatch; + const postData = Object.assign({}, noteData); + if (postData.isDraft === true) { + methodToDispatch = replyId + ? 'batchComments/addDraftToDiscussion' + : 'batchComments/createNewDraft'; + if (!postData.draft_note && noteData.note) { + postData.draft_note = postData.note; + delete postData.note; + } + } else { + methodToDispatch = replyId ? 'replyToDiscussion' : 'createNewNote'; + } $('.notes-form .flash-container').hide(); // hide previous flash notification commit(types.REMOVE_PLACEHOLDER_NOTES); // remove previous placeholders @@ -180,7 +193,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => { } } - return dispatch(methodToDispatch, noteData).then(res => { + return dispatch(methodToDispatch, postData, { root: true }).then(res => { const { errors } = res; const commandsChanges = res.commands_changes; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index d4babf1fab2..a829149a17e 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -74,6 +74,9 @@ export const allDiscussions = (state, getters) => { return Object.values(resolved).concat(unresolved); }; +export const isDiscussionResolved = (state, getters) => discussionId => + getters.resolvedDiscussionsById[discussionId] !== undefined; + export const allResolvableDiscussions = (state, getters) => getters.allDiscussions.filter(d => !d.individual_note && d.resolvable); @@ -126,8 +129,8 @@ export const unresolvedDiscussionsIdsByDiff = (state, getters) => const filenameComparison = a.diff_file.file_path.localeCompare(b.diff_file.file_path); // Get the line numbers, to compare within the same file - const aLines = [a.position.formatter.new_line, a.position.formatter.old_line]; - const bLines = [b.position.formatter.new_line, b.position.formatter.old_line]; + const aLines = [a.position.new_line, a.position.old_line]; + const bLines = [b.position.new_line, b.position.old_line]; return filenameComparison < 0 || (filenameComparison === 0 && diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index 9aa83ce6269..72f3f70b98f 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -79,10 +79,13 @@ export default class Todos { .then(({ data }) => { this.updateRowState(target); this.updateBadges(data); - }).catch(() => flash(__('Error updating todo status.'))); + }).catch(() => { + this.updateRowState(target, true); + return flash(__('Error updating todo status.')); + }); } - updateRowState(target) { + updateRowState(target, isInactive = false) { const row = target.closest('li'); const restoreBtn = row.querySelector('.js-undo-todo'); const doneBtn = row.querySelector('.js-done-todo'); @@ -91,7 +94,10 @@ export default class Todos { target.removeAttribute('disabled'); target.classList.remove('disabled'); - if (target === doneBtn) { + if (isInactive === true) { + restoreBtn.classList.add('hidden'); + doneBtn.classList.remove('hidden'); + } else if (target === doneBtn) { row.classList.add('done-reversible'); restoreBtn.classList.remove('hidden'); } else if (target === restoreBtn) { diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js index aea7b649c20..c7ce4675573 100644 --- a/app/assets/javascripts/pages/profiles/show/index.js +++ b/app/assets/javascripts/pages/profiles/show/index.js @@ -11,7 +11,7 @@ document.addEventListener('DOMContentLoaded', () => { const statusEmojiField = document.getElementById('js-status-emoji-field'); const statusMessageField = document.getElementById('js-status-message-field'); - const toggleNoEmojiPlaceholder = (isVisible) => { + const toggleNoEmojiPlaceholder = isVisible => { const placeholderElement = document.getElementById('js-no-emoji-placeholder'); placeholderElement.classList.toggle('hidden', !isVisible); }; @@ -69,5 +69,5 @@ document.addEventListener('DOMContentLoaded', () => { } }); }) - .catch(() => createFlash('Failed to load emoji list!')); + .catch(() => createFlash('Failed to load emoji list.')); }); diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js index 6c1788dc160..58bb8c5b0c8 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign */ +/* eslint-disable func-names, no-var, one-var, camelcase, no-param-reassign, prefer-template, no-return-assign */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js index a02ec9e5f00..5f91686347a 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, max-len, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */ +/* eslint-disable func-names, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, no-return-assign, prefer-arrow-callback, prefer-template, no-else-return, no-shadow */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js index d12249bf612..cd0e2bc023c 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */ +/* eslint-disable func-names, object-shorthand, no-var, one-var, camelcase, no-param-reassign, no-return-assign, prefer-arrow-callback, consistent-return, no-unused-vars, no-cond-assign, no-else-return */ import _ from 'underscore'; export default { diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js index 5659e13981a..b0345b4e50d 100644 --- a/app/assets/javascripts/pages/projects/index.js +++ b/app/assets/javascripts/pages/projects/index.js @@ -1,5 +1,5 @@ -import initDismissableCallout from '~/dismissable_callout'; import initGkeDropdowns from '~/projects/gke_cluster_dropdowns'; +import PersistentUserCallout from '../../persistent_user_callout'; import Project from './project'; import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation'; @@ -12,7 +12,9 @@ document.addEventListener('DOMContentLoaded', () => { ]; if (newClusterViews.indexOf(page) > -1) { - initDismissableCallout('.gcp-signup-offer'); + const callout = document.querySelector('.gcp-signup-offer'); + if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new + initGkeDropdowns(); } diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js index 77368c47451..70fbb3f301c 100644 --- a/app/assets/javascripts/pages/projects/network/network.js +++ b/app/assets/javascripts/pages/projects/network/network.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */ +/* eslint-disable func-names, no-var, prefer-template */ import $ from 'jquery'; import BranchGraph from '../../../network/branch_graph'; diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 34a13eb3251..52d66beefc9 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -1,5 +1,4 @@ -/* eslint-disable func-names, no-var, no-return-assign, one-var, - one-var-declaration-per-line, object-shorthand, vars-on-top */ +/* eslint-disable func-names, no-var, no-return-assign, one-var, object-shorthand, vars-on-top */ import $ from 'jquery'; import Cookies from 'js-cookie'; diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 875f6928bed..a16f7e6b77c 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -52,6 +52,21 @@ required: false, default: '', }, + pagesAvailable: { + type: Boolean, + required: false, + default: false, + }, + pagesAccessControlEnabled: { + type: Boolean, + required: false, + default: false, + }, + pagesHelpPath: { + type: String, + required: false, + default: '', + }, }, data() { @@ -64,6 +79,7 @@ buildsAccessLevel: 20, wikiAccessLevel: 20, snippetsAccessLevel: 20, + pagesAccessLevel: 20, containerRegistryEnabled: true, lfsEnabled: true, requestAccessEnabled: true, @@ -90,6 +106,13 @@ ); }, + pagesFeatureAccessLevelOptions() { + if (this.visibilityLevel !== visibilityOptions.PUBLIC) { + return this.featureAccessLevelOptions.concat([[30, 'Everyone']]); + } + return this.featureAccessLevelOptions; + }, + repositoryEnabled() { return this.repositoryAccessLevel > 0; }, @@ -109,6 +132,10 @@ this.buildsAccessLevel = Math.min(10, this.buildsAccessLevel); this.wikiAccessLevel = Math.min(10, this.wikiAccessLevel); this.snippetsAccessLevel = Math.min(10, this.snippetsAccessLevel); + if (this.pagesAccessLevel === 20) { + // When from Internal->Private narrow access for only members + this.pagesAccessLevel = 10; + } this.highlightChanges(); } else if (oldValue === visibilityOptions.PRIVATE) { // if changing away from private, make enabled features more permissive @@ -118,6 +145,7 @@ if (this.buildsAccessLevel > 0) this.buildsAccessLevel = 20; if (this.wikiAccessLevel > 0) this.wikiAccessLevel = 20; if (this.snippetsAccessLevel > 0) this.snippetsAccessLevel = 20; + if (this.pagesAccessLevel === 10) this.pagesAccessLevel = 20; this.highlightChanges(); } }, @@ -323,6 +351,18 @@ name="project[project_feature_attributes][snippets_access_level]" /> </project-setting-row> + <project-setting-row + v-if="pagesAvailable && pagesAccessControlEnabled" + :help-path="pagesHelpPath" + label="Pages" + help-text="Static website for the project." + > + <project-feature-setting + v-model="pagesAccessLevel" + :options="pagesFeatureAccessLevelOptions" + name="project[project_feature_attributes][pages_access_level]" + /> + </project-setting-row> </div> </div> </template> diff --git a/app/assets/javascripts/pages/root/index.js b/app/assets/javascripts/pages/root/index.js new file mode 100644 index 00000000000..09f8185d3b5 --- /dev/null +++ b/app/assets/javascripts/pages/root/index.js @@ -0,0 +1,5 @@ +// if the "projects dashboard" is a user's default dashboard, when they visit the +// instance root index, the dashboard will be served by the root controller instead +// of a dashboard controller. The root index redirects for all other default dashboards. + +import '../dashboard/projects/index'; diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js index 97cf1aeaadc..d621f988d86 100644 --- a/app/assets/javascripts/pages/sessions/new/username_validator.js +++ b/app/assets/javascripts/pages/sessions/new/username_validator.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, consistent-return, class-methods-use-this */ +/* eslint-disable consistent-return, class-methods-use-this */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/pages/snippets/form.js b/app/assets/javascripts/pages/snippets/form.js index f369c7ef9a6..8859557e62d 100644 --- a/app/assets/javascripts/pages/snippets/form.js +++ b/app/assets/javascripts/pages/snippets/form.js @@ -11,6 +11,7 @@ export default () => { epics: false, milestones: false, labels: false, + snippets: false, }); new ZenMode(); // eslint-disable-line no-new }; diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js index 9892a039941..bf592ba7a3c 100644 --- a/app/assets/javascripts/pages/users/activity_calendar.js +++ b/app/assets/javascripts/pages/users/activity_calendar.js @@ -43,7 +43,15 @@ const initColorKey = () => .domain([0, 3]); export default class ActivityCalendar { - constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0, firstDayOfWeek = 0) { + constructor( + container, + activitiesContainer, + timestamps, + calendarActivitiesPath, + utcOffset = 0, + firstDayOfWeek = 0, + monthsAgo = 12, + ) { this.calendarActivitiesPath = calendarActivitiesPath; this.clickDay = this.clickDay.bind(this); this.currentSelectedDate = ''; @@ -66,6 +74,8 @@ export default class ActivityCalendar { ]; this.months = []; this.firstDayOfWeek = firstDayOfWeek; + this.activitiesContainer = activitiesContainer; + this.container = container; // Loop through the timestamps to create a group of objects // The group of objects will be grouped based on the day of the week they are @@ -75,13 +85,13 @@ export default class ActivityCalendar { const today = getSystemDate(utcOffset); today.setHours(0, 0, 0, 0, 0); - const oneYearAgo = new Date(today); - oneYearAgo.setFullYear(today.getFullYear() - 1); + const timeAgo = new Date(today); + timeAgo.setMonth(today.getMonth() - monthsAgo); - const days = getDayDifference(oneYearAgo, today); + const days = getDayDifference(timeAgo, today); for (let i = 0; i <= days; i += 1) { - const date = new Date(oneYearAgo); + const date = new Date(timeAgo); date.setDate(date.getDate() + i); const day = date.getDay(); @@ -280,7 +290,7 @@ export default class ActivityCalendar { this.currentSelectedDate.getDate(), ].join('-'); - $('.user-calendar-activities').html(LOADING_HTML); + $(this.activitiesContainer).html(LOADING_HTML); axios .get(this.calendarActivitiesPath, { @@ -289,11 +299,11 @@ export default class ActivityCalendar { }, responseType: 'text', }) - .then(({ data }) => $('.user-calendar-activities').html(data)) + .then(({ data }) => $(this.activitiesContainer).html(data)) .catch(() => flash(__('An error occurred while retrieving calendar activity'))); } else { this.currentSelectedDate = ''; - $('.user-calendar-activities').html(''); + $(this.activitiesContainer).html(''); } } } diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js new file mode 100644 index 00000000000..0009419cd0c --- /dev/null +++ b/app/assets/javascripts/pages/users/user_overview_block.js @@ -0,0 +1,42 @@ +import axios from '~/lib/utils/axios_utils'; + +export default class UserOverviewBlock { + constructor(options = {}) { + this.container = options.container; + this.url = options.url; + this.limit = options.limit || 20; + this.loadData(); + } + + loadData() { + const loadingEl = document.querySelector(`${this.container} .loading`); + + loadingEl.classList.remove('hide'); + + axios + .get(this.url, { + params: { + limit: this.limit, + }, + }) + .then(({ data }) => this.render(data)) + .catch(() => loadingEl.classList.add('hide')); + } + + render(data) { + const { html, count } = data; + const contentList = document.querySelector(`${this.container} .overview-content-list`); + + contentList.innerHTML += html; + + const loadingEl = document.querySelector(`${this.container} .loading`); + + if (count && count > 0) { + document.querySelector(`${this.container} .js-view-all`).classList.remove('hide'); + } else { + document.querySelector(`${this.container} .nothing-here-block`).classList.add('text-left', 'p-0'); + } + + loadingEl.classList.add('hide'); + } +} diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index a2ca03536f2..23b0348a99f 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -2,9 +2,10 @@ import $ from 'jquery'; import axios from '~/lib/utils/axios_utils'; import Activities from '~/activities'; import { localTimeAgo } from '~/lib/utils/datetime_utility'; -import { __ } from '~/locale'; +import { __, sprintf } from '~/locale'; import flash from '~/flash'; import ActivityCalendar from './activity_calendar'; +import UserOverviewBlock from './user_overview_block'; /** * UserTabs @@ -61,19 +62,28 @@ import ActivityCalendar from './activity_calendar'; * </div> */ -const CALENDAR_TEMPLATE = ` - <div class="clearfix calendar"> - <div class="js-contrib-calendar"></div> - <div class="calendar-hint"> - Summary of issues, merge requests, push events, and comments +const CALENDAR_TEMPLATES = { + activity: ` + <div class="clearfix calendar"> + <div class="js-contrib-calendar"></div> + <div class="calendar-hint bottom-right"></div> </div> - </div> -`; + `, + overview: ` + <div class="clearfix calendar"> + <div class="calendar-hint"></div> + <div class="js-contrib-calendar prepend-top-20"></div> + </div> + `, +}; + +const CALENDAR_PERIOD_6_MONTHS = 6; +const CALENDAR_PERIOD_12_MONTHS = 12; export default class UserTabs { constructor({ defaultAction, action, parentEl }) { this.loaded = {}; - this.defaultAction = defaultAction || 'activity'; + this.defaultAction = defaultAction || 'overview'; this.action = action || this.defaultAction; this.$parentEl = $(parentEl) || $(document); this.windowLocation = window.location; @@ -124,6 +134,8 @@ export default class UserTabs { } if (action === 'activity') { this.loadActivities(); + } else if (action === 'overview') { + this.loadOverviewTab(); } const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; @@ -154,7 +166,40 @@ export default class UserTabs { if (this.loaded.activity) { return; } - const $calendarWrap = this.$parentEl.find('.user-calendar'); + + this.loadActivityCalendar('activity'); + + // eslint-disable-next-line no-new + new Activities(); + + this.loaded.activity = true; + } + + loadOverviewTab() { + if (this.loaded.overview) { + return; + } + + this.loadActivityCalendar('overview'); + + UserTabs.renderMostRecentBlocks('#js-overview .activities-block', 5); + UserTabs.renderMostRecentBlocks('#js-overview .projects-block', 10); + + this.loaded.overview = true; + } + + static renderMostRecentBlocks(container, limit) { + // eslint-disable-next-line no-new + new UserOverviewBlock({ + container, + url: $(`${container} .overview-content-list`).data('href'), + limit, + }); + } + + loadActivityCalendar(action) { + const monthsAgo = action === 'overview' ? CALENDAR_PERIOD_6_MONTHS : CALENDAR_PERIOD_12_MONTHS; + const $calendarWrap = this.$parentEl.find('.tab-pane.active .user-calendar'); const calendarPath = $calendarWrap.data('calendarPath'); const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath'); const utcOffset = $calendarWrap.data('utcOffset'); @@ -166,17 +211,22 @@ export default class UserTabs { axios .get(calendarPath) .then(({ data }) => { - $calendarWrap.html(CALENDAR_TEMPLATE); - $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`); + $calendarWrap.html(CALENDAR_TEMPLATES[action]); + + let calendarHint = ''; + + if (action === 'activity') { + calendarHint = sprintf(__('Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})'), { utcFormatted }); + } else if (action === 'overview') { + calendarHint = __('Issues, merge requests, pushes and comments.'); + } + + $calendarWrap.find('.calendar-hint').text(calendarHint); // eslint-disable-next-line no-new - new ActivityCalendar('.js-contrib-calendar', data, calendarActivitiesPath, utcOffset); + new ActivityCalendar('.tab-pane.active .js-contrib-calendar', '.tab-pane.active .user-calendar-activities', data, calendarActivitiesPath, utcOffset, 0, monthsAgo); }) .catch(() => flash(__('There was an error loading users activity calendar.'))); - - // eslint-disable-next-line no-new - new Activities(); - this.loaded.activity = true; } toggleLoading(status) { diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js new file mode 100644 index 00000000000..1e34e74a152 --- /dev/null +++ b/app/assets/javascripts/persistent_user_callout.js @@ -0,0 +1,34 @@ +import axios from './lib/utils/axios_utils'; +import { __ } from './locale'; +import Flash from './flash'; + +export default class PersistentUserCallout { + constructor(container) { + const { dismissEndpoint, featureId } = container.dataset; + this.container = container; + this.dismissEndpoint = dismissEndpoint; + this.featureId = featureId; + + this.init(); + } + + init() { + const closeButton = this.container.querySelector('.js-close'); + closeButton.addEventListener('click', event => this.dismiss(event)); + } + + dismiss(event) { + event.preventDefault(); + + axios + .post(this.dismissEndpoint, { + feature_name: this.featureId, + }) + .then(() => { + this.container.remove(); + }) + .catch(() => { + Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.')); + }); + } +} diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index f0e845d0773..16e69759091 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -26,8 +26,13 @@ export default { methods: { onClickAction(action) { if (action.scheduled_at) { - const confirmationMessage = sprintf(s__("DelayedJobs|Are you sure you want to run %{jobName} immediately? This job will run automatically after it's timer finishes."), { jobName: action.name }); - // https://gitlab.com/gitlab-org/gitlab-ce/issues/52099 + const confirmationMessage = sprintf( + s__( + "DelayedJobs|Are you sure you want to run %{jobName} immediately? This job will run automatically after it's timer finishes.", + ), + { jobName: action.name }, + ); + // https://gitlab.com/gitlab-org/gitlab-ce/issues/52156 // eslint-disable-next-line no-alert if (!window.confirm(confirmationMessage)) { return; @@ -49,7 +54,7 @@ export default { remainingTime(action) { const remainingMilliseconds = new Date(action.scheduled_at).getTime() - Date.now(); - return formatTime(remainingMilliseconds); + return formatTime(Math.max(0, remainingMilliseconds)); }, }, }; diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue index 0d7324f3fb5..cb14d4400d1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue @@ -35,7 +35,7 @@ export default { }, data() { return { - pipelineId: '', + pipelineId: 0, endpoint: '', cancelingPipeline: null, }; diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 88957554d12..b03438ddba1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -47,7 +47,7 @@ export default { required: true, }, cancelingPipeline: { - type: String, + type: Number, required: false, default: null, }, @@ -63,10 +63,10 @@ export default { if (!this.pipeline || !this.pipeline.details) { return []; } - const { details: pipelineDetails } = this.pipeline; + const { details } = this.pipeline; return [ - ...(pipelineDetails.manual_actions || []), - ...(pipelineDetails.scheduled_actions || []), + ...(details.manual_actions || []), + ...(details.scheduled_actions || []), ]; }, /** diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js index f641b23e519..af134881f31 100644 --- a/app/assets/javascripts/profile/gl_crop.js +++ b/app/assets/javascripts/profile/gl_crop.js @@ -1,4 +1,4 @@ -/* eslint-disable no-useless-escape, max-len, no-var, no-underscore-dangle, func-names, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */ +/* eslint-disable no-useless-escape, no-var, no-underscore-dangle, func-names, no-unused-vars, no-return-assign, object-shorthand, one-var, consistent-return, class-methods-use-this */ import $ from 'jquery'; import 'cropper'; diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 05485e352dc..12cfa7de316 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, prefer-template, no-unused-vars, no-return-assign */ +/* eslint-disable func-names, no-var, consistent-return, one-var, no-cond-assign, prefer-template, no-unused-vars, no-return-assign */ import $ from 'jquery'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 6f3b32f8eea..eaaeda8b339 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */ +/* eslint-disable func-names, no-var, object-shorthand, one-var, no-else-return */ import $ from 'jquery'; import Api from './api'; diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index b27d635c6ac..64f3dde5be7 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, no-else-return, no-param-reassign, max-len */ +/* eslint-disable func-names, no-var, no-unused-vars, consistent-return, one-var, prefer-template, no-else-return, no-param-reassign */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 50dd3c12382..7bde4860973 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -1,4 +1,4 @@ -/* eslint-disable no-return-assign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, object-shorthand, prefer-template, class-methods-use-this, no-lonely-if, vars-on-top, max-len */ +/* eslint-disable no-return-assign, one-var, no-var, no-unused-vars, consistent-return, object-shorthand, prefer-template, class-methods-use-this, no-lonely-if, vars-on-top */ import $ from 'jquery'; import { escape, throttle } from 'underscore'; diff --git a/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js new file mode 100644 index 00000000000..14a89ef9293 --- /dev/null +++ b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js @@ -0,0 +1,21 @@ +import { AwardsHandler } from '~/awards_handler'; + +class EmojiMenuInModal extends AwardsHandler { + constructor(emoji, toggleButtonSelector, menuClass, selectEmojiCallback, targetContainerEl) { + super(emoji); + + this.selectEmojiCallback = selectEmojiCallback; + this.toggleButtonSelector = toggleButtonSelector; + this.menuClass = menuClass; + this.targetContainerEl = targetContainerEl; + + this.bindEvents(); + } + + postEmoji($emojiButton, awardUrl, selectedEmoji, callback) { + this.selectEmojiCallback(selectedEmoji, this.emoji.glEmojiTag(selectedEmoji)); + callback(); + } +} + +export default EmojiMenuInModal; diff --git a/app/assets/javascripts/set_status_modal/event_hub.js b/app/assets/javascripts/set_status_modal/event_hub.js new file mode 100644 index 00000000000..0948c2e5352 --- /dev/null +++ b/app/assets/javascripts/set_status_modal/event_hub.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; + +export default new Vue(); diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue b/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue new file mode 100644 index 00000000000..48e5ede80f2 --- /dev/null +++ b/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue @@ -0,0 +1,33 @@ +<script> +import { s__ } from '~/locale'; +import eventHub from './event_hub'; + +export default { + props: { + hasStatus: { + type: Boolean, + required: true, + }, + }, + computed: { + buttonText() { + return this.hasStatus ? s__('SetStatusModal|Edit status') : s__('SetStatusModal|Set status'); + }, + }, + methods: { + openModal() { + eventHub.$emit('openModal'); + }, + }, +}; +</script> + +<template> + <button + type="button" + class="btn menu-item" + @click="openModal" + > + {{ buttonText }} + </button> +</template> diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue new file mode 100644 index 00000000000..43f0b6651b9 --- /dev/null +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -0,0 +1,241 @@ +<script> +import $ from 'jquery'; +import createFlash from '~/flash'; +import Icon from '~/vue_shared/components/icon.vue'; +import GfmAutoComplete from '~/gfm_auto_complete'; +import { __, s__ } from '~/locale'; +import Api from '~/api'; +import eventHub from './event_hub'; +import EmojiMenuInModal from './emoji_menu_in_modal'; + +const emojiMenuClass = 'js-modal-status-emoji-menu'; + +export default { + components: { + Icon, + }, + props: { + currentEmoji: { + type: String, + required: true, + }, + currentMessage: { + type: String, + required: true, + }, + }, + data() { + return { + defaultEmojiTag: '', + emoji: this.currentEmoji, + emojiMenu: null, + emojiTag: '', + isEmojiMenuVisible: false, + message: this.currentMessage, + modalId: 'set-user-status-modal', + noEmoji: true, + }; + }, + computed: { + isDirty() { + return this.message.length || this.emoji.length; + }, + }, + mounted() { + eventHub.$on('openModal', this.openModal); + }, + beforeDestroy() { + this.emojiMenu.destroy(); + }, + methods: { + openModal() { + this.$root.$emit('bv::show::modal', this.modalId); + }, + closeModal() { + this.$root.$emit('bv::hide::modal', this.modalId); + }, + setupEmojiListAndAutocomplete() { + const toggleEmojiMenuButtonSelector = '#set-user-status-modal .js-toggle-emoji-menu'; + const emojiAutocomplete = new GfmAutoComplete(); + emojiAutocomplete.setup($(this.$refs.statusMessageField), { emojis: true }); + + import(/* webpackChunkName: 'emoji' */ '~/emoji') + .then(Emoji => { + if (this.emoji) { + this.emojiTag = Emoji.glEmojiTag(this.emoji); + } + this.noEmoji = this.emoji === ''; + this.defaultEmojiTag = Emoji.glEmojiTag('speech_balloon'); + + this.emojiMenu = new EmojiMenuInModal( + Emoji, + toggleEmojiMenuButtonSelector, + emojiMenuClass, + this.setEmoji, + this.$refs.userStatusForm, + ); + }) + .catch(() => createFlash(__('Failed to load emoji list.'))); + }, + showEmojiMenu() { + this.isEmojiMenuVisible = true; + this.emojiMenu.showEmojiMenu($(this.$refs.toggleEmojiMenuButton)); + }, + hideEmojiMenu() { + if (!this.isEmojiMenuVisible) { + return; + } + + this.isEmojiMenuVisible = false; + this.emojiMenu.hideMenuElement($(`.${emojiMenuClass}`)); + }, + setDefaultEmoji() { + const { emojiTag } = this; + const hasStatusMessage = this.message; + if (hasStatusMessage && emojiTag) { + return; + } + + if (hasStatusMessage) { + this.noEmoji = false; + this.emojiTag = this.defaultEmojiTag; + } else if (emojiTag === this.defaultEmojiTag) { + this.noEmoji = true; + this.clearEmoji(); + } + }, + setEmoji(emoji, emojiTag) { + this.emoji = emoji; + this.noEmoji = false; + this.clearEmoji(); + this.emojiTag = emojiTag; + }, + clearEmoji() { + if (this.emojiTag) { + this.emojiTag = ''; + } + }, + clearStatusInputs() { + this.emoji = ''; + this.message = ''; + this.noEmoji = true; + this.clearEmoji(); + this.hideEmojiMenu(); + }, + removeStatus() { + this.clearStatusInputs(); + this.setStatus(); + }, + setStatus() { + const { emoji, message } = this; + + Api.postUserStatus({ + emoji, + message, + }) + .then(this.onUpdateSuccess) + .catch(this.onUpdateFail); + }, + onUpdateSuccess() { + this.closeModal(); + window.location.reload(); + }, + onUpdateFail() { + createFlash( + s__("SetStatusModal|Sorry, we weren't able to set your status. Please try again later."), + ); + + this.closeModal(); + }, + }, +}; +</script> + +<template> + <gl-ui-modal + :title="s__('SetStatusModal|Set a status')" + :modal-id="modalId" + :ok-title="s__('SetStatusModal|Set status')" + :cancel-title="s__('SetStatusModal|Remove status')" + ok-variant="success" + class="set-user-status-modal" + @shown="setupEmojiListAndAutocomplete" + @hide="hideEmojiMenu" + @ok="setStatus" + @cancel="removeStatus" + > + <div> + <input + v-model="emoji" + class="js-status-emoji-field" + type="hidden" + name="user[status][emoji]" + /> + <div + ref="userStatusForm" + class="form-group position-relative m-0" + > + <div class="input-group"> + <span class="input-group-btn"> + <button + ref="toggleEmojiMenuButton" + v-gl-tooltip.bottom + :title="s__('SetStatusModal|Add status emoji')" + :aria-label="s__('SetStatusModal|Add status emoji')" + name="button" + type="button" + class="js-toggle-emoji-menu emoji-menu-toggle-button btn" + @click="showEmojiMenu" + > + <span v-html="emojiTag"></span> + <span + v-show="noEmoji" + class="js-no-emoji-placeholder no-emoji-placeholder position-relative" + > + <icon + name="emoji_slightly_smiling_face" + css-classes="award-control-icon-neutral" + /> + <icon + name="emoji_smiley" + css-classes="award-control-icon-positive" + /> + <icon + name="emoji_smile" + css-classes="award-control-icon-super-positive" + /> + </span> + </button> + </span> + <input + ref="statusMessageField" + v-model="message" + :placeholder="s__('SetStatusModal|What\'s your status?')" + type="text" + class="form-control form-control input-lg js-status-message-field" + name="user[status][message]" + @keyup="setDefaultEmoji" + @keyup.enter.prevent + @click="hideEmojiMenu" + /> + <span + v-show="isDirty" + class="input-group-btn" + > + <button + v-gl-tooltip.bottom + :title="s__('SetStatusModal|Clear status')" + :aria-label="s__('SetStatusModal|Clear status')" + name="button" + type="button" + class="js-clear-user-status-button clear-user-status btn" + @click="clearStatusInputs()" + > + <icon name="close" /> + </button> + </span> + </div> + </div> + </div> + </gl-ui-modal> +</template> diff --git a/app/assets/javascripts/shared/milestones/form.js b/app/assets/javascripts/shared/milestones/form.js index 8681a1776c6..0ff84dc4667 100644 --- a/app/assets/javascripts/shared/milestones/form.js +++ b/app/assets/javascripts/shared/milestones/form.js @@ -15,5 +15,6 @@ export default (initGFM = true) => { epics: initGFM, milestones: initGFM, labels: initGFM, + snippets: initGFM, }); }; diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js index 6fea03af46a..74166313940 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js +++ b/app/assets/javascripts/templates/issuable_template_selector.js @@ -1,4 +1,4 @@ -/* eslint-disable no-useless-return, max-len */ +/* eslint-disable no-useless-return */ import $ from 'jquery'; import Api from '../api'; diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index 85123a63a45..066fd6278a7 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */ +/* eslint-disable func-names, consistent-return, no-var, one-var, no-else-return, prefer-arrow-callback, class-methods-use-this */ import $ from 'jquery'; import { visitUrl } from './lib/utils/url_utility'; diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index e19bbbacf4d..e2259a8d07b 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, one-var, no-var, prefer-rest-params, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, no-void, camelcase, no-param-reassign */ +/* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, prefer-arrow-callback, consistent-return, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, no-void, camelcase, no-param-reassign */ /* global Issuable */ /* global emitSidebarEvent */ @@ -592,7 +592,7 @@ function UsersSelect(currentUser, els, options = {}) { if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { var trimmed = query.term.trim(); emailUser = { - name: "Invite \"" + query.term + "\" by email", + name: "Invite \"" + trimmed + "\" by email", username: trimmed, id: trimmed, invite: true diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index d62537021ca..10e8ddad9cd 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -76,6 +76,7 @@ epics: this.enableAutocomplete, milestones: this.enableAutocomplete, labels: this.enableAutocomplete, + snippets: this.enableAutocomplete, }); }, beforeDestroy() { diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index afc4196c729..ccd53158820 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -18,6 +18,16 @@ required: true, }, }, + computed: { + mdTable() { + return [ + '| header | header |', + '| ------ | ------ |', + '| cell | cell |', + '| cell | cell |', + ].join('\n'); + }, + }, mounted() { $(document).on('markdown-preview:show.vue', this.previewMarkdownTab); $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab); @@ -129,6 +139,12 @@ button-title="Add a task list" icon="task-done" /> + <toolbar-button + :tag="mdTable" + :prepend="true" + :button-title="__('Add a table')" + icon="table" + /> <button v-tooltip aria-label="Go full screen" diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index 0138c9be803..bdb2351c344 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */ +/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, class-methods-use-this */ // Zen Mode (full screen) textarea // diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 0b9dff64b0b..9638fee6078 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,8 +1,7 @@ -.calender-block { +.calendar-block { padding-left: 0; padding-right: 0; border-top: 0; - direction: rtl; @media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) { overflow-x: auto; @@ -42,10 +41,13 @@ } .calendar-hint { - margin-top: -23px; - float: right; font-size: 12px; - direction: ltr; + + &.bottom-right { + direction: ltr; + margin-top: -23px; + float: right; + } } .pika-single.gitlab-theme { diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss index 2193e8e8de3..2e7f25d975e 100644 --- a/app/assets/stylesheets/framework/contextual_sidebar.scss +++ b/app/assets/stylesheets/framework/contextual_sidebar.scss @@ -9,8 +9,7 @@ padding-left: $contextual-sidebar-width; } - .issues-bulk-update.right-sidebar.right-sidebar-expanded - .issuable-sidebar-header { + .issues-bulk-update.right-sidebar.right-sidebar-expanded .issuable-sidebar-header { padding: 10px 0 15px; } } @@ -75,7 +74,7 @@ .nav-sidebar { transition: width $sidebar-transition-duration, left $sidebar-transition-duration; position: fixed; - z-index: 400; + z-index: 600; width: $contextual-sidebar-width; top: $header-height; bottom: 0; @@ -86,8 +85,7 @@ &:not(.sidebar-collapsed-desktop) { @media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) { - box-shadow: inset -1px 0 0 $border-color, - 2px 1px 3px $dropdown-shadow-color; + box-shadow: inset -1px 0 0 $border-color, 2px 1px 3px $dropdown-shadow-color; } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 11a30d83f03..c430009bfe0 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -529,9 +529,10 @@ } .header-user { - .dropdown-menu { + &.show .dropdown-menu { width: auto; min-width: unset; + max-height: 323px; margin-top: 4px; color: $gl-text-color; left: auto; @@ -542,6 +543,18 @@ .user-name { display: block; } + + .user-status-emoji { + margin-right: 0; + display: block; + vertical-align: text-top; + max-width: 148px; + font-size: 12px; + + gl-emoji { + font-size: $gl-font-size; + } + } } svg { @@ -573,3 +586,24 @@ } } } + +.set-user-status-modal { + .modal-body { + min-height: unset; + } + + .input-lg { + max-width: unset; + } + + .no-emoji-placeholder, + .clear-user-status { + svg { + fill: $gl-text-color-secondary; + } + } + + .emoji-menu-toggle-button { + @include emoji-menu-toggle-button; + } +} diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 7f37dd3de91..be41dbfc61f 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -20,20 +20,24 @@ display: inline-block; overflow-x: auto; border: 0; - border-color: $gray-100; + border-color: $gl-gray-100; @supports (width: fit-content) { display: block; width: fit-content; } + tbody { + background-color: $white-light; + } + tr { th { - border-bottom: solid 2px $gray-100; + border-bottom: solid 2px $gl-gray-100; } td { - border-color: $gray-100; + border-color: $gl-gray-100; } } } @@ -266,3 +270,59 @@ border-radius: 50%; } } + +@mixin emoji-menu-toggle-button { + line-height: 1; + padding: 0; + min-width: 16px; + color: $gray-darkest; + fill: $gray-darkest; + + .fa { + position: relative; + font-size: 16px; + } + + svg { + @include btn-svg; + margin: 0; + } + + .award-control-icon-positive, + .award-control-icon-super-positive { + position: absolute; + top: 0; + left: 0; + opacity: 0; + } + + &:hover, + &.is-active { + .danger-highlight { + color: $red-500; + } + + .link-highlight { + color: $blue-600; + fill: $blue-600; + } + + .award-control-icon-neutral { + opacity: 0; + } + + .award-control-icon-positive { + opacity: 1; + } + } + + &.is-active { + .award-control-icon-positive { + opacity: 0; + } + + .award-control-icon-super-positive { + opacity: 1; + } + } +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 837016db67b..b7a95f604b8 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -314,7 +314,8 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%); $monospace-font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, - 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; /* * Dropdowns @@ -634,5 +635,4 @@ Modals */ $modal-body-height: 134px; - $priority-label-empty-state-width: 114px; diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss index 7d90452e1f4..759b4f333ca 100644 --- a/app/assets/stylesheets/framework/variables_overrides.scss +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -18,3 +18,4 @@ $success: $green-500; $info: $blue-500; $warning: $orange-500; $danger: $red-500; +$zindex-modal-backdrop: 1040; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 628a4ca38da..11966931a6c 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -59,7 +59,7 @@ vertical-align: middle; .stage-cell .stage-container { - margin: 3px 3px 3px 0; + margin: 0 3px 3px 0; } .stage-container:last-child { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 5035714b95f..17b02c6e31e 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1000,6 +1000,7 @@ } .tree-list-holder { + position: -webkit-sticky; position: sticky; top: 100px; max-height: calc(100vh - 100px); diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index c9e0899425f..bfba1bf1b2b 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -343,6 +343,10 @@ ul.notes { &.parallel { border-width: 1px; + + &.new { + border-right-width: 0; + } } .discussion-notes { @@ -519,59 +523,7 @@ ul.notes { } .note-action-button { - line-height: 1; - padding: 0; - min-width: 16px; - color: $gray-darkest; - fill: $gray-darkest; - - .fa { - position: relative; - font-size: 16px; - } - - svg { - @include btn-svg; - margin: 0; - } - - .award-control-icon-positive, - .award-control-icon-super-positive { - position: absolute; - top: 0; - left: 0; - opacity: 0; - } - - &:hover, - &.is-active { - .danger-highlight { - color: $red-500; - } - - .link-highlight { - color: $blue-600; - fill: $blue-600; - } - - .award-control-icon-neutral { - opacity: 0; - } - - .award-control-icon-positive { - opacity: 1; - } - } - - &.is-active { - .award-control-icon-positive { - opacity: 0; - } - - .award-control-icon-super-positive { - opacity: 1; - } - } + @include emoji-menu-toggle-button; } .discussion-toggle-button { @@ -790,7 +742,7 @@ ul.notes { padding-top: 0; .discussion-wrapper { - border-color: transparent; + border: 0; } } } diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index caa839c32a5..f084adaf5d3 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -81,14 +81,14 @@ // Middle dot divider between each element in a list of items. .middle-dot-divider { &::after { - content: "\00B7"; // Middle Dot + content: '\00B7'; // Middle Dot padding: 0 6px; font-weight: $gl-font-weight-bold; } &:last-child { &::after { - content: ""; + content: ''; padding: 0; } } @@ -191,7 +191,6 @@ @include media-breakpoint-down(xs) { width: auto; } - } .profile-crop-image-container { @@ -215,7 +214,6 @@ } } - .user-profile { .cover-controls a { margin-left: 5px; @@ -418,7 +416,7 @@ table.u2f-registrations { } &.unverified { - @include status-color($gray-dark, color("gray"), $common-gray-dark); + @include status-color($gray-dark, color('gray'), $common-gray-dark); } } } @@ -431,7 +429,7 @@ table.u2f-registrations { } .emoji-menu-toggle-button { - @extend .note-action-button; + @include emoji-menu-toggle-button; .no-emoji-placeholder { position: relative; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 7c42dcad959..da3d8aa53ad 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -830,6 +830,14 @@ } } +.repository-language-bar-tooltip-language { + font-weight: $gl-font-weight-bold; +} + +.repository-language-bar-tooltip-share { + color: $theme-gray-400; +} + pre.light-well { border-color: $well-light-border; } diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss index 2e2ab8532d2..59fdbf31fe9 100644 --- a/app/assets/stylesheets/performance_bar.scss +++ b/app/assets/stylesheets/performance_bar.scss @@ -1,4 +1,5 @@ @import 'framework/variables'; +@import 'framework/variables_overrides'; @import 'peek/views/rblineprof'; #js-peek { @@ -6,7 +7,7 @@ left: 0; top: 0; width: 100%; - z-index: 1039; + z-index: #{$zindex-modal-backdrop + 1}; height: $performance-bar-height; background: $black; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index b7c758a42ed..8040a14ef56 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -67,8 +67,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end end - def reset_runners_token + def reset_registration_token @application_setting.reset_runners_registration_token! + flash[:notice] = 'New runners registration token has been generated!' redirect_to admin_runners_path end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d7dbc712743..ec45e2813c5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -67,7 +67,6 @@ class ApplicationController < ActionController::Base end rescue_from Gitlab::Git::Storage::Inaccessible, GRPC::Unavailable, Gitlab::Git::CommandError do |exception| - Raven.capture_exception(exception) if sentry_enabled? log_exception(exception) headers['Retry-After'] = exception.retry_after if exception.respond_to?(:retry_after) diff --git a/app/controllers/concerns/labels_as_hash.rb b/app/controllers/concerns/labels_as_hash.rb new file mode 100644 index 00000000000..1171aa9cf44 --- /dev/null +++ b/app/controllers/concerns/labels_as_hash.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module LabelsAsHash + extend ActiveSupport::Concern + + def labels_as_hash(target = nil, params = {}) + available_labels = LabelsFinder.new( + current_user, + params + ).execute + + label_hashes = available_labels.as_json(only: [:title, :color]) + + if target&.respond_to?(:labels) + already_set_labels = available_labels & target.labels + if already_set_labels.present? + titles = already_set_labels.map(&:title) + label_hashes.each do |hash| + if titles.include?(hash['title']) + hash[:set] = true + end + end + end + end + + label_hashes + end +end diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index cb9ab35de85..26768c628ca 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -12,7 +12,8 @@ class Groups::LabelsController < Groups::ApplicationController def index respond_to do |format| format.html do - @labels = GroupLabelsFinder.new(@group, params.merge(sort: sort)).execute + @labels = GroupLabelsFinder + .new(current_user, @group, params.merge(sort: sort)).execute end format.json do render json: LabelSerializer.new.represent_appearance(available_labels) diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index 6d9a225b771..93f3eb2be6d 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -10,6 +10,13 @@ module Groups define_secret_variables end + def reset_registration_token + @group.reset_runners_token! + + flash[:notice] = 'New runners registration token has been generated!' + redirect_to group_settings_ci_cd_path + end + private def define_secret_variables diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb index 7c93cf36862..d386fb63d9f 100644 --- a/app/controllers/projects/autocomplete_sources_controller.rb +++ b/app/controllers/projects/autocomplete_sources_controller.rb @@ -27,6 +27,10 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController render json: @autocomplete_service.commands(target, params[:type]) end + def snippets + render json: @autocomplete_service.snippets + end + private def load_autocomplete_service diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index c2df7b34f90..2917925947f 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -16,6 +16,8 @@ class Projects::CompareController < Projects::ApplicationController before_action :define_diff_notes_disabled, only: [:show, :diff_for_path] before_action :define_commits, only: [:show, :diff_for_path, :signatures] before_action :merge_request, only: [:index, :show] + # Validation + before_action :validate_refs! def index end @@ -63,6 +65,21 @@ class Projects::CompareController < Projects::ApplicationController private + def valid_ref?(ref_name) + return true unless ref_name.present? + + Gitlab::GitRefValidator.validate(ref_name) + end + + def validate_refs! + valid = [head_ref, start_ref].map { |ref| valid_ref?(ref) } + + return if valid.all? + + flash[:alert] = "Invalid branch name" + redirect_to project_compare_index_path(@project) + end + def compare return @compare if defined?(@compare) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4e859de6fde..b06a6f3bb0d 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -127,7 +127,7 @@ class Projects::IssuesController < Projects::ApplicationController end def related_branches - @related_branches = @issue.related_branches(current_user) + @related_branches = Issues::RelatedBranchesService.new(project, current_user).execute(issue) respond_to do |format| format.json do diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index 9c9bbe04947..3ecf94c008e 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -2,6 +2,7 @@ class Projects::JobsController < Projects::ApplicationController include SendFileUpload + include ContinueParams before_action :build, except: [:index, :cancel_all] before_action :authorize_read_build! @@ -107,7 +108,12 @@ class Projects::JobsController < Projects::ApplicationController return respond_422 unless @build.cancelable? @build.cancel - redirect_to build_path(@build) + + if continue_params + redirect_to continue_params[:to] + else + redirect_to builds_project_pipeline_path(@project, @build.pipeline.id) + end end def unschedule diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index a0ce3b08d9f..640038818f2 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -163,6 +163,7 @@ class Projects::LabelsController < Projects::ApplicationController project_id: @project.id, include_ancestor_groups: params[:include_ancestor_groups], search: params[:search], + subscribed: params[:subscribed], sort: sort).execute end diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb index 25d2c11b7db..5307cd0720a 100644 --- a/app/controllers/projects/merge_requests/diffs_controller.rb +++ b/app/controllers/projects/merge_requests/diffs_controller.rb @@ -25,7 +25,13 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic @diffs.write_cache - render json: DiffsSerializer.new(current_user: current_user, project: @merge_request.project).represent(@diffs, additional_attributes) + request = { + current_user: current_user, + project: @merge_request.project, + render: ->(partial, locals) { view_to_html_string(partial, locals) } + } + + render json: DiffsSerializer.new(request).represent(@diffs, additional_attributes) end def define_diff_vars diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index d691744d72a..8bc3a81d771 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -44,12 +44,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @noteable = @merge_request @commits_count = @merge_request.commits_count - # TODO cleanup- Fatih Simon Create an issue to remove these after the refactoring - # we no longer render notes here. I see it will require a small frontend refactoring, - # since we gather some data from this collection. - @discussions = @merge_request.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) - labels set_pipeline_variables @@ -207,7 +201,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo environments = begin @merge_request.environments_for(current_user).map do |environment| - project = environment.project + project = environment.project deployment = environment.first_deployment_for(@merge_request.diff_head_sha) stop_url = @@ -217,7 +211,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo metrics_url = if can?(current_user, :read_environment, environment) && environment.has_metrics? - metrics_project_environment_deployment_path(environment.project, environment, deployment) + metrics_project_environment_deployment_path(project, environment, deployment) end metrics_monitoring_url = diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index a2d1b7866c2..3a1344651df 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -36,6 +36,13 @@ module Projects end end + def reset_registration_token + @project.reset_runners_token! + + flash[:notice] = 'New runners registration token has been generated!' + redirect_to namespace_project_settings_ci_cd_path + end + private def update_params diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index 6d83d24cdb8..1d76c90d4eb 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -14,10 +14,10 @@ module Projects @new_deploy_token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute if @new_deploy_token.persisted? - flash[:notice] = s_('DeployTokens|Your new project deploy token has been created.') + flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.') end - redirect_to action: :show + render_show end private diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a9417369ca2..ee438e160f2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -366,6 +366,7 @@ class ProjectsController < Projects::ApplicationController repository_access_level snippets_access_level wiki_access_level + pages_access_level ] ] end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index e509098d778..d16240af404 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -29,11 +29,17 @@ class UsersController < ApplicationController format.json do load_events - pager_json("events/_events", @events.count) + pager_json("events/_events", @events.count, events: @events) end end end + def activity + respond_to do |format| + format.html { render 'show' } + end + end + def groups load_groups @@ -53,9 +59,7 @@ class UsersController < ApplicationController respond_to do |format| format.html { render 'show' } format.json do - render json: { - html: view_to_html_string("shared/projects/_list", projects: @projects) - } + pager_json("shared/projects/_list", @projects.count, projects: @projects) end end end @@ -125,6 +129,7 @@ class UsersController < ApplicationController @projects = PersonalProjectsFinder.new(user).execute(current_user) .page(params[:page]) + .per(params[:limit]) prepare_projects_for_rendering(@projects) end diff --git a/app/finders/group_labels_finder.rb b/app/finders/group_labels_finder.rb index 903023033ed..a668a0f0fae 100644 --- a/app/finders/group_labels_finder.rb +++ b/app/finders/group_labels_finder.rb @@ -1,17 +1,29 @@ # frozen_string_literal: true class GroupLabelsFinder - attr_reader :group, :params + attr_reader :current_user, :group, :params - def initialize(group, params = {}) + def initialize(current_user, group, params = {}) + @current_user = current_user @group = group @params = params end def execute group.labels + .optionally_subscribed_by(subscriber_id) .optionally_search(params[:search]) .order_by(params[:sort]) .page(params[:page]) end + + private + + def subscriber_id + current_user&.id if subscribed? + end + + def subscribed? + params[:subscribed] == 'true' + end end diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 9e24154e4b6..1f98ecf95ca 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -363,6 +363,7 @@ class IssuableFinder def use_cte_for_search? return false unless search return false unless Gitlab::Database.postgresql? + return false unless Feature.enabled?(:use_cte_for_group_issues_search, default_enabled: true) params[:use_cte_for_search] end @@ -428,6 +429,10 @@ class IssuableFinder params[:milestone_title] == Milestone::Upcoming.name end + def filter_by_any_milestone? + params[:milestone_title] == Milestone::Any.title + end + def filter_by_started_milestone? params[:milestone_title] == Milestone::Started.name end @@ -437,6 +442,8 @@ class IssuableFinder if milestones? if filter_by_no_milestone? items = items.left_joins_milestones.where(milestone_id: [-1, nil]) + elsif filter_by_any_milestone? + items = items.any_milestone elsif filter_by_upcoming_milestone? upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items)) items = items.left_joins_milestones.where(milestone_id: upcoming_ids) diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 770e0bfe1a3..abdc47b9866 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -120,9 +120,13 @@ class IssuesFinder < IssuableFinder return @user_can_see_all_confidential_issues = true if current_user.full_private_access? @user_can_see_all_confidential_issues = - project? && - project && - project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL + if project? && project + project.team.max_member_access(current_user.id) >= CONFIDENTIAL_ACCESS_LEVEL + elsif group + group.max_member_access_for_user(current_user) >= CONFIDENTIAL_ACCESS_LEVEL + else + false + end end def user_cannot_see_confidential_issues? diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 08fc2968e77..d000af21be3 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -17,6 +17,7 @@ class LabelsFinder < UnionFinder @skip_authorization = skip_authorization items = find_union(label_ids, Label) || Label.none items = with_title(items) + items = by_subscription(items) items = by_search(items) sort(items) end @@ -84,6 +85,18 @@ class LabelsFinder < UnionFinder labels.search(params[:search]) end + def by_subscription(labels) + labels.optionally_subscribed_by(subscriber_id) + end + + def subscriber_id + current_user&.id if subscribed? + end + + def subscribed? + params[:subscribed] == 'true' + end + # Gets redacted array of group ids # which can include the ancestors and descendants of the requested group. def group_ids_for(group) @@ -116,7 +129,7 @@ class LabelsFinder < UnionFinder end def project? - params[:project_id].present? + params[:project].present? || params[:project_id].present? end def projects? @@ -139,7 +152,7 @@ class LabelsFinder < UnionFinder return @project if defined?(@project) if project? - @project = Project.find(params[:project_id]) + @project = params[:project] || Project.find(params[:project_id]) @project = nil unless authorized_to_read_labels?(@project) else @project = nil diff --git a/app/finders/license_template_finder.rb b/app/finders/license_template_finder.rb index 196922709f7..d735a4c1d69 100644 --- a/app/finders/license_template_finder.rb +++ b/app/finders/license_template_finder.rb @@ -5,33 +5,47 @@ # Used to find license templates, which may come from a variety of external # sources # -# Arguments: +# Params can be any of the following: # popular: boolean. When set to true, only "popular" licenses are shown. When # false, all licenses except popular ones are shown. When nil (the # default), *all* licenses will be shown. +# name: string. If set, return a single license matching that name (or nil) class LicenseTemplateFinder - attr_reader :params + include Gitlab::Utils::StrongMemoize - def initialize(params = {}) + attr_reader :project, :params + + def initialize(project, params = {}) + @project = project @params = params end def execute - Licensee::License.all(featured: popular_only?).map do |license| - LicenseTemplate.new( - id: license.key, - name: license.name, - nickname: license.nickname, - category: (license.featured? ? :Popular : :Other), - content: license.content, - url: license.url, - meta: license.meta - ) + if params[:name] + vendored_licenses.find { |template| template.key == params[:name] } + else + vendored_licenses end end private + def vendored_licenses + strong_memoize(:vendored_licenses) do + Licensee::License.all(featured: popular_only?).map do |license| + LicenseTemplate.new( + key: license.key, + name: license.name, + nickname: license.nickname, + category: (license.featured? ? :Popular : :Other), + content: license.content, + url: license.url, + meta: license.meta + ) + end + end + end + def popular_only? params.fetch(:popular, nil) end diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 50c051c3aa1..e190d5d90c9 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -66,10 +66,6 @@ class MergeRequestsFinder < IssuableFinder items.where(target_branch: target_branch) end - def item_project_ids(items) - items&.reorder(nil)&.select(:target_project_id) - end - def by_wip(items) if params[:wip] == 'yes' items.where(wip_match(items.arel_table)) diff --git a/app/finders/pending_todos_finder.rb b/app/finders/pending_todos_finder.rb new file mode 100644 index 00000000000..c21d90c9182 --- /dev/null +++ b/app/finders/pending_todos_finder.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +# Finder for retrieving the pending todos of a user, optionally filtered using +# various fields. +# +# While this finder is a bit more verbose compared to use +# `where(params.slice(...))`, it allows us to decouple the input parameters from +# the actual column names. For example, if we ever decide to use separate +# columns for target types (e.g. `issue_id`, `merge_request_id`, etc), we no +# longer need to change _everything_ that uses this finder. Instead, we just +# change the various `by_*` methods in this finder, without having to touch +# everything that uses it. +class PendingTodosFinder + attr_reader :current_user, :params + + # current_user - The user to retrieve the todos for. + # params - A Hash containing columns and values to use for filtering todos. + def initialize(current_user, params = {}) + @current_user = current_user + @params = params + end + + def execute + todos = current_user.todos.pending + todos = by_project(todos) + todos = by_target_id(todos) + todos = by_target_type(todos) + todos = by_commit_id(todos) + + todos + end + + def by_project(todos) + if (id = params[:project_id]) + todos.for_project(id) + else + todos + end + end + + def by_target_id(todos) + if (id = params[:target_id]) + todos.for_target(id) + else + todos + end + end + + def by_target_type(todos) + if (type = params[:target_type]) + todos.for_type(type) + else + todos + end + end + + def by_commit_id(todos) + if (id = params[:commit_id]) + todos.for_commit(id) + else + todos + end + end +end diff --git a/app/finders/template_finder.rb b/app/finders/template_finder.rb index c92ee9ca9ac..3e483716064 100644 --- a/app/finders/template_finder.rb +++ b/app/finders/template_finder.rb @@ -1,29 +1,32 @@ # frozen_string_literal: true class TemplateFinder - VENDORED_TEMPLATES = { + include Gitlab::Utils::StrongMemoize + + VENDORED_TEMPLATES = HashWithIndifferentAccess.new( dockerfiles: ::Gitlab::Template::DockerfileTemplate, gitignores: ::Gitlab::Template::GitignoreTemplate, gitlab_ci_ymls: ::Gitlab::Template::GitlabCiYmlTemplate - }.freeze + ).freeze class << self - def build(type, params = {}) - if type == :licenses - LicenseTemplateFinder.new(params) # rubocop: disable CodeReuse/Finder + def build(type, project, params = {}) + if type.to_s == 'licenses' + LicenseTemplateFinder.new(project, params) # rubocop: disable CodeReuse/Finder else - new(type, params) + new(type, project, params) end end end - attr_reader :type, :params + attr_reader :type, :project, :params attr_reader :vendored_templates private :vendored_templates - def initialize(type, params = {}) + def initialize(type, project, params = {}) @type = type + @project = project @params = params @vendored_templates = VENDORED_TEMPLATES.fetch(type) diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 74baf79e4f2..d001e18fea9 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -23,6 +23,8 @@ class TodosFinder NONE = '0'.freeze + TODO_TYPES = Set.new(%w(Issue MergeRequest Epic)).freeze + attr_accessor :current_user, :params def initialize(current_user, params = {}) @@ -45,6 +47,13 @@ class TodosFinder sort(items) end + # Returns `true` if the current user has any todos for the given target. + # + # target - The value of the `target_type` column, such as `Issue`. + def any_for_target?(target) + current_user.todos.any_for_target?(target) + end + private def action_id? @@ -72,14 +81,11 @@ class TodosFinder end def author - return @author if defined?(@author) - - @author = + strong_memoize(:author) do if author? && params[:author_id] != NONE User.find(params[:author_id]) - else - nil end + end end def project? @@ -91,17 +97,9 @@ class TodosFinder end def project - return @project if defined?(@project) - - if project? - @project = Project.find(params[:project_id]) - - @project = nil if @project.pending_delete? - else - @project = nil + strong_memoize(:project) do + Project.find_without_deleted(params[:project_id]) if project? end - - @project end def group @@ -111,7 +109,7 @@ class TodosFinder end def type? - type.present? && %w(Issue MergeRequest Epic).include?(type) + type.present? && TODO_TYPES.include?(type) end def type @@ -119,77 +117,66 @@ class TodosFinder end def sort(items) - params[:sort] ? items.sort_by_attribute(params[:sort]) : items.order_id_desc + if params[:sort] + items.sort_by_attribute(params[:sort]) + else + items.order_id_desc + end end - # rubocop: disable CodeReuse/ActiveRecord def by_action(items) if action? - items = items.where(action: to_action_id) + items.for_action(to_action_id) + else + items end - - items end - # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def by_action_id(items) if action_id? - items = items.where(action: action_id) + items.for_action(action_id) + else + items end - - items end - # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def by_author(items) if author? - items = items.where(author_id: author.try(:id)) + items.for_author(author) + else + items end - - items end - # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def by_project(items) if project? - items = items.where(project: project) + items.for_project(project) + else + items end - - items end - # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def by_group(items) - return items unless group? - - groups = group.self_and_descendants - project_todos = items.where(project_id: Project.where(group: groups).select(:id)) - group_todos = items.where(group_id: groups.select(:id)) - - Todo.from_union([project_todos, group_todos]) + if group? + items.for_group_and_descendants(group) + else + items + end end - # rubocop: enable CodeReuse/ActiveRecord def by_state(items) - case params[:state].to_s - when 'done' + if params[:state].to_s == 'done' items.done else items.pending end end - # rubocop: disable CodeReuse/ActiveRecord def by_type(items) if type? - items = items.where(target_type: type) + items.for_type(type) + else + items end - - items end - # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb index eeca5026da1..3f2e813d381 100644 --- a/app/finders/user_recent_events_finder.rb +++ b/app/finders/user_recent_events_finder.rb @@ -31,7 +31,7 @@ class UserRecentEventsFinder recent_events(params[:offset] || 0) .joins(:project) .with_associations - .limit_recent(LIMIT, params[:offset]) + .limit_recent(params[:limit].presence || LIMIT, params[:offset]) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/finders/users_with_pending_todos_finder.rb b/app/finders/users_with_pending_todos_finder.rb new file mode 100644 index 00000000000..461bd92a366 --- /dev/null +++ b/app/finders/users_with_pending_todos_finder.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# Finder that given a target (e.g. an issue) finds all the users that have +# pending todos for said target. +class UsersWithPendingTodosFinder + attr_reader :target + + # target - The target, such as an Issue or MergeRequest. + def initialize(target) + @target = target + end + + def execute + User.for_todos(target.todos.pending) + end +end diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb index 066ce64a254..ab37c282fe5 100644 --- a/app/graphql/types/permission_types/project.rb +++ b/app/graphql/types/permission_types/project.rb @@ -16,7 +16,7 @@ module Types :create_deployment, :push_to_delete_protected_branch, :admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki, - :create_pages, :destroy_pages + :create_pages, :destroy_pages, :read_pages_content end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 32fc8e5e9ce..4f91e3e4117 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -292,7 +292,8 @@ module ApplicationHelper mergeRequests: merge_requests_project_autocomplete_sources_path(object), labels: labels_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), milestones: milestones_project_autocomplete_sources_path(object), - commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]) + commands: commands_project_autocomplete_sources_path(object, type: noteable_type, type_id: params[:id]), + snippets: snippets_project_autocomplete_sources_path(object) } end end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 9cbd5b5f785..883e5ddff57 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -159,10 +159,6 @@ module BlobHelper end end - def licenses_for_select - @licenses_for_select ||= template_dropdown_names(TemplateFinder.build(:licenses).execute) - end - def ref_project @ref_project ||= @target_project || @project end @@ -173,29 +169,34 @@ module BlobHelper categories.each_with_object({}) do |category, hash| hash[category] = grouped[category].map do |item| - { name: item.name, id: item.id } + { name: item.name, id: item.key } end end end private :template_dropdown_names - def gitignore_names - @gitignore_names ||= template_dropdown_names(TemplateFinder.build(:gitignores).execute) + def licenses_for_select(project = @project) + @licenses_for_select ||= template_dropdown_names(TemplateFinder.build(:licenses, project).execute) + end + + def gitignore_names(project = @project) + @gitignore_names ||= template_dropdown_names(TemplateFinder.build(:gitignores, project).execute) end - def gitlab_ci_ymls - @gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls).execute) + def gitlab_ci_ymls(project = @project) + @gitlab_ci_ymls ||= template_dropdown_names(TemplateFinder.build(:gitlab_ci_ymls, project).execute) end - def dockerfile_names - @dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles).execute) + def dockerfile_names(project = @project) + @dockerfile_names ||= template_dropdown_names(TemplateFinder.build(:dockerfiles, project).execute) end - def blob_editor_paths + def blob_editor_paths(project = @project) { 'relative-url-root' => Rails.application.config.relative_url_root, 'assets-prefix' => Gitlab::Application.config.assets.prefix, - 'blob-language' => @blob && @blob.language.try(:ace_mode) + 'blob-language' => @blob && @blob.language.try(:ace_mode), + 'project-id' => project.id } end diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index e3b74f443f7..be1e7016a1e 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -59,8 +59,8 @@ module BoardsHelper { toggle: "dropdown", - list_labels_path: labels_filter_path(true, include_ancestor_groups: true), - labels: labels_filter_path(true, include_descendant_groups: include_descendant_groups), + list_labels_path: labels_filter_path_with_defaults(only_group_labels: true, include_ancestor_groups: true), + labels: labels_filter_path_with_defaults(only_group_labels: true, include_descendant_groups: include_descendant_groups), labels_endpoint: @labels_endpoint, namespace_path: @namespace_path, project_path: @project&.path, diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index a67c91b21d7..19eb763e1de 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -13,8 +13,4 @@ module ClustersHelper render 'projects/clusters/gcp_signup_offer_banner' end end - - def rbac_clusters_feature_enabled? - Feature.enabled?(:rbac_clusters) - end end diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 463f4145bdd..33c53021c11 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -21,6 +21,29 @@ module DashboardHelper links.any? { |link| dashboard_nav_link?(link) } end + def controller_action_to_child_dashboards(controller = controller_name, action = action_name) + case "#{controller}##{action}" + when 'projects#index', 'root#index', 'projects#starred', 'projects#trending' + %w(projects stars) + when 'dashboard#activity' + %w(starred_project_activity project_activity) + when 'groups#index' + %w(groups) + when 'todos#index' + %w(todos) + when 'dashboard#issues' + %w(issues) + when 'dashboard#merge_requests' + %w(merge_requests) + else + [] + end + end + + def user_default_dashboard?(user = current_user) + controller_action_to_child_dashboards.any? {|dashboard| dashboard == user.dashboard } + end + private def get_dashboard_nav_links diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 6c51739ba1a..76ed8efe2c6 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -131,20 +131,26 @@ module LabelsHelper end end - def labels_filter_path(only_group_labels = false, include_ancestor_groups: true, include_descendant_groups: false) - project = @target_project || @project - + def labels_filter_path_with_defaults(only_group_labels: false, include_ancestor_groups: true, include_descendant_groups: false) options = {} options[:include_ancestor_groups] = include_ancestor_groups if include_ancestor_groups options[:include_descendant_groups] = include_descendant_groups if include_descendant_groups + options[:only_group_labels] = only_group_labels if only_group_labels && @group + options[:format] = :json + + labels_filter_path(options) + end + + def labels_filter_path(options = {}) + project = @target_project || @project + format = options.delete(:format) || :html if project - project_labels_path(project, :json, options) + project_labels_path(project, format, options) elsif @group - options[:only_group_labels] = only_group_labels if only_group_labels - group_labels_path(@group, :json, options) + group_labels_path(@group, format, options) else - dashboard_labels_path(:json) + dashboard_labels_path(format, options) end end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index a80c8f273a8..033686823a2 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -142,7 +142,7 @@ module NotesHelper def initial_notes_data(autocomplete) { notesUrl: notes_url, - notesIds: @notes.map(&:id), + notesIds: @noteable.notes.pluck(:id), # rubocop: disable CodeReuse/ActiveRecord now: Time.now.to_i, diffView: diff_view, enableGFM: { @@ -178,7 +178,7 @@ module NotesHelper notesPath: notes_url, totalNotes: issuable.discussions.length, lastFetchedAt: Time.now.to_i - }.to_json + } end def discussion_resolved_intro(discussion) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 8b17e6ef75d..0016f89db5c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -454,6 +454,7 @@ module ProjectsHelper buildsAccessLevel: feature.builds_access_level, wikiAccessLevel: feature.wiki_access_level, snippetsAccessLevel: feature.snippets_access_level, + pagesAccessLevel: feature.pages_access_level, containerRegistryEnabled: !!project.container_registry_enabled, lfsEnabled: !!project.lfs_enabled } @@ -468,7 +469,10 @@ module ProjectsHelper registryAvailable: Gitlab.config.registry.enabled, registryHelpPath: help_page_path('user/project/container_registry'), lfsAvailable: Gitlab.config.lfs.enabled, - lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'), + pagesAvailable: Gitlab.config.pages.enabled, + pagesAccessControlEnabled: Gitlab.config.pages.access_control, + pagesHelpPath: help_page_path('user/project/pages/index.md') } end diff --git a/app/helpers/repository_languages_helper.rb b/app/helpers/repository_languages_helper.rb index c1505b52808..cf7eee7fff3 100644 --- a/app/helpers/repository_languages_helper.rb +++ b/app/helpers/repository_languages_helper.rb @@ -13,6 +13,7 @@ module RepositoryLanguagesHelper content_tag :div, nil, class: "progress-bar has-tooltip", style: "width: #{lang.share}%; background-color:#{lang.color}", - title: lang.name + data: { html: true }, + title: "<span class=\"repository-language-bar-tooltip-language\">#{escape_javascript(lang.name)}</span> <span class=\"repository-language-bar-tooltip-share\">#{lang.share.round(1)}%</span>" end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index bcd91f619c8..42b533ad772 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -76,7 +76,7 @@ module UsersHelper tabs = [] if can?(current_user, :read_user_profile, @user) - tabs += [:activity, :groups, :contributed, :projects, :snippets] + tabs += [:overview, :activity, :groups, :contributed, :projects, :snippets] end tabs diff --git a/app/models/ci/artifact_blob.rb b/app/models/ci/artifact_blob.rb index cd0b31482d2..d87d6a5cb2f 100644 --- a/app/models/ci/artifact_blob.rb +++ b/app/models/ci/artifact_blob.rb @@ -4,7 +4,7 @@ module Ci class ArtifactBlob include BlobLike - EXTENSIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json].freeze + EXTENSIONS_SERVED_BY_PAGES = %w[.html .htm .txt .json .log].freeze attr_reader :entry diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 9b0bb2e05fc..f244ffc1c3a 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -263,7 +263,7 @@ module Ci end def schedulable? - Feature.enabled?('ci_enable_scheduled_build') && + Feature.enabled?('ci_enable_scheduled_build', default_enabled: true) && self.when == 'delayed' && options[:start_in].present? end @@ -792,6 +792,9 @@ module Ci variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(',')) variables.append(key: 'CI_SERVER_NAME', value: 'GitLab') variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION) + variables.append(key: 'CI_SERVER_VERSION_MAJOR', value: gitlab_version_info.major.to_s) + variables.append(key: 'CI_SERVER_VERSION_MINOR', value: gitlab_version_info.minor.to_s) + variables.append(key: 'CI_SERVER_VERSION_PATCH', value: gitlab_version_info.patch.to_s) variables.append(key: 'CI_SERVER_REVISION', value: Gitlab.revision) variables.append(key: 'CI_JOB_NAME', value: name) variables.append(key: 'CI_JOB_STAGE', value: stage) @@ -806,6 +809,10 @@ module Ci end end + def gitlab_version_info + @gitlab_version_info ||= Gitlab::VersionInfo.parse(Gitlab::VERSION) + end + def legacy_variables Gitlab::Ci::Variables::Collection.new.tap do |variables| variables.append(key: 'CI_BUILD_REF', value: sha) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 765943104a0..17024e8a0af 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -633,6 +633,22 @@ module Ci end end + def branch_updated? + strong_memoize(:branch_updated) do + push_details.branch_updated? + end + end + + def modified_paths + strong_memoize(:modified_paths) do + push_details.modified_paths + end + end + + def default_branch? + ref == project.default_branch + end + private def ci_yaml_from_repo @@ -656,6 +672,22 @@ module Ci Gitlab::DataBuilder::Pipeline.build(self) end + def push_details + strong_memoize(:push_details) do + Gitlab::Git::Push.new(project, before_sha, sha, push_ref) + end + end + + def push_ref + if branch? + Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s + elsif tag? + Gitlab::Git::TAG_REF_PREFIX + ref.to_s + else + raise ArgumentError, 'Invalid pipeline type!' + end + end + def latest_builds_status return 'failed' unless yaml_errors.blank? diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 811261a252e..58f3fe2460a 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -66,7 +66,7 @@ module Ci transition any - [:manual] => :manual end - event :schedule do + event :delay do transition any - [:scheduled] => :scheduled end end @@ -81,7 +81,7 @@ module Ci when 'failed' then drop when 'canceled' then cancel when 'manual' then block - when 'scheduled' then schedule + when 'scheduled' then delay when 'skipped', nil then skip else raise HasStatus::UnknownStatusError, diff --git a/app/models/commit.rb b/app/models/commit.rb index 49c36ad9d3f..a61ed03cf35 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -319,7 +319,11 @@ class Commit def status(ref = nil) return @statuses[ref] if @statuses.key?(ref) - @statuses[ref] = project.pipelines.latest_status_per_commit(id, ref)[id] + @statuses[ref] = status_for_project(ref, project) + end + + def status_for_project(ref, pipeline_project) + pipeline_project.pipelines.latest_status_per_commit(id, ref)[id] end def set_status_for_ref(ref, status) diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb new file mode 100644 index 00000000000..b61bf29e6ad --- /dev/null +++ b/app/models/concerns/diff_positionable_note.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true +module DiffPositionableNote + extend ActiveSupport::Concern + + included do + before_validation :set_original_position, on: :create + before_validation :update_position, on: :create, if: :on_text? + + serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + end + + %i(original_position position change_position).each do |meth| + define_method "#{meth}=" do |new_position| + if new_position.is_a?(String) + new_position = JSON.parse(new_position) rescue nil + end + + if new_position.is_a?(Hash) + new_position = new_position.with_indifferent_access + new_position = Gitlab::Diff::Position.new(new_position) + end + + return if new_position == read_attribute(meth) + + super(new_position) + end + end + + def on_text? + position&.position_type == "text" + end + + def on_image? + position&.position_type == "image" + end + + def supported? + for_commit? || self.noteable.has_complete_diff_refs? + end + + def active?(diff_refs = nil) + return false unless supported? + return true if for_commit? + + diff_refs ||= noteable.diff_refs + + self.position.diff_refs == diff_refs + end + + def set_original_position + return unless position + + self.original_position = self.position.dup unless self.original_position&.complete? + end + + def update_position + return unless supported? + return if for_commit? + + return if active? + return unless position + + tracer = Gitlab::Diff::PositionTracer.new( + project: self.project, + old_diff_refs: self.position.diff_refs, + new_diff_refs: self.noteable.diff_refs, + paths: self.position.paths + ) + + result = tracer.trace(self.position) + return unless result + + if result[:outdated] + self.change_position = result[:position] + else + self.position = result[:position] + end + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 5f65fceb7af..2aa52bbaeea 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -76,6 +76,7 @@ module Issuable scope :recent, -> { reorder(id: :desc) } scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_milestones, ->(ids) { where(milestone_id: ids) } + scope :any_milestone, -> { where('milestone_id IS NOT NULL') } scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) } scope :opened, -> { with_state(:opened) } scope :only_opened, -> { with_state(:opened) } diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb index 1d0a61364b0..92a5c1112af 100644 --- a/app/models/concerns/subscribable.rb +++ b/app/models/concerns/subscribable.rb @@ -31,9 +31,11 @@ module Subscribable end def subscribers(project) - subscriptions_available(project) - .where(subscribed: true) - .map(&:user) + relation = subscriptions_available(project) + .where(subscribed: true) + .select(:user_id) + + User.where(id: relation) end def toggle_subscription(user, project = nil) diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 047d353b4b5..95694377fe3 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -5,14 +5,11 @@ # A note of this type can be resolvable. class DiffNote < Note include NoteOnDiff + include DiffPositionableNote include Gitlab::Utils::StrongMemoize NOTEABLE_TYPES = %w(MergeRequest Commit).freeze - serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize - serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize - serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize - validates :original_position, presence: true validates :position, presence: true validates :line_code, presence: true, line_code: true, if: :on_text? @@ -21,8 +18,6 @@ class DiffNote < Note validate :verify_supported validate :diff_refs_match_commit, if: :for_commit? - before_validation :set_original_position, on: :create - before_validation :update_position, on: :create, if: :on_text? before_validation :set_line_code, if: :on_text? after_save :keep_around_commits after_commit :create_diff_file, on: :create @@ -31,31 +26,6 @@ class DiffNote < Note DiffDiscussion end - %i(original_position position change_position).each do |meth| - define_method "#{meth}=" do |new_position| - if new_position.is_a?(String) - new_position = JSON.parse(new_position) rescue nil - end - - if new_position.is_a?(Hash) - new_position = new_position.with_indifferent_access - new_position = Gitlab::Diff::Position.new(new_position) - end - - return if new_position == read_attribute(meth) - - super(new_position) - end - end - - def on_text? - position.position_type == "text" - end - - def on_image? - position.position_type == "image" - end - def create_diff_file return unless should_create_diff_file? @@ -87,15 +57,6 @@ class DiffNote < Note self.diff_file.line_code(self.diff_line) end - def active?(diff_refs = nil) - return false unless supported? - return true if for_commit? - - diff_refs ||= noteable.diff_refs - - self.position.diff_refs == diff_refs - end - def created_at_diff?(diff_refs) return false unless supported? return true if for_commit? @@ -141,37 +102,10 @@ class DiffNote < Note for_commit? || self.noteable.has_complete_diff_refs? end - def set_original_position - self.original_position = self.position.dup unless self.original_position&.complete? - end - def set_line_code self.line_code = self.position.line_code(self.project.repository) end - def update_position - return unless supported? - return if for_commit? - - return if active? - - tracer = Gitlab::Diff::PositionTracer.new( - project: self.project, - old_diff_refs: self.position.diff_refs, - new_diff_refs: self.noteable.diff_refs, - paths: self.position.paths - ) - - result = tracer.trace(self.position) - return unless result - - if result[:outdated] - self.change_position = result[:position] - else - self.position = result[:position] - end - end - def verify_supported return if supported? diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 68ba4b213b2..b2fb79bc7ed 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -37,38 +37,4 @@ class WebHook < ActiveRecord::Base def allow_local_requests? false end - - # In 11.4, the web_hooks table has both `token` and `encrypted_token` fields. - # Ensure that the encrypted version always takes precedence if present. - alias_method :attr_encrypted_token, :token - def token - attr_encrypted_token.presence || read_attribute(:token) - end - - # In 11.4, the web_hooks table has both `token` and `encrypted_token` fields. - # Pending a background migration to encrypt all fields, we should just clear - # the unencrypted value whenever the new value is set. - alias_method :'attr_encrypted_token=', :'token=' - def token=(value) - self.attr_encrypted_token = value - - write_attribute(:token, nil) - end - - # In 11.4, the web_hooks table has both `url` and `encrypted_url` fields. - # Ensure that the encrypted version always takes precedence if present. - alias_method :attr_encrypted_url, :url - def url - attr_encrypted_url.presence || read_attribute(:url) - end - - # In 11.4, the web_hooks table has both `url` and `encrypted_url` fields. - # Pending a background migration to encrypt all fields, we should just clear - # the unencrypted value whenever the new value is set. - alias_method :'attr_encrypted_url=', :'url=' - def url=(value) - self.attr_encrypted_url = value - - write_attribute(:url, nil) - end end diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb index 7d8ce0bbd05..11289887e00 100644 --- a/app/models/instance_configuration.rb +++ b/app/models/instance_configuration.rb @@ -64,10 +64,10 @@ class InstanceConfiguration end def ssh_algorithm_md5(ssh_file_content) - OpenSSL::Digest::MD5.hexdigest(ssh_file_content).scan(/../).join(':') + Gitlab::SSHPublicKey.new(ssh_file_content).fingerprint end def ssh_algorithm_sha256(ssh_file_content) - OpenSSL::Digest::SHA256.hexdigest(ssh_file_content) + Gitlab::SSHPublicKey.new(ssh_file_content).fingerprint('SHA256') end end diff --git a/app/models/issue.rb b/app/models/issue.rb index d13fbcf002c..4ace5d3ab97 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -170,24 +170,6 @@ class Issue < ActiveRecord::Base "#{project.to_reference(from, full: full)}#{reference}" end - # All branches containing the current issue's ID, except for - # those with a merge request open referencing the current issue. - # rubocop: disable CodeReuse/ServiceClass - def related_branches(current_user) - branches_with_iid = project.repository.branch_names.select do |branch| - branch =~ /\A#{iid}-(?!\d+-stable)/i - end - - branches_with_merge_request = - Issues::ReferencedMergeRequestsService - .new(project, current_user) - .referenced_merge_requests(self) - .map(&:source_branch) - - branches_with_iid - branches_with_merge_request - end - # rubocop: enable CodeReuse/ServiceClass - def suggested_branch_name return to_branch_name unless project.repository.branch_exists?(to_branch_name) diff --git a/app/models/label.rb b/app/models/label.rb index 9ef57a05b3e..43b49445765 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -45,6 +45,7 @@ class Label < ActiveRecord::Base scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) } scope :order_name_asc, -> { reorder(title: :asc) } scope :order_name_desc, -> { reorder(title: :desc) } + scope :subscribed_by, ->(user_id) { joins(:subscriptions).where(subscriptions: { user_id: user_id, subscribed: true }) } def self.prioritized(project) joins(:priorities) @@ -74,6 +75,14 @@ class Label < ActiveRecord::Base joins(label_priorities) end + def self.optionally_subscribed_by(user_id) + if user_id + subscribed_by(user_id) + else + all + end + end + alias_attribute :name, :title def self.reference_prefix diff --git a/app/models/license_template.rb b/app/models/license_template.rb index 693a6a89fd2..73e403f98b4 100644 --- a/app/models/license_template.rb +++ b/app/models/license_template.rb @@ -12,12 +12,10 @@ class LicenseTemplate (fullname|name\sof\s(author|copyright\sowner)) [\>\}\]]}xi.freeze - attr_reader :id, :name, :category, :nickname, :url, :meta + attr_reader :key, :name, :category, :nickname, :url, :meta - alias_method :key, :id - - def initialize(id:, name:, category:, content:, nickname: nil, url: nil, meta: {}) - @id = id + def initialize(key:, name:, category:, content:, nickname: nil, url: nil, meta: {}) + @key = key @name = name @category = category @content = content diff --git a/app/models/note.rb b/app/models/note.rb index bea02d69b65..95e1d3afa00 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -42,8 +42,10 @@ class Note < ActiveRecord::Base # Banzai::ObjectRenderer. attr_accessor :redacted_note_html - # An Array containing the number of visible references as generated by - # Banzai::ObjectRenderer + # Total of all references as generated by Banzai::ObjectRenderer + attr_accessor :total_reference_count + + # Number of user visible references as generated by Banzai::ObjectRenderer attr_accessor :user_visible_reference_count # Attribute used to store the attributes that have been changed by quick actions. @@ -288,15 +290,7 @@ class Note < ActiveRecord::Base end def cross_reference_not_visible_for?(user) - cross_reference? && !has_referenced_mentionables?(user) - end - - def has_referenced_mentionables?(user) - if user_visible_reference_count.present? - user_visible_reference_count > 0 - else - referenced_mentionables(user).any? - end + cross_reference? && !all_referenced_mentionables_allowed?(user) end def award_emoji? @@ -466,9 +460,18 @@ class Note < ActiveRecord::Base self.discussion_id ||= discussion_class.discussion_id(self) end + def all_referenced_mentionables_allowed?(user) + if user_visible_reference_count.present? && total_reference_count.present? + # if they are not equal, then there are private/confidential references as well + user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count + else + referenced_mentionables(user).any? + end + end + def force_cross_reference_regex_check? return unless system? - SystemNoteMetadata::TYPES_WITH_CROSS_REFERENCES.include?(system_note_metadata&.action) + system_note_metadata&.cross_reference_types&.include?(system_note_metadata&.action) end end diff --git a/app/models/project.rb b/app/models/project.rb index 59f088156c7..05e14c578b5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -55,8 +55,8 @@ class Project < ActiveRecord::Base cache_markdown_field :description, pipeline: :description delegate :feature_available?, :builds_enabled?, :wiki_enabled?, - :merge_requests_enabled?, :issues_enabled?, to: :project_feature, - allow_nil: true + :merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?, + to: :project_feature, allow_nil: true delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage @@ -356,7 +356,7 @@ class Project < ActiveRecord::Base # "enabled" here means "not disabled". It includes private features! scope :with_feature_enabled, ->(feature) { access_level_attribute = ProjectFeature.access_level_attribute(feature) - with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] }) + with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED, ProjectFeature::PUBLIC] }) } # Picks a feature where the level is exactly that given. @@ -386,6 +386,13 @@ class Project < ActiveRecord::Base only_integer: true, message: 'needs to be beetween 10 minutes and 1 month' } + # Returns a project, if it is not about to be removed. + # + # id - The ID of the project to retrieve. + def self.find_without_deleted(id) + without_deleted.find_by_id(id) + end + # Paginates a collection using a `WHERE id < ?` condition. # # before - A project ID to use for filtering out projects with an equal or @@ -418,15 +425,15 @@ class Project < ActiveRecord::Base end end - # project features may be "disabled", "internal" or "enabled". If "internal", + # project features may be "disabled", "internal", "enabled" or "public". If "internal", # they are only available to team members. This scope returns projects where - # the feature is either enabled, or internal with permission for the user. + # the feature is either public, enabled, or internal with permission for the user. # # This method uses an optimised version of `with_feature_access_level` for # logged in users to more efficiently get private projects with the given # feature. def self.with_feature_available_for_user(feature, user) - visible = [nil, ProjectFeature::ENABLED] + visible = [nil, ProjectFeature::ENABLED, ProjectFeature::PUBLIC] if user&.admin? with_feature_enabled(feature) @@ -450,6 +457,7 @@ class Project < ActiveRecord::Base scope :joins_import_state, -> { joins("LEFT JOIN project_mirror_data import_state ON import_state.project_id = projects.id") } scope :import_started, -> { joins_import_state.where("import_state.status = 'started' OR projects.import_status = 'started'") } + scope :for_group, -> (group) { where(group: group) } class << self # Searches for a list of projects based on the query given in `query`. @@ -1082,31 +1090,13 @@ class Project < ActiveRecord::Base end def find_or_initialize_services(exceptions: []) - services_templates = Service.where(template: true) - available_services_names = Service.available_services_names - exceptions available_services = available_services_names.map do |service_name| - service = find_service(services, service_name) - - if service - service - else - # We should check if template for the service exists - template = find_service(services_templates, service_name) - - if template.nil? - # If no template, we should create an instance. Ex `build_gitlab_ci_service` - public_send("build_#{service_name}_service") # rubocop:disable GitlabSecurity/PublicSend - else - Service.build_from_template(id, template) - end - end + find_or_initialize_service(service_name) end - available_services.reject do |service| - disabled_services.include?(service.to_param) - end + available_services.compact end def disabled_services @@ -1114,7 +1104,20 @@ class Project < ActiveRecord::Base end def find_or_initialize_service(name) - find_or_initialize_services.find { |service| service.to_param == name } + return if disabled_services.include?(name) + + service = find_service(services, name) + return service if service + + # We should check if template for the service exists + template = find_service(services_templates, name) + + if template + Service.build_from_template(id, template) + else + # If no template, we should create an instance. Ex `build_gitlab_ci_service` + public_send("build_#{name}_service") # rubocop:disable GitlabSecurity/PublicSend + end end # rubocop: disable CodeReuse/ServiceClass @@ -2277,4 +2280,8 @@ class Project < ActiveRecord::Base check_access.call end end + + def services_templates + @services_templates ||= Service.where(template: true) + end end diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index dc6736dd9cd..2253ad7b543 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -5,7 +5,8 @@ class ProjectAutoDevops < ActiveRecord::Base enum deploy_strategy: { continuous: 0, - manual: 1 + manual: 1, + timed_incremental: 2 } scope :enabled, -> { where(enabled: true) } @@ -30,10 +31,7 @@ class ProjectAutoDevops < ActiveRecord::Base value: domain.presence || instance_domain) end - if manual? - variables.append(key: 'STAGING_ENABLED', value: '1') - variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1') - end + variables.concat(deployment_strategy_default_variables) end end @@ -51,4 +49,16 @@ class ProjectAutoDevops < ActiveRecord::Base !project.public? && !project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present? end + + def deployment_strategy_default_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + if manual? + variables.append(key: 'STAGING_ENABLED', value: '1') + variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1') # deprecated + variables.append(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'manual') + elsif timed_incremental? + variables.append(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'timed') + end + end + end end diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 4a0324e8b5c..39f2b8fe0de 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -13,14 +13,16 @@ class ProjectFeature < ActiveRecord::Base # Disabled: not enabled for anyone # Private: enabled only for team members # Enabled: enabled for everyone able to access the project + # Public: enabled for everyone (only allowed for pages) # # Permission levels DISABLED = 0 PRIVATE = 10 ENABLED = 20 + PUBLIC = 30 - FEATURES = %i(issues merge_requests wiki snippets builds repository).freeze + FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze class << self def access_level_attribute(feature) @@ -46,6 +48,7 @@ class ProjectFeature < ActiveRecord::Base validates :project, presence: true validate :repository_children_level + validate :allowed_access_levels default_value_for :builds_access_level, value: ENABLED, allows_nil: false default_value_for :issues_access_level, value: ENABLED, allows_nil: false @@ -55,6 +58,9 @@ class ProjectFeature < ActiveRecord::Base default_value_for :repository_access_level, value: ENABLED, allows_nil: false def feature_available?(feature, user) + # This feature might not be behind a feature flag at all, so default to true + return false unless ::Feature.enabled?(feature, user, default_enabled: true) + get_permission(user, access_level(feature)) end @@ -78,6 +84,16 @@ class ProjectFeature < ActiveRecord::Base issues_access_level > DISABLED end + def pages_enabled? + pages_access_level > DISABLED + end + + def public_pages? + return true unless Gitlab.config.pages.access_control + + pages_access_level == PUBLIC || pages_access_level == ENABLED && project.public? + end + private # Validates builds and merge requests access level @@ -92,6 +108,17 @@ class ProjectFeature < ActiveRecord::Base %i(merge_requests_access_level builds_access_level).each(&validator) end + # Validates access level for other than pages cannot be PUBLIC + def allowed_access_levels + validator = lambda do |field| + level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend + not_allowed = level > ProjectFeature::ENABLED + self.errors.add(field, "cannot have public visibility level") if not_allowed + end + + (FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")} + end + def get_permission(user, level) case level when DISABLED @@ -100,6 +127,8 @@ class ProjectFeature < ActiveRecord::Base user && (project.team.member?(user) || user.full_private_access?) when ENABLED true + when PUBLIC + true else true end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 66012f0da99..a69b7b4c4b6 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -149,7 +149,7 @@ class HipchatService < Service context.merge!(options) - html = Banzai.post_process(Banzai.render(text, context), context) + html = Banzai.render_and_post_process(text, context) sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt]) sanitized_html.truncate(200, separator: ' ', omission: '...') diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb index 6fadbcefa53..d555ebe5322 100644 --- a/app/models/system_note_metadata.rb +++ b/app/models/system_note_metadata.rb @@ -9,6 +9,7 @@ class SystemNoteMetadata < ActiveRecord::Base TYPES_WITH_CROSS_REFERENCES = %w[ commit cross_reference close duplicate + moved ].freeze ICON_TYPES = %w[ @@ -26,4 +27,8 @@ class SystemNoteMetadata < ActiveRecord::Base def icon_types ICON_TYPES end + + def cross_reference_types + TYPES_WITH_CROSS_REFERENCES + end end diff --git a/app/models/todo.rb b/app/models/todo.rb index 265fb932f7c..7b64615f699 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -40,6 +40,13 @@ class Todo < ActiveRecord::Base scope :pending, -> { with_state(:pending) } scope :done, -> { with_state(:done) } + scope :for_action, -> (action) { where(action: action) } + scope :for_author, -> (author) { where(author: author) } + scope :for_project, -> (project) { where(project: project) } + scope :for_group, -> (group) { where(group: group) } + scope :for_type, -> (type) { where(target_type: type) } + scope :for_target, -> (id) { where(target_id: id) } + scope :for_commit, -> (id) { where(commit_id: id) } state_machine :state, initial: :pending do event :done do @@ -53,6 +60,42 @@ class Todo < ActiveRecord::Base after_save :keep_around_commit, if: :commit_id class << self + # Returns all todos for the given group and its descendants. + # + # group - A `Group` to retrieve todos for. + # + # Returns an `ActiveRecord::Relation`. + def for_group_and_descendants(group) + groups = group.self_and_descendants + + from_union([ + for_project(Project.for_group(groups)), + for_group(groups) + ]) + end + + # Returns `true` if the current user has any todos for the given target. + # + # target - The value of the `target_type` column, such as `Issue`. + def any_for_target?(target) + exists?(target: target) + end + + # Updates the state of a relation of todos to the new state. + # + # new_state - The new state of the todos. + # + # Returns an `Array` containing the IDs of the updated todos. + def update_state(new_state) + # Only update those that are not really on that state + base = where.not(state: new_state).except(:order) + ids = base.pluck(:id) + + base.update_all(state: new_state) + + ids + end + # Priority sorting isn't displayed in the dropdown, because we don't show # milestones, but still show something if the user has a URL with that # selected. diff --git a/app/models/user.rb b/app/models/user.rb index cd3b1c95b7e..8a7acfb73b1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -265,6 +265,7 @@ class User < ActiveRecord::Base scope :order_oldest_sign_in, -> { reorder(Gitlab::Database.nulls_last_order('current_sign_in_at', 'ASC')) } scope :confirmed, -> { where.not(confirmed_at: nil) } scope :by_username, -> (usernames) { iwhere(username: usernames) } + scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) } # Limits the users to those that have TODOs, optionally in the given state. # @@ -1366,6 +1367,10 @@ class User < ActiveRecord::Base !consented_usage_stats? && 7.days.ago > self.created_at && !has_current_license? && User.single_user? end + def todos_limited_to(ids) + todos.where(id: ids) + end + # @deprecated alias_method :owned_or_masters_groups, :owned_or_maintainers_groups diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index 1cd05cf3aac..2c0e8659fc1 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -6,7 +6,8 @@ class UserCallout < ActiveRecord::Base enum feature_name: { gke_cluster_integration: 1, gcp_signup_offer: 2, - cluster_security_warning: 3 + cluster_security_warning: 3, + gold_trial: 4 } validates :user, presence: true diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index d0e84b1aa38..a76a083bceb 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -110,6 +110,7 @@ class ProjectPolicy < BasePolicy snippets wiki builds + pages ] features.each do |f| @@ -167,6 +168,7 @@ class ProjectPolicy < BasePolicy enable :upload_file enable :read_cycle_analytics enable :award_emoji + enable :read_pages_content end # These abilities are not allowed to admins that are not members of the project, @@ -286,6 +288,8 @@ class ProjectPolicy < BasePolicy prevent(*create_read_update_admin_destroy(:merge_request)) end + rule { pages_disabled }.prevent :read_pages_content + rule { issues_disabled & merge_requests_disabled }.policy do prevent(*create_read_update_admin_destroy(:label)) prevent(*create_read_update_admin_destroy(:milestone)) @@ -345,6 +349,7 @@ class ProjectPolicy < BasePolicy enable :download_code enable :download_wiki_code enable :read_cycle_analytics + enable :read_pages_content # NOTE: may be overridden by IssuePolicy enable :read_issue @@ -390,7 +395,11 @@ class ProjectPolicy < BasePolicy greedy_load_subject ||= !@user.persisted? if greedy_load_subject - project.team.members.include?(user) + # We want to load all the members with one query. Calling #include? on + # project.team.members will perform a separate query for each user, unless + # project.team.members was loaded before somewhere else. Calling #to_a + # ensures it's always loaded before checking for membership. + project.team.members.to_a.include?(user) else # otherwise we just make a specific query for # this particular user. diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index c85b1790e73..3d508a9a407 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true class BuildDetailsEntity < JobEntity - include EnvironmentHelper - include RequestAwareEntity - include CiStatusHelper - expose :coverage, :erased_at, :duration expose :tag_list, as: :tags expose :has_trace?, as: :has_trace @@ -15,10 +11,6 @@ class BuildDetailsEntity < JobEntity expose :deployment_status, if: -> (*) { build.has_environment? } do expose :deployment_status, as: :status - expose :icon do |build| - ci_label_for_status(build.status) - end - expose :persisted_environment, as: :environment, with: EnvironmentEntity end diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb index 396e95a03c8..a94e32478ce 100644 --- a/app/serializers/commit_entity.rb +++ b/app/serializers/commit_entity.rb @@ -25,4 +25,25 @@ class CommitEntity < API::Entities::Commit expose :title_html, if: { type: :full } do |commit| markdown_field(commit, :title) end + + expose :signature_html, if: { type: :full } do |commit| + render('projects/commit/_signature', signature: commit.signature) if commit.has_signature? + end + + expose :pipeline_status_path, if: { type: :full } do |commit, options| + pipeline_ref = options[:pipeline_ref] + pipeline_project = options[:pipeline_project] || commit.project + next unless pipeline_ref && pipeline_project + + status = commit.status_for_project(pipeline_ref, pipeline_project) + next unless status + + pipelines_project_commit_path(pipeline_project, commit.id, ref: pipeline_ref) + end + + def render(*args) + return unless request.respond_to?(:render) && request.render.respond_to?(:call) + + request.render.call(*args) + end end diff --git a/app/serializers/detailed_status_entity.rb b/app/serializers/detailed_status_entity.rb index c772c807f76..da994d78286 100644 --- a/app/serializers/detailed_status_entity.rb +++ b/app/serializers/detailed_status_entity.rb @@ -10,7 +10,12 @@ class DetailedStatusEntity < Grape::Entity expose :illustration do |status| begin - status.illustration + illustration = { + image: ActionController::Base.helpers.image_path(status.illustration[:image]) + } + illustration = status.illustration.merge(illustration) + + illustration rescue NotImplementedError # ignored end @@ -25,5 +30,6 @@ class DetailedStatusEntity < Grape::Entity expose :action_title, as: :title expose :action_path, as: :path expose :action_method, as: :method + expose :action_button_title, as: :button_title end end diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb index c193ed10fef..63ea8e8f95f 100644 --- a/app/serializers/diff_file_entity.rb +++ b/app/serializers/diff_file_entity.rb @@ -116,6 +116,10 @@ class DiffFileEntity < Grape::Entity project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path)) end + expose :viewer, using: DiffViewerEntity do |diff_file| + diff_file.rich_viewer || diff_file.simple_viewer + end + expose :replaced_view_path, if: -> (_, options) { options[:merge_request] } do |diff_file| image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image' image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha diff --git a/app/serializers/diff_viewer_entity.rb b/app/serializers/diff_viewer_entity.rb new file mode 100644 index 00000000000..27fba03cb3f --- /dev/null +++ b/app/serializers/diff_viewer_entity.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class DiffViewerEntity < Grape::Entity + # Partial name refers directly to a Rails feature, let's avoid + # using this on the frontend. + expose :partial_name, as: :name +end diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb index 00dc55fc004..b51e4a7e6d0 100644 --- a/app/serializers/diffs_entity.rb +++ b/app/serializers/diffs_entity.rb @@ -18,7 +18,9 @@ class DiffsEntity < Grape::Entity expose :commit do |diffs, options| CommitEntity.represent options[:commit], options.merge( type: :full, - commit_url_params: { merge_request_iid: merge_request&.iid } + commit_url_params: { merge_request_iid: merge_request&.iid }, + pipeline_ref: merge_request&.source_branch, + pipeline_project: merge_request&.source_project ) end diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb index ebe76c9fcda..b6786a0d597 100644 --- a/app/serializers/discussion_entity.rb +++ b/app/serializers/discussion_entity.rb @@ -27,7 +27,7 @@ class DiscussionEntity < Grape::Entity expose :resolved?, as: :resolved expose :resolved_by_push?, as: :resolved_by_push - expose :resolved_by + expose :resolved_by, using: NoteUserEntity expose :resolved_at expose :resolve_path, if: -> (d, _) { d.resolvable? } do |discussion| resolve_project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion.id) diff --git a/app/services/issues/related_branches_service.rb b/app/services/issues/related_branches_service.rb new file mode 100644 index 00000000000..76af482b7ac --- /dev/null +++ b/app/services/issues/related_branches_service.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# This service fetches all branches containing the current issue's ID, except for +# those with a merge request open referencing the current issue. +module Issues + class RelatedBranchesService < Issues::BaseService + def execute(issue) + branches_with_iid_of(issue) - branches_with_merge_request_for(issue) + end + + private + + def branches_with_merge_request_for(issue) + Issues::ReferencedMergeRequestsService + .new(project, current_user) + .referenced_merge_requests(issue) + .map(&:source_branch) + end + + def branches_with_iid_of(issue) + project.repository.branch_names.select do |branch| + branch =~ /\A#{issue.iid}-(?!\d+-stable)/i + end + end + end +end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index aa5d8406d0f..28c3219b37b 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -57,10 +57,10 @@ module MergeRequests # Returns all origin and fork merge requests from `@project` satisfying passed arguments. # rubocop: disable CodeReuse/ActiveRecord def merge_requests_for(source_branch, mr_states: [:opened]) - MergeRequest + @project.source_of_merge_requests .with_state(mr_states) - .where(source_branch: source_branch, source_project_id: @project.id) - .preload(:source_project) # we don't need a #includes since we're just preloading for the #select + .where(source_branch: source_branch) + .preload(:source_project) # we don't need #includes since we're just preloading for the #select .select(&:source_project) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index bcdd752ddc4..b03d14fa3cc 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -3,16 +3,16 @@ module MergeRequests class RefreshService < MergeRequests::BaseService def execute(oldrev, newrev, ref) - return true unless Gitlab::Git.branch_ref?(ref) + push = Gitlab::Git::Push.new(@project, oldrev, newrev, ref) + return true unless push.branch_push? - do_execute(oldrev, newrev, ref) + refresh_merge_requests!(push) end private - def do_execute(oldrev, newrev, ref) - @oldrev, @newrev = oldrev, newrev - @branch_name = Gitlab::Git.ref_name(ref) + def refresh_merge_requests!(push) + @push = push Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits)) # Be sure to close outstanding MRs before reloading them to avoid generating an @@ -25,7 +25,7 @@ module MergeRequests cache_merge_requests_closing_issues # Leave a system note if a branch was deleted/added - if branch_added? || branch_removed? + if @push.branch_added? || @push.branch_removed? comment_mr_branch_presence_changed end @@ -54,8 +54,10 @@ module MergeRequests # rubocop: disable CodeReuse/ActiveRecord def post_merge_manually_merged commit_ids = @commits.map(&:id) - merge_requests = @project.merge_requests.preload(:latest_merge_request_diff).opened.where(target_branch: @branch_name).to_a - merge_requests = merge_requests.select(&:diff_head_commit) + merge_requests = @project.merge_requests.opened + .preload(:latest_merge_request_diff) + .where(target_branch: @push.branch_name).to_a + .select(&:diff_head_commit) merge_requests = merge_requests.select do |merge_request| commit_ids.include?(merge_request.diff_head_sha) && @@ -70,24 +72,20 @@ module MergeRequests end # rubocop: enable CodeReuse/ActiveRecord - def force_push? - Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev) - end - # Refresh merge request diff if we push to source or target branch of merge request # Note: we should update merge requests from forks too # rubocop: disable CodeReuse/ActiveRecord def reload_merge_requests merge_requests = @project.merge_requests.opened - .by_source_or_target_branch(@branch_name).to_a + .by_source_or_target_branch(@push.branch_name).to_a # Fork merge requests merge_requests += MergeRequest.opened - .where(source_branch: @branch_name, source_project: @project) + .where(source_branch: @push.branch_name, source_project: @project) .where.not(target_project: @project).to_a filter_merge_requests(merge_requests).each do |merge_request| - if merge_request.source_branch == @branch_name || force_push? + if merge_request.source_branch == @push.branch_name || @push.force_push? merge_request.reload_diff(current_user) else mr_commit_ids = merge_request.commit_shas @@ -117,7 +115,7 @@ module MergeRequests end def find_new_commits - if branch_added? + if @push.branch_added? @commits = [] merge_request = merge_requests_for_source_branch.first @@ -126,28 +124,28 @@ module MergeRequests begin # Since any number of commits could have been made to the restored branch, # find the common root to see what has been added. - common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @newrev) + common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @push.newrev) # If the a commit no longer exists in this repo, gitlab_git throws # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 - @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref + @commits = @project.repository.commits_between(common_ref, @push.newrev) if common_ref rescue end - elsif branch_removed? + elsif @push.branch_removed? # No commits for a deleted branch. @commits = [] else - @commits = @project.repository.commits_between(@oldrev, @newrev) + @commits = @project.repository.commits_between(@push.oldrev, @push.newrev) end end # Add comment about branches being deleted or added to merge requests def comment_mr_branch_presence_changed - presence = branch_added? ? :add : :delete + presence = @push.branch_added? ? :add : :delete merge_requests_for_source_branch.each do |merge_request| SystemNoteService.change_branch_presence( merge_request, merge_request.project, @current_user, - :source, @branch_name, presence) + :source, @push.branch_name, presence) end end @@ -164,7 +162,7 @@ module MergeRequests SystemNoteService.add_commits(merge_request, merge_request.project, @current_user, new_commits, - existing_commits, @oldrev) + existing_commits, @push.oldrev) notification_service.push_to_merge_request(merge_request, @current_user, new_commits: new_commits, existing_commits: existing_commits) end @@ -195,7 +193,7 @@ module MergeRequests # Call merge request webhook with update branches def execute_mr_web_hooks merge_requests_for_source_branch.each do |merge_request| - execute_hooks(merge_request, 'update', old_rev: @oldrev) + execute_hooks(merge_request, 'update', old_rev: @push.oldrev) end end @@ -203,7 +201,7 @@ module MergeRequests # `MergeRequestsClosingIssues` model (as a performance optimization). # rubocop: disable CodeReuse/ActiveRecord def cache_merge_requests_closing_issues - @project.merge_requests.where(source_branch: @branch_name).each do |merge_request| + @project.merge_requests.where(source_branch: @push.branch_name).each do |merge_request| merge_request.cache_merge_request_closes_issues!(@current_user) end end @@ -215,15 +213,7 @@ module MergeRequests def merge_requests_for_source_branch(reload: false) @source_merge_requests = nil if reload - @source_merge_requests ||= merge_requests_for(@branch_name) - end - - def branch_added? - Gitlab::Git.blank_ref?(@oldrev) - end - - def branch_removed? - Gitlab::Git.blank_ref?(@newrev) + @source_merge_requests ||= merge_requests_for(@push.branch_name) end end end diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 5286b92ab6b..61f6402a810 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -2,6 +2,7 @@ module Projects class AutocompleteService < BaseService + include LabelsAsHash def issues IssuesFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title]) end @@ -22,34 +23,18 @@ module Projects MergeRequestsFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title]) end - def labels_as_hash(target = nil) - available_labels = LabelsFinder.new( - current_user, - project_id: project.id, - include_ancestor_groups: true - ).execute - - label_hashes = available_labels.as_json(only: [:title, :color]) - - if target&.respond_to?(:labels) - already_set_labels = available_labels & target.labels - if already_set_labels.present? - titles = already_set_labels.map(&:title) - label_hashes.each do |hash| - if titles.include?(hash['title']) - hash[:set] = true - end - end - end - end - - label_hashes - end - def commands(noteable, type) return [] unless noteable QuickActions::InterpretService.new(project, current_user).available_commands(noteable) end + + def snippets + SnippetsFinder.new(current_user, project: project).execute.select([:id, :title]) + end + + def labels_as_hash(target) + super(target, project_id: project.id, include_ancestor_groups: true) + end end end diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index efbd4c7b323..abf40b3ad7a 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -21,7 +21,9 @@ module Projects def pages_config { domains: pages_domains_config, - https_only: project.pages_https_only? + https_only: project.pages_https_only?, + id: project.project_id, + access_control: !project.public_pages? } end @@ -31,7 +33,9 @@ module Projects domain: domain.domain, certificate: domain.certificate, key: domain.key, - https_only: project.pages_https_only? && domain.https? + https_only: project.pages_https_only? && domain.https?, + id: project.project_id, + access_control: !project.public_pages? } end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index d6d9bacf232..f25a4e30938 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -72,7 +72,11 @@ module Projects system_hook_service.execute_hooks_for(project, :update) end - update_pages_config if changing_pages_https_only? + update_pages_config if changing_pages_related_config? + end + + def changing_pages_related_config? + changing_pages_https_only? || changing_pages_access_level? end def update_failed! @@ -102,6 +106,10 @@ module Projects params.dig(:project_feature_attributes, :wiki_access_level).to_i > ProjectFeature::DISABLED end + def changing_pages_access_level? + params.dig(:project_feature_attributes, :pages_access_level) + end + def ensure_wiki_exists ProjectWiki.new(project, project.owner).wiki rescue ProjectWiki::CouldNotCreateWikiError diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 8933bef29ee..defa579f9a8 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -210,9 +210,14 @@ module QuickActions end params '~label1 ~"label 2"' condition do - available_labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true).execute + if project + available_labels = LabelsFinder + .new(current_user, project_id: project.id, include_ancestor_groups: true) + .execute + end - current_user.can?(:"admin_#{issuable.to_ability_name}", project) && + project && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) && available_labels.any? end command :label do |labels_param| @@ -286,7 +291,8 @@ module QuickActions end params '#issue | !merge_request' condition do - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + [MergeRequest, Issue].include?(issuable.class) && + current_user.can?(:"update_#{issuable.to_ability_name}", issuable) end parse_params do |issuable_param| extract_references(issuable_param, :issue).first || @@ -443,7 +449,8 @@ module QuickActions end params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>' condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) + issuable.is_a?(TimeTrackable) && + current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) end parse_params do |raw_time_date| Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute @@ -493,7 +500,7 @@ module QuickActions desc "Lock the discussion" explanation "Locks the discussion" condition do - issuable.is_a?(Issuable) && + [MergeRequest, Issue].include?(issuable.class) && issuable.persisted? && !issuable.discussion_locked? && current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) @@ -505,7 +512,7 @@ module QuickActions desc "Unlock the discussion" explanation "Unlocks the discussion" condition do - issuable.is_a?(Issuable) && + [MergeRequest, Issue].include?(issuable.class) && issuable.persisted? && issuable.discussion_locked? && current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 4fe6c1ec986..f357dc37fe7 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -41,15 +41,13 @@ class TodoService # collects the todo users before the todos themselves are deleted, then # updates the todo counts for those users. # - # rubocop: disable CodeReuse/ActiveRecord def destroy_target(target) - todo_users = User.where(id: target.todos.pending.select(:user_id)).to_a + todo_users = UsersWithPendingTodosFinder.new(target).execute.to_a yield target todo_users.each(&:update_todos_count_cache) end - # rubocop: enable CodeReuse/ActiveRecord # When we reassign an issue we should: # @@ -200,30 +198,23 @@ class TodoService create_todos(current_user, attributes) end - # rubocop: disable CodeReuse/ActiveRecord def todo_exist?(issuable, current_user) - TodosFinder.new(current_user).execute.exists?(target: issuable) + TodosFinder.new(current_user).any_for_target?(issuable) end - # rubocop: enable CodeReuse/ActiveRecord private - # rubocop: disable CodeReuse/ActiveRecord def todos_by_ids(ids, current_user) - current_user.todos.where(id: Array(ids)) + current_user.todos_limited_to(Array(ids)) end - # rubocop: enable CodeReuse/ActiveRecord - # rubocop: disable CodeReuse/ActiveRecord def update_todos_state(todos, current_user, state) - # Only update those that are not really on that state - todos = todos.where.not(state: state) - todos_ids = todos.pluck(:id) - todos.unscope(:order).update_all(state: state) + todos_ids = todos.update_state(state) + current_user.update_todos_count_cache + todos_ids end - # rubocop: enable CodeReuse/ActiveRecord def create_todos(users, attributes) Array(users).map do |user| @@ -348,10 +339,7 @@ class TodoService end end - # rubocop: disable CodeReuse/ActiveRecord def pending_todos(user, criteria = {}) - valid_keys = [:project_id, :target_id, :target_type, :commit_id] - user.todos.pending.where(criteria.slice(*valid_keys)) + PendingTodosFinder.new(user, criteria).execute end - # rubocop: enable CodeReuse/ActiveRecord end diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index ccba1c461fc..fefb4c7455d 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,6 +1,8 @@ - add_to_breadcrumbs "Projects", admin_projects_path - breadcrumb_title @project.full_name - page_title @project.full_name, "Projects" +- @content_class = "admin-projects" + %h3.page-title Project: #{@project.full_name} = link_to edit_project_path(@project), class: "btn btn-nr float-right" do @@ -110,6 +112,8 @@ = visibility_level_icon(@project.visibility_level) = visibility_level_label(@project.visibility_level) + = render_if_exists 'admin/projects/geo_status_widget', locals: { project: @project } + .card .card-header Transfer project diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index a5326f4b909..e9e4e0847d3 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -2,106 +2,103 @@ - @no_container = true %div{ class: container_class } - .bs-callout - %p - = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.") - %br - = _('Runners can be placed on separate users, servers, even on your local machine.') - %br + .row + .col-sm-6 + .bs-callout + %p + = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.") + %br + = _('Runners can be placed on separate users, servers, even on your local machine.') + %br - %div - %span= _('Each Runner can be in one of the following states:') - %ul - %li - %span.badge.badge-success shared - \- - = _('Runner runs jobs from all unassigned projects') - %li - %span.badge.badge-success group - \- - = _('Runner runs jobs from all unassigned projects in its group') - %li - %span.badge.badge-info specific - \- - = _('Runner runs jobs from assigned projects') - %li - %span.badge.badge-warning locked - \- - = _('Runner cannot be assigned to other projects') - %li - %span.badge.badge-danger paused - \- - = _('Runner will not receive any new jobs') + %div + %span= _('Each Runner can be in one of the following states:') + %ul + %li + %span.badge.badge-success shared + \- + = _('Runner runs jobs from all unassigned projects') + %li + %span.badge.badge-success group + \- + = _('Runner runs jobs from all unassigned projects in its group') + %li + %span.badge.badge-info specific + \- + = _('Runner runs jobs from assigned projects') + %li + %span.badge.badge-warning locked + \- + = _('Runner cannot be assigned to other projects') + %li + %span.badge.badge-danger paused + \- + = _('Runner will not receive any new jobs') - .bs-callout.clearfix - .float-left - %p - = _('You can reset runners registration token by pressing a button below.') - .prepend-top-10 - = button_to _('Reset runners registration token'), reset_runners_token_admin_application_settings_path, - method: :put, class: 'btn btn-default', - data: { confirm: _('Are you sure you want to reset registration token?') } + .col-sm-6 + .bs-callout + = render partial: 'ci/runner/how_to_setup_runner', + locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, + type: 'shared', + reset_token_url: reset_registration_token_admin_application_settings_path } - = render partial: 'ci/runner/how_to_setup_shared_runner', - locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token } + .row + .col-sm-9 + = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do + .filtered-search-wrapper + .filtered-search-box + = dropdown_tag(custom_icon('icon_history'), + options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', + toggle_class: 'filtered-search-history-dropdown-toggle-button', + dropdown_class: 'filtered-search-history-dropdown', + content_class: 'filtered-search-history-dropdown-content', + title: _('Recent searches') }) do + .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } + .filtered-search-box-input-container.droplab-dropdown + .scroll-container + %ul.tokens-container.list-unstyled + %li.input-token + %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } } + #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown + %ul{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { action: 'submit' } } + = button_tag class: %w[btn btn-link] do + = sprite_icon('search') + %span + = _('Press Enter or click to search') + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + = button_tag class: %w[btn btn-link] do + -# Encapsulate static class name `{{icon}}` inside #{} to bypass + -# haml lint's ClassAttributeWithStaticValue + %svg + %use{ 'xlink:href': "#{'{{icon}}'}" } + %span.js-filter-hint + {{hint}} + %span.js-filter-tag.dropdown-light-content + {{tag}} - .bs-callout - %p - = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } + #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_STATUSES.each do |status| + %li.filter-dropdown-item{ data: { value: status } } + = button_tag class: %w[btn btn-link] do + = status.titleize - .row-content-block.second-block - = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do - .filtered-search-wrapper - .filtered-search-box - = dropdown_tag(custom_icon('icon_history'), - options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', - toggle_class: 'filtered-search-history-dropdown-toggle-button', - dropdown_class: 'filtered-search-history-dropdown', - content_class: 'filtered-search-history-dropdown-content', - title: _('Recent searches') }) do - .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } - .filtered-search-box-input-container.droplab-dropdown - .scroll-container - %ul.tokens-container.list-unstyled - %li.input-token - %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } } - #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown - %ul{ data: { dropdown: true } } - %li.filter-dropdown-item{ data: { action: 'submit' } } - = button_tag class: %w[btn btn-link] do - = sprite_icon('search') - %span - = _('Press Enter or click to search') - %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } - %li.filter-dropdown-item - = button_tag class: %w[btn btn-link] do - -# Encapsulate static class name `{{icon}}` inside #{} to bypass - -# haml lint's ClassAttributeWithStaticValue - %svg - %use{ 'xlink:href': "#{'{{icon}}'}" } - %span.js-filter-hint - {{hint}} - %span.js-filter-tag.dropdown-light-content - {{tag}} + #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| + %li.filter-dropdown-item{ data: { value: runner_type } } + = button_tag class: %w[btn btn-link] do + = runner_type.titleize - #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - - Ci::Runner::AVAILABLE_STATUSES.each do |status| - %li.filter-dropdown-item{ data: { value: status } } - = button_tag class: %w[btn btn-link] do - = status.titleize + = button_tag class: %w[clear-search hidden] do + = icon('times') + .filter-dropdown-container + = render 'sort_dropdown' - #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| - %li.filter-dropdown-item{ data: { value: runner_type } } - = button_tag class: %w[btn btn-link] do - = runner_type.titleize - - = button_tag class: %w[clear-search hidden] do - = icon('times') - .filter-dropdown-container - = render 'sort_dropdown' + .col-sm-3.text-right-lg + = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } - if @runners.any? .runners-content.content-list diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml index b1b142460b0..4307060d748 100644 --- a/app/views/ci/runner/_how_to_setup_runner.html.haml +++ b/app/views/ci/runner/_how_to_setup_runner.html.haml @@ -13,5 +13,9 @@ = _("Use the following registration token during setup:") %code#registration_token= registration_token = clipboard_button(target: '#registration_token', title: _("Copy token to clipboard"), class: "btn-transparent btn-clipboard") + .prepend-top-10.append-bottom-10 + = button_to _("Reset runners registration token"), reset_token_url, + method: :put, class: 'btn btn-default', + data: { confirm: _("Are you sure you want to reset registration token?") } %li = _("Start the Runner!") diff --git a/app/views/ci/runner/_how_to_setup_shared_runner.html.haml b/app/views/ci/runner/_how_to_setup_shared_runner.html.haml deleted file mode 100644 index 2a190cb9250..00000000000 --- a/app/views/ci/runner/_how_to_setup_shared_runner.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -.bs-callout.help-callout - = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: registration_token, type: 'shared' } diff --git a/app/views/ci/runner/_how_to_setup_specific_runner.html.haml b/app/views/ci/runner/_how_to_setup_specific_runner.html.haml deleted file mode 100644 index afe57bdfa01..00000000000 --- a/app/views/ci/runner/_how_to_setup_specific_runner.html.haml +++ /dev/null @@ -1,26 +0,0 @@ -.bs-callout.help-callout - .append-bottom-10 - %h4= _('Set up a specific Runner automatically') - - %p - - link_to_help_page = link_to(_('Learn more about Kubernetes'), - help_page_path('user/project/clusters/index'), - target: '_blank', - rel: 'noopener noreferrer') - - = _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page } - - %ol - %li - = _('Click the button below to begin the install process by navigating to the Kubernetes page') - %li - = _('Select an existing Kubernetes cluster or create a new one') - %li - = _('From the Kubernetes cluster details view, install Runner from the applications list') - - = link_to _('Install Runner on Kubernetes'), - project_clusters_path(@project), - class: 'btn btn-info' - %hr - = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: registration_token, type: 'specific' } diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index 31d4b3da4f1..3cee5841bbc 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -4,6 +4,9 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - page_title "Activity" - header_title "Activity", activity_dashboard_path diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index 50f39f93283..985928305a2 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -3,6 +3,9 @@ - header_title "Groups", dashboard_groups_path = render 'dashboard/groups_head' += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if params[:filter].blank? && @groups.empty? = render 'shared/groups/empty_state' - else diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 86a21e24ac9..91f58ddcfcc 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -4,6 +4,9 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues") += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + .top-area = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set .nav-controls diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 61aae31be60..27f53a8d1c6 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -2,6 +2,9 @@ - page_title _("Merge Requests") - @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id) += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + .top-area = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set .nav-controls diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index deed774a4a5..f0d16936a51 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -4,6 +4,9 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 8933d9e31ff..42638b8528d 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -4,6 +4,9 @@ - page_title "Starred Projects" - header_title "Projects", dashboard_projects_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + %div{ class: container_class } = render "projects/last_push" = render 'dashboard/projects_head' diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 8b3974d97f8..bbfa4cc7413 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -2,6 +2,9 @@ - page_title "Todos" - header_title "Todos", dashboard_todos_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user.todos.any? .top-area %ul.nav-links.mobile-separator.nav.nav-tabs diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 387c37b7a91..1d8b9c5bc8f 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -2,6 +2,9 @@ - page_title _("Groups") - header_title _("Groups"), dashboard_groups_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user = render 'dashboard/groups_head' - else diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 452f390695c..16be5791f83 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -2,6 +2,9 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user = render 'dashboard/projects_head' - else diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index 452f390695c..16be5791f83 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -2,6 +2,9 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user = render 'dashboard/projects_head' - else diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 452f390695c..16be5791f83 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -2,6 +2,9 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user = render 'dashboard/projects_head' - else diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 003bd25dd06..5b78ce910b8 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -3,29 +3,23 @@ - can_admin_label = can?(current_user, :admin_label, @group) - issuables = ['issues', 'merge requests'] - search = params[:search] +- subscribed = params[:subscribed] +- labels_or_filters = @labels.exists? || search.present? || subscribed.present? - if can_admin_label - content_for(:header_content) do .nav-controls = link_to _('New label'), new_group_label_path(@group), class: "btn btn-success" -- if @labels.exists? || search.present? +- if labels_or_filters #promote-label-modal %div{ class: container_class } - .top-area.adjust - .nav-text - = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence } - .nav-controls - = form_tag group_labels_path(@group), method: :get do - .input-group - = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false } - %span.input-group-append - %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') } - = icon("search") - = render 'shared/labels/sort_dropdown' + = render 'shared/labels/nav' .labels-container.prepend-top-5 - if @labels.any? + .text-muted + = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence } .other-labels %h5= _('Labels') %ul.content-list.manage-labels-list.js-other-labels @@ -34,6 +28,9 @@ - elsif search.present? .nothing-here-block = _('No labels with such name or description') + - elsif subscribed.present? + .nothing-here-block + = _('You do not have any subscriptions yet') - else = render 'shared/empty_states/labels' diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml index e6c089c3494..bcfb6d99716 100644 --- a/app/views/groups/runners/_group_runners.html.haml +++ b/app/views/groups/runners/_group_runners.html.haml @@ -11,7 +11,9 @@ -# https://gitlab.com/gitlab-org/gitlab-ce/issues/45894 - if can?(current_user, :admin_pipeline, @group) = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: @group.runners_token, type: 'group' } + locals: { registration_token: @group.runners_token, + type: 'group', + reset_token_url: reset_registration_token_group_settings_ci_cd_path } - if @group.runners.empty? %h4.underlined-title diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml index b7c673db705..3814d45929d 100644 --- a/app/views/groups/settings/_advanced.html.haml +++ b/app/views/groups/settings/_advanced.html.haml @@ -12,8 +12,8 @@ .group-root-path.input-group-prepend.has-tooltip{ title: group_path(@group), :'data-placement' => 'bottom' } .input-group-text %span>= root_url - - if parent - %strong= parent.full_path + '/' + - if @group.parent + %strong= @group.parent.full_path + '/' = f.hidden_field :parent_id = f.text_field :path, placeholder: 'open-source', class: 'form-control', autofocus: local_assigns[:autofocus] || false, required: true, diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml index 9ed05d6e3d0..261d758622b 100644 --- a/app/views/layouts/header/_current_user_dropdown.html.haml +++ b/app/views/layouts/header/_current_user_dropdown.html.haml @@ -5,7 +5,14 @@ .user-name.bold = current_user.name = current_user.to_reference + - if current_user.status + .user-status-emoji.str-truncated.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } } + = emoji_icon current_user.status.emoji + = current_user.status.message_html.html_safe %li.divider + - if can?(current_user, :update_user_status, current_user) + %li + .js-set-status-modal-trigger{ data: { has_status: current_user.status.present? ? 'true' : 'false' } } - if current_user_menu?(:profile) %li = link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 044b49c12cc..39604611440 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -74,3 +74,6 @@ %span.sr-only= _('Toggle navigation') = sprite_icon('ellipsis_h', size: 12, css_class: 'more-icon js-navbar-toggle-right') = sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left') + +- if can?(current_user, :update_user_status, current_user) + .js-set-status-modal-wrapper{ data: { current_emoji: current_user.status.present? ? current_user.status.emoji : '', current_message: current_user.status.present? ? current_user.status.message : '' } } diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index f53bd2b5e4d..c35451827c8 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -1,6 +1,7 @@ - container = @no_breadcrumb_container ? 'container-fluid' : container_class - hide_top_links = @hide_top_links || false += yield :above_breadcrumbs_content %nav.breadcrumbs{ role: "navigation", class: [container, @content_class] } .breadcrumbs-container - if defined?(@left_sidebar) diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 25cd53b378a..48025f9bd20 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -216,7 +216,7 @@ = _('Metrics') = nav_link(controller: :environments, action: [:index, :folder, :show, :new, :edit, :create, :update, :stop, :terminal]) do - = link_to project_environments_path(@project), title: _('Environments'), class: 'shortcuts-environments' do + = link_to project_environments_path(@project), title: _('Environments'), class: 'shortcuts-environments qa-operations-environments-link' do %span = _('Environments') diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml index fb4fff12027..759d39cf5f5 100644 --- a/app/views/profiles/two_factor_auths/_codes.html.haml +++ b/app/views/profiles/two_factor_auths/_codes.html.haml @@ -1,5 +1,5 @@ %p.slead - Should you ever lose your phone, each of these recovery codes can be used one + Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %b will lose access to your account. diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index cd10b8758f6..94ec0cc5db8 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -6,13 +6,13 @@ .row.prepend-top-default .col-lg-4 %h4.prepend-top-0 - Register Two-Factor Authentication App + Register Two-Factor Authenticator %p - Use an app on your mobile device to enable two-factor authentication (2FA). + Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA). .col-lg-8 - if current_user.two_factor_otp_enabled? %p - You've already enabled two-factor authentication using mobile authenticator applications. In order to register a different device, you must first disable two-factor authentication. + You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication. %p If you lose your recovery codes you can generate new ones, invalidating all previous codes. %div diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 5436806162d..f398d97028b 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -26,6 +26,7 @@ = markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "* ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a bullet list") }) = markdown_toolbar_button({ icon: "list-numbered", data: { "md-tag" => "1. ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a numbered list") }) = markdown_toolbar_button({ icon: "task-done", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a task list") }) + = markdown_toolbar_button({ icon: "table", data: { "md-tag" => "| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |", "md-prepend" => true }, title: s_("MarkdownToolbar|Add a table") }) %button.toolbar-btn.toolbar-fullscreen-btn.js-zen-enter.has-tooltip{ type: "button", tabindex: -1, "aria-label": "Go full screen", title: s_("MarkdownToolbar|Go full screen"), data: { container: "body" } } = sprite_icon("screen-full") diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index e1f28364e19..ba7d3154326 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -33,7 +33,7 @@ .form-group.project-path.col-sm-6 = f.label :path, class: 'label-bold' do %span= _("Project slug") - = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, required: true + = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", required: true - if current_user.can_create_group? .form-text.text-muted Want to house several dependent projects under the same namespace? @@ -61,5 +61,5 @@ .option-description Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository. -= f.submit 'Create project', class: "btn btn-success project-submit", tabindex: 4, data: { track_label: "#{track_label}", track_event: "click_button", track_property: "create_project", track_value: "" } += f.submit 'Create project', class: "btn btn-success project-submit", data: { track_label: "#{track_label}", track_event: "click_button", track_property: "create_project", track_value: "" } = link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel" } diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 59c297c46a5..95828626bd9 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -101,7 +101,7 @@ = sprite_icon('download') - if can?(current_user, :update_build, job) - if job.active? - = link_to cancel_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do + = link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - elsif job.scheduled? .btn-group diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml index 73cfea0ef92..141314b4e4e 100644 --- a/app/views/projects/clusters/_banner.html.haml +++ b/app/views/projects/clusters/_banner.html.haml @@ -9,7 +9,7 @@ = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details") - if show_cluster_security_warning? - .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning - %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } × + .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning{ data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } + %button.close.js-close{ type: "button" } × = s_("ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application.") = link_to s_("More information"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications') diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml index 73b11d509d3..85d1002243b 100644 --- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml +++ b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml @@ -1,6 +1,6 @@ - link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') -.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' } - %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } × +.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } + %button.close.js-close{ type: "button" } × .gcp-signup-offer--content .gcp-signup-offer--icon.append-right-8 = sprite_icon("information", size: 16) diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml index 0222bbf7338..171ceeceb68 100644 --- a/app/views/projects/clusters/gcp/_form.html.haml +++ b/app/views/projects/clusters/gcp/_form.html.haml @@ -61,15 +61,14 @@ %p.form-text.text-muted = s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end } - - if rbac_clusters_feature_enabled? - .form-group - .form-check - = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true - = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' - .form-text.text-muted - = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') - = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') - = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank' + .form-group + .form-check + = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true + = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + .form-text.text-muted + = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') + = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-core-only'), target: '_blank' .form-group = field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml index be84f2ae67c..779c9c245c1 100644 --- a/app/views/projects/clusters/gcp/_show.html.haml +++ b/app/views/projects/clusters/gcp/_show.html.haml @@ -37,14 +37,13 @@ = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') - - if rbac_clusters_feature_enabled? - .form-group - .form-check - = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' - .form-text.text-muted - = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') - = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') + .form-group + .form-check + = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + .form-text.text-muted + = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') .form-group = field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml index f497f5b606c..54a6e685bb0 100644 --- a/app/views/projects/clusters/user/_form.html.haml +++ b/app/views/projects/clusters/user/_form.html.haml @@ -25,15 +25,14 @@ = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') - - if rbac_clusters_feature_enabled? - .form-group - .form-check - = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input' }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' - .form-text.text-muted - = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') - = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') - = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank' + .form-group + .form-check + = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input qa-rbac-checkbox' }, 'rbac', 'abac' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + .form-text.text-muted + = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') + = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-core-only'), target: '_blank' .form-group = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml index 56b597d295a..5b57f7ceb7d 100644 --- a/app/views/projects/clusters/user/_show.html.haml +++ b/app/views/projects/clusters/user/_show.html.haml @@ -26,14 +26,13 @@ = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') - - if rbac_clusters_feature_enabled? - .form-group - .form-check - = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' - .form-text.text-muted - = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') - = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') + .form-group + .form-check + = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + .form-text.text-muted + = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') .form-group = field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml index 4694bc39d54..b3a82d1ef41 100644 --- a/app/views/projects/environments/_external_url.html.haml +++ b/app/views/projects/environments/_external_url.html.haml @@ -1,4 +1,4 @@ - if environment.external_url && can?(current_user, :read_environment, environment) - = link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn external-url has-tooltip', title: s_('Environments|Open live environment') do + = link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn external-url has-tooltip qa-view-deployment', title: s_('Environments|Open live environment') do = sprite_icon('external-link') View deployment diff --git a/app/views/projects/environments/empty.html.haml b/app/views/projects/environments/empty.html.haml index 1413930ebdb..129dbbf4e56 100644 --- a/app/views/projects/environments/empty.html.haml +++ b/app/views/projects/environments/empty.html.haml @@ -1,14 +1,14 @@ - page_title _("Metrics") -.row +.row.empty-state .col-sm-12 .svg-content = image_tag 'illustrations/operations_metrics_empty.svg' -.row.empty-environments - .col-sm-12.text-center - %h4 - = s_('Metrics|No deployed environments') - .state-description - = s_('Metrics|Check out the CI/CD documentation on deploying to an environment') - .prepend-top-10 - = link_to s_("Metrics|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success' + .col-12 + .text-content + %h4.text-center + = s_('Metrics|No deployed environments') + %p.state-description + = s_('Metrics|Check out the CI/CD documentation on deploying to an environment') + .text-center + = link_to s_("Metrics|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index c7890b37381..8c5b6e089ea 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -49,15 +49,16 @@ .environments-container - if @deployments.blank? - .blank-state-row - .blank-state-center - %h2.blank-state-title + .empty-state + .text-content + %h4.state-title You don't have any deployments right now. %p.blank-state-text Define environments in the deploy stage(s) in %code .gitlab-ci.yml to track deployments here. - = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" + .text-center + = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success" - else .table-holder .ci-table.environments{ role: 'grid' } diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 665968a64e1..28998acdc13 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -6,7 +6,7 @@ = link_to 'Close issue', issue_path(@issue, issue: {state_event: :close}, format: 'json'), data: {original_text: "Close issue", alternative_text: "Comment & close issue"}, class: "btn btn-nr btn-close btn-comment js-note-target-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' %section.js-vue-notes-event - #js-vue-notes{ data: { notes_data: notes_data(@issue), + #js-vue-notes{ data: { notes_data: notes_data(@issue).to_json, noteable_data: serialize_issuable(@issue), noteable_type: 'Issue', target_type: 'issue', diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml deleted file mode 100644 index ea552c73c92..00000000000 --- a/app/views/projects/jobs/_empty_state.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -- illustration = local_assigns.fetch(:illustration) -- illustration_size = local_assigns.fetch(:illustration_size) -- title = local_assigns.fetch(:title) -- content = local_assigns.fetch(:content, nil) -- action = local_assigns.fetch(:action, nil) - -.row.empty-state - .col-12 - .svg-content{ class: illustration_size } - = image_tag illustration - .col-12 - .text-content - %h4.text-center= title - - if content - %p= content - - if action - .text-center - = action diff --git a/app/views/projects/jobs/_empty_states.html.haml b/app/views/projects/jobs/_empty_states.html.haml deleted file mode 100644 index e5198d047df..00000000000 --- a/app/views/projects/jobs/_empty_states.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- detailed_status = @build.detailed_status(current_user) -- illustration = detailed_status.illustration - -= render 'empty_state', - illustration: illustration[:image], - illustration_size: illustration[:size], - title: illustration[:title], - content: illustration[:content], - action: detailed_status.has_action? ? link_to(detailed_status.action_button_title, detailed_status.action_path, method: detailed_status.action_method, class: 'btn btn-primary', title: detailed_status.action_button_title) : nil diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index ab7963737ca..a5f814b722d 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -42,8 +42,6 @@ = custom_icon('scroll_down') = render 'shared/builds/build_output' - - else - = render "empty_states" #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } } diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 683dda4f166..11a05eada30 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -2,32 +2,25 @@ - page_title "Labels" - can_admin_label = can?(current_user, :admin_label, @project) - search = params[:search] +- subscribed = params[:subscribed] +- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present? - if can_admin_label - content_for(:header_content) do .nav-controls = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success" -- if @labels.exists? || @prioritized_labels.exists? || search.present? +- if labels_or_filters #promote-label-modal %div{ class: container_class } - .top-area.adjust - .nav-text - = _('Labels can be applied to issues and merge requests.') - - .nav-controls - = form_tag project_labels_path(@project), method: :get do - .input-group - = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false } - %span.input-group-append - %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') } - = icon("search") - = render 'shared/labels/sort_dropdown' + = render 'shared/labels/nav' .labels-container.prepend-top-10 - if can_admin_label - if search.blank? %p.text-muted + = _('Labels can be applied to issues and merge requests.') + %br = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.') -# Only show it in the first page - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') @@ -59,7 +52,9 @@ - else .nothing-here-block = _('No labels with such name or description') - + - elsif subscribed.present? + .nothing-here-block + = _('You do not have any subscriptions yet') - else = render 'shared/empty_states/labels' diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index b23baa22d8b..ef2fa8668c0 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -60,7 +60,7 @@ %section.col-md-12 %script.js-notes-data{ type: "application/json" }= initial_notes_data(true).to_json.html_safe .issuable-discussion.js-vue-notes-event - #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request), + #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request).to_json, noteable_data: serialize_issuable(@merge_request), noteable_type: 'MergeRequest', target_type: 'merge_request', diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index 314af44490e..ec503cd8bef 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -1,8 +1,34 @@ %h3 = _('Specific Runners') -= render partial: 'ci/runner/how_to_setup_specific_runner', - locals: { registration_token: @project.runners_token } +.bs-callout.help-callout + .append-bottom-10 + %h4= _('Set up a specific Runner automatically') + + %p + - link_to_help_page = link_to(_('Learn more about Kubernetes'), + help_page_path('user/project/clusters/index'), + target: '_blank', + rel: 'noopener noreferrer') + + = _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page } + + %ol + %li + = _('Click the button below to begin the install process by navigating to the Kubernetes page') + %li + = _('Select an existing Kubernetes cluster or create a new one') + %li + = _('From the Kubernetes cluster details view, install Runner from the applications list') + + = link_to _('Install Runner on Kubernetes'), + project_clusters_path(@project), + class: 'btn btn-info' + %hr + = render partial: 'ci/runner/how_to_setup_runner', + locals: { registration_token: @project.runners_token, + type: 'specific', + reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path } - if @project_runners.any? %h4.underlined-title Runners activated for this project diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index ab92b757836..5ec5a06396e 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -39,10 +39,17 @@ = form.label :deploy_strategy_continuous, class: 'form-check-label' do = s_('CICD|Continuous deployment to production') = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-deploy'), target: '_blank' + + .form-check + = form.radio_button :deploy_strategy, 'timed_incremental', class: 'form-check-input' + = form.label :deploy_strategy_timed_incremental, class: 'form-check-label' do + = s_('CICD|Continuous deployment to production using timed incremental rollout') + = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'timed-incremental-rollout-to-production'), target: '_blank' + .form-check = form.radio_button :deploy_strategy, 'manual', class: 'form-check-input' = form.label :deploy_strategy_manual, class: 'form-check-label' do = s_('CICD|Automatic deployment to staging, manual deployment to production') - = link_to icon('question-circle'), help_page_path('ci/environments.md', anchor: 'manually-deploying-to-environments'), target: '_blank' + = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production'), target: '_blank' = f.submit _('Save changes'), class: "btn btn-success prepend-top-15" diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index ae923d8e6dc..41afaa9ffc0 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -3,16 +3,6 @@ = form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings') do |f| = form_errors(@project) %fieldset.builds-feature - .form-group.append-bottom-default.js-secret-runner-token - = f.label :runners_token, _("Runner token"), class: 'label-bold' - .form-control.js-secret-value-placeholder - = '*' * 20 - = f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89' - %p.form-text.text-muted= _("The secure token used by the Runner to checkout the project") - %button.btn.btn-info.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: 'false' } } - = _('Reveal value') - - %hr .form-group %h5.prepend-top-0 = _("Git strategy for pipelines") diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 16961784e00..98e2829ba43 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -12,7 +12,7 @@ %button.btn.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') %p - = _("Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report.") + = _("Customize your pipeline configuration, view your pipeline status and coverage report.") .settings-content = render 'form' diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml index 28e6fe1b16d..0d2f6bb77d6 100644 --- a/app/views/shared/boards/_show.html.haml +++ b/app/views/shared/boards/_show.html.haml @@ -33,7 +33,7 @@ - if @project %board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project), "milestone-path" => milestones_filter_dropdown_path, - "label-path" => labels_filter_path, + "label-path" => labels_filter_path_with_defaults, "empty-state-svg" => image_path('illustrations/issues.svg'), ":issue-link-base" => "issueLinkBase", ":root-path" => "rootPath", diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml index 532045f3697..6138914206b 100644 --- a/app/views/shared/boards/components/sidebar/_labels.html.haml +++ b/app/views/shared/boards/components/sidebar/_labels.html.haml @@ -25,7 +25,7 @@ show_no: "true", show_any: "true", project_id: @project&.try(:id), - labels: labels_filter_path(false), + labels: labels_filter_path_with_defaults, namespace_path: @namespace_path, project_path: @project.try(:path) } } %span.dropdown-toggle-text diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 0b42b33581a..6eb1f8f0853 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -8,7 +8,7 @@ - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label") -- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"} +- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path_with_defaults, default_label: "Labels"} - dropdown_data.merge!(data_options) - label_name = local_assigns.fetch(:label_name, "Labels") - no_default_styles = local_assigns.fetch(:no_default_styles, false) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 32b609eed0d..aa136af1955 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -109,7 +109,7 @@ - selected_labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path(false) if @project), display: 'static' } } + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path_with_defaults if @project), display: 'static' } } %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } = multi_label_name(selected_labels, "Labels") = icon('chevron-down', 'aria-hidden': 'true') diff --git a/app/views/shared/labels/_nav.html.haml b/app/views/shared/labels/_nav.html.haml new file mode 100644 index 00000000000..98572db738b --- /dev/null +++ b/app/views/shared/labels/_nav.html.haml @@ -0,0 +1,20 @@ +- subscribed = params[:subscribed] + +.top-area.adjust + %ul.nav-links.nav.nav-tabs + %li{ class: active_when(subscribed != 'true') }> + = link_to labels_filter_path do + = _('All') + - if current_user + %li{ class: active_when(subscribed == 'true') }> + = link_to labels_filter_path(subscribed: 'true') do + = _('Subscribed') + .nav-controls + = form_tag labels_filter_path, method: :get do + = hidden_field_tag :subscribed, params[:subscribed] + .input-group + = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false } + %span.input-group-append + %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') } + = icon("search") + = render 'shared/labels/sort_dropdown' diff --git a/app/views/shared/labels/_sort_dropdown.html.haml b/app/views/shared/labels/_sort_dropdown.html.haml index ff6e2947ffd..8a7d037e15b 100644 --- a/app/views/shared/labels/_sort_dropdown.html.haml +++ b/app/views/shared/labels/_sort_dropdown.html.haml @@ -6,4 +6,4 @@ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort %li - label_sort_options_hash.each do |value, title| - = sortable_item(title, page_filter_path(sort: value, label: true), sort_title) + = sortable_item(title, page_filter_path(sort: value, label: true, subscribed: params[:subscribed]), sort_title) diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml new file mode 100644 index 00000000000..f8b3754840d --- /dev/null +++ b/app/views/users/_overview.html.haml @@ -0,0 +1,32 @@ +.row + .col-md-12.col-lg-6 + .calendar-block + .content-block.hide-bottom-border + %h4 + = s_('UserProfile|Activity') + .user-calendar.d-none.d-sm-block.text-left{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } } + %h4.center.light + %i.fa.fa-spinner.fa-spin + .user-calendar-activities.d-none.d-sm-block + + - if can?(current_user, :read_cross_project) + .activities-block + .content-block + %h5.prepend-top-10 + = s_('UserProfile|Recent contributions') + .overview-content-list{ data: { href: user_path } } + .center.light.loading + %i.fa.fa-spinner.fa-spin + .prepend-top-10 + = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all" + + .col-md-12.col-lg-6 + .projects-block + .content-block + %h4 + = s_('UserProfile|Personal projects') + .overview-content-list{ data: { href: user_projects_path } } + .center.light.loading + %i.fa.fa-spinner.fa-spin + .prepend-top-10 + = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all" diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 7a38d290915..d6c8420b744 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -12,22 +12,22 @@ .cover-block.user-cover-block.top-area .cover-controls - if @user == current_user - = link_to profile_path, class: 'btn btn-default has-tooltip', title: 'Edit profile', 'aria-label': 'Edit profile' do + = link_to profile_path, class: 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do = icon('pencil') - elsif current_user - if @user.abuse_report - %button.btn.btn-danger{ title: 'Already reported for abuse', + %button.btn.btn-danger{ title: s_('UserProfile|Already reported for abuse'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } } = icon('exclamation-circle') - else = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn', - title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do + title: s_('UserProfile|Report abuse'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('exclamation-circle') - if can?(current_user, :read_user_profile, @user) - = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: 'Subscribe', 'aria-label': 'Subscribe' do + = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: s_('UserProfile|Subscribe'), 'aria-label': 'Subscribe' do = icon('rss') - if current_user && current_user.admin? - = link_to [:admin, @user], class: 'btn btn-default', title: 'View user in admin area', + = link_to [:admin, @user], class: 'btn btn-default', title: s_('UserProfile|View user in admin area'), data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('users') @@ -51,7 +51,7 @@ @#{@user.username} - if can?(current_user, :read_user_profile, @user) %span.middle-dot-divider - Member since #{@user.created_at.to_date.to_s(:long)} + = s_('Member since %{date}') % { date: @user.created_at.to_date.to_s(:long) } .cover-desc - unless @user.public_email.blank? @@ -91,32 +91,40 @@ .fade-left= icon('angle-left') .fade-right= icon('angle-right') %ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs + - if profile_tab?(:overview) + %li.js-overview-tab + = link_to user_path, data: { target: 'div#js-overview', action: 'overview', toggle: 'tab' } do + = s_('UserProfile|Overview') - if profile_tab?(:activity) %li.js-activity-tab - = link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do - Activity + = link_to user_activity_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do + = s_('UserProfile|Activity') - if profile_tab?(:groups) %li.js-groups-tab = link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do - Groups + = s_('UserProfile|Groups') - if profile_tab?(:contributed) %li.js-contributed-tab = link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do - Contributed projects + = s_('UserProfile|Contributed projects') - if profile_tab?(:projects) %li.js-projects-tab = link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do - Personal projects + = s_('UserProfile|Personal projects') - if profile_tab?(:snippets) %li.js-snippets-tab = link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do - Snippets + = s_('UserProfile|Snippets') %div{ class: container_class } .tab-content + - if profile_tab?(:overview) + #js-overview.tab-pane + = render "users/overview" + - if profile_tab?(:activity) #activity.tab-pane - .row-content-block.calender-block.white.second-block.d-none.d-sm-block + .row-content-block.calendar-block.white.second-block.d-none.d-sm-block .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } } %h4.center.light %i.fa.fa-spinner.fa-spin @@ -124,7 +132,7 @@ - if can?(current_user, :read_cross_project) %h4.prepend-top-20 - Most Recent Activity + = s_('UserProfile|Most Recent Activity') .content_list{ data: { href: user_path } } = spinner @@ -155,4 +163,4 @@ .col-12.text-center .text-content %h4 - This user has a private profile + = s_('UserProfile|This user has a private profile') diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index d44ad0d8030..dc4b7670131 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -6,14 +6,13 @@ class PruneOldEventsWorker # rubocop: disable CodeReuse/ActiveRecord def perform - # Contribution calendar shows maximum 12 months of events. - # Double nested query is used because MySQL doesn't allow DELETE subqueries - # on the same table. + # Contribution calendar shows maximum 12 months of events, we retain 2 years for data integrity. + # Double nested query is used because MySQL doesn't allow DELETE subqueries on the same table. Event.unscoped.where( '(id IN (SELECT id FROM (?) ids_to_remove))', Event.unscoped.where( 'created_at < ?', - (12.months + 1.day).ago) + (2.years + 1.day).ago) .select(:id) .limit(10_000)) .delete_all diff --git a/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml b/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml new file mode 100644 index 00000000000..074cc9d642b --- /dev/null +++ b/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml @@ -0,0 +1,5 @@ +--- +title: Show percentage of language detection on the language bar +merge_request: 22056 +author: Johann Hubert Sonntagbauer +type: added diff --git a/changelogs/unreleased/22104-fix-environment-name-overlap.yml b/changelogs/unreleased/22104-fix-environment-name-overlap.yml new file mode 100644 index 00000000000..aaa1a1709c8 --- /dev/null +++ b/changelogs/unreleased/22104-fix-environment-name-overlap.yml @@ -0,0 +1,4 @@ +--- +title: "Fix the issue where long environment names aren't being truncated, causing the environment name to overlap into the column next to it." +merge_request: 22104 +type: fixed diff --git a/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml b/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml new file mode 100644 index 00000000000..e24c55e3bad --- /dev/null +++ b/changelogs/unreleased/22188-drop-down-filter-for-project-snippets.yml @@ -0,0 +1,5 @@ +--- +title: Add autocomplete drop down filter for project snippets +merge_request: 21458 +author: Fabian Schneider +type: added diff --git a/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml b/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml new file mode 100644 index 00000000000..8131e2ff54f --- /dev/null +++ b/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml @@ -0,0 +1,5 @@ +--- +title: Rephrase 2FA and TOTP documentation and view +merge_request: 21998 +author: Marc Schwede +type: other diff --git a/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml b/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml new file mode 100644 index 00000000000..1ebad500e9f --- /dev/null +++ b/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml @@ -0,0 +1,5 @@ +--- +title: Instance Configuration page now displays correct SSH fingerprints +merge_request: 22081 +author: +type: fixed diff --git a/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml b/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml new file mode 100644 index 00000000000..582d7824d27 --- /dev/null +++ b/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml @@ -0,0 +1,5 @@ +--- +title: Simplify runner registration token resetting +merge_request: 21658 +author: +type: changed diff --git a/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml b/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml new file mode 100644 index 00000000000..dd230d5f35e --- /dev/null +++ b/changelogs/unreleased/46050_add_new_ci_predefined_variables_for_gitlab_version.yml @@ -0,0 +1,5 @@ +--- +title: Add GitLab version components to CI environment variables +merge_request: 21853 +author: +type: added diff --git a/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml b/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml new file mode 100644 index 00000000000..f70011ac827 --- /dev/null +++ b/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml @@ -0,0 +1,5 @@ +--- +title: Reduce queries needed to compute notification recipients +merge_request: 22050 +author: +type: performance diff --git a/changelogs/unreleased/48222-fix-todos-status-button.yml b/changelogs/unreleased/48222-fix-todos-status-button.yml new file mode 100644 index 00000000000..2f7c79a07d0 --- /dev/null +++ b/changelogs/unreleased/48222-fix-todos-status-button.yml @@ -0,0 +1,6 @@ +--- +title: Fix the state of the Done button when there is an error in the GitLab Todos + section +merge_request: +author: marcos8896 +type: fixed diff --git a/changelogs/unreleased/48494-fix-merge-request-buttons-spacing.yml b/changelogs/unreleased/48494-fix-merge-request-buttons-spacing.yml new file mode 100644 index 00000000000..41cc024b8ac --- /dev/null +++ b/changelogs/unreleased/48494-fix-merge-request-buttons-spacing.yml @@ -0,0 +1,5 @@ +--- +title: Fix incorrect spacing between buttons when commenting on a MR. +merge_request: 22135 +author: +type: fixed diff --git a/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml b/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml new file mode 100644 index 00000000000..2c65c92dd8b --- /dev/null +++ b/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml @@ -0,0 +1,5 @@ +--- +title: Set user status from within user menu +merge_request: 21643 +author: +type: added diff --git a/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml b/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml new file mode 100644 index 00000000000..5e2be42c8b7 --- /dev/null +++ b/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml @@ -0,0 +1,5 @@ +--- +title: Adds new 'Overview' tab on user profile page +merge_request: 21663 +author: +type: other diff --git a/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml b/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml new file mode 100644 index 00000000000..cc7a79d25e5 --- /dev/null +++ b/changelogs/unreleased/50246-can-t-sort-group-issues-by-popularity-when-searching.yml @@ -0,0 +1,6 @@ +--- +title: Fix sorting by priority or popularity on group issues page, when also searching + issue content +merge_request: 21521 +author: +type: fixed diff --git a/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml b/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml new file mode 100644 index 00000000000..09ec4b8d73d --- /dev/null +++ b/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Fix timeout when running the RemoveRestrictedTodos background migration +merge_request: 21893 +author: +type: fixed diff --git a/changelogs/unreleased/50552-unable-to-close-performance-bar.yml b/changelogs/unreleased/50552-unable-to-close-performance-bar.yml new file mode 100644 index 00000000000..e3619149d2a --- /dev/null +++ b/changelogs/unreleased/50552-unable-to-close-performance-bar.yml @@ -0,0 +1,5 @@ +--- +title: Fix performance bar modal position +merge_request: 21577 +author: +type: fixed diff --git a/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml b/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml new file mode 100644 index 00000000000..99946b954ce --- /dev/null +++ b/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Remove 'rbac_clusters' feature flag +merge_request: 22096 +author: +type: changed diff --git a/changelogs/unreleased/51748-filter-any-milestone-via-api.yml b/changelogs/unreleased/51748-filter-any-milestone-via-api.yml new file mode 100644 index 00000000000..30304e5a4ac --- /dev/null +++ b/changelogs/unreleased/51748-filter-any-milestone-via-api.yml @@ -0,0 +1,5 @@ +--- +title: Allows to filter issues by Any milestone in the API +merge_request: 22080 +author: Jacopo Beschi @jacopo-beschi +type: added diff --git a/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml b/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml new file mode 100644 index 00000000000..e67cc27f852 --- /dev/null +++ b/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml @@ -0,0 +1,5 @@ +--- +title: Includes commit stats in POST project commits API +merge_request: 21968 +author: Jacopo Beschi @jacopo-beschi +type: fixed diff --git a/changelogs/unreleased/51958-fix-mr-discussion-loading.yml b/changelogs/unreleased/51958-fix-mr-discussion-loading.yml new file mode 100644 index 00000000000..f80ee51291d --- /dev/null +++ b/changelogs/unreleased/51958-fix-mr-discussion-loading.yml @@ -0,0 +1,5 @@ +--- +title: Fix loading issue on some merge request discussion +merge_request: 21982 +author: +type: fixed diff --git a/changelogs/unreleased/52178-markdown-table-borders.yml b/changelogs/unreleased/52178-markdown-table-borders.yml new file mode 100644 index 00000000000..965f21f2a97 --- /dev/null +++ b/changelogs/unreleased/52178-markdown-table-borders.yml @@ -0,0 +1,5 @@ +--- +title: Add borders and white background to markdown tables +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/52193-Pipeline-graph-is-not-vertically-aligned-in-commit-page.yml b/changelogs/unreleased/52193-Pipeline-graph-is-not-vertically-aligned-in-commit-page.yml new file mode 100644 index 00000000000..2d3ac49807a --- /dev/null +++ b/changelogs/unreleased/52193-Pipeline-graph-is-not-vertically-aligned-in-commit-page.yml @@ -0,0 +1,5 @@ +--- +title: Vertical align Pipeline Graph in Commit Page +merge_request: 22173 +author: Johann Hubert Sonntagbauer +type: fixed diff --git a/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml b/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml new file mode 100644 index 00000000000..d96c2bc7acd --- /dev/null +++ b/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml @@ -0,0 +1,5 @@ +--- +title: Trim whitespace when inviting a new user by email +merge_request: 22119 +author: Jacopo Beschi @jacopo-beschi +type: fixed diff --git a/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml b/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml new file mode 100644 index 00000000000..3fea6f33451 --- /dev/null +++ b/changelogs/unreleased/52242-ui-ux-bug-in-change-group-path.yml @@ -0,0 +1,5 @@ +--- +title: Fix incorrect parent path on group settings page +merge_request: 22142 +author: +type: fixed diff --git a/changelogs/unreleased/52353-keyboard-navigation-project-slug-is-not-focused-on-new-project-page.yml b/changelogs/unreleased/52353-keyboard-navigation-project-slug-is-not-focused-on-new-project-page.yml new file mode 100644 index 00000000000..ffcc0cc08a0 --- /dev/null +++ b/changelogs/unreleased/52353-keyboard-navigation-project-slug-is-not-focused-on-new-project-page.yml @@ -0,0 +1,5 @@ +--- +title: Focus project slug on tab navigation +merge_request: 22198 +author: +type: other diff --git a/changelogs/unreleased/52367-cleanup-web-hooks-columns.yml b/changelogs/unreleased/52367-cleanup-web-hooks-columns.yml new file mode 100644 index 00000000000..d1f3ca83613 --- /dev/null +++ b/changelogs/unreleased/52367-cleanup-web-hooks-columns.yml @@ -0,0 +1,5 @@ +--- +title: Remove legacy unencrypted webhook columns from the database +merge_request: 22199 +author: +type: changed diff --git a/changelogs/unreleased/52408-pip-cache-dir-to-cache-python-dependencies.yml b/changelogs/unreleased/52408-pip-cache-dir-to-cache-python-dependencies.yml new file mode 100644 index 00000000000..19d3e35c15c --- /dev/null +++ b/changelogs/unreleased/52408-pip-cache-dir-to-cache-python-dependencies.yml @@ -0,0 +1,5 @@ +--- +title: Use the standard PIP_CACHE_DIR for Python dependency caching template +merge_request: 22211 +author: Takuya Noguchi +type: fixed diff --git a/changelogs/unreleased/5987-project-templates-api.yml b/changelogs/unreleased/5987-project-templates-api.yml new file mode 100644 index 00000000000..a627ba9f0de --- /dev/null +++ b/changelogs/unreleased/5987-project-templates-api.yml @@ -0,0 +1,5 @@ +--- +title: Allow file templates to be requested at the project level +merge_request: 7776 +author: +type: added diff --git a/changelogs/unreleased/Fix-pipeline-redirect.yml b/changelogs/unreleased/Fix-pipeline-redirect.yml new file mode 100644 index 00000000000..459273c7740 --- /dev/null +++ b/changelogs/unreleased/Fix-pipeline-redirect.yml @@ -0,0 +1,5 @@ +--- +title: Redirect to the pipeline builds page when a build is canceled +merge_request: +author: Eva Kadlecova +type: fixed diff --git a/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml b/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml new file mode 100644 index 00000000000..69432c0d20c --- /dev/null +++ b/changelogs/unreleased/add-button-to-insert-table-in-markdown.yml @@ -0,0 +1,5 @@ +--- +title: Add markdown header toolbar button to insert table +merge_request: 18480 +author: George Tsiolis +type: added diff --git a/changelogs/unreleased/add-installation-type-backup-information.yml b/changelogs/unreleased/add-installation-type-backup-information.yml new file mode 100644 index 00000000000..24cf4cc21f4 --- /dev/null +++ b/changelogs/unreleased/add-installation-type-backup-information.yml @@ -0,0 +1,5 @@ +--- +title: Add installation type to backup information file +merge_request: 22150 +author: +type: changed diff --git a/changelogs/unreleased/add_reliable_fetcher.yml b/changelogs/unreleased/add_reliable_fetcher.yml new file mode 100644 index 00000000000..c08c755e546 --- /dev/null +++ b/changelogs/unreleased/add_reliable_fetcher.yml @@ -0,0 +1,5 @@ +--- +title: Use Reliable Sidekiq fetch +merge_request: 21715 +author: +type: fixed diff --git a/changelogs/unreleased/auth.yml b/changelogs/unreleased/auth.yml new file mode 100644 index 00000000000..cd4bbf0059e --- /dev/null +++ b/changelogs/unreleased/auth.yml @@ -0,0 +1,5 @@ +--- +title: Add access control to GitLab pages and make it possible to enable/disable it in project settings +merge_request: 18589 +author: Tuomo Ala-Vannesluoma +type: added diff --git a/changelogs/unreleased/autodevops-timed-incremental-rollout.yml b/changelogs/unreleased/autodevops-timed-incremental-rollout.yml new file mode 100644 index 00000000000..72c7b41177d --- /dev/null +++ b/changelogs/unreleased/autodevops-timed-incremental-rollout.yml @@ -0,0 +1,5 @@ +--- +title: Add timed incremental rollout to Auto DevOps +merge_request: 22023 +author: +type: added diff --git a/changelogs/unreleased/clone-nurtch-demo-repo.yml b/changelogs/unreleased/clone-nurtch-demo-repo.yml new file mode 100644 index 00000000000..c77138d27f0 --- /dev/null +++ b/changelogs/unreleased/clone-nurtch-demo-repo.yml @@ -0,0 +1,5 @@ +--- +title: Copy nurtch demo notebooks at Jupyter startup +merge_request: 21698 +author: Amit Rathi +type: added diff --git a/changelogs/unreleased/copy-changes-for-abuse-clarity.yml b/changelogs/unreleased/copy-changes-for-abuse-clarity.yml new file mode 100644 index 00000000000..00d9fec5e42 --- /dev/null +++ b/changelogs/unreleased/copy-changes-for-abuse-clarity.yml @@ -0,0 +1,5 @@ +--- +title: Increased retained event data by extending events pruner timeframe to 2 years +merge_request: 22145 +author: +type: changed diff --git a/changelogs/unreleased/dz-labels-subscribe-filter.yml b/changelogs/unreleased/dz-labels-subscribe-filter.yml new file mode 100644 index 00000000000..768c20c77c7 --- /dev/null +++ b/changelogs/unreleased/dz-labels-subscribe-filter.yml @@ -0,0 +1,5 @@ +--- +title: Add subscribe filter to group and project labels pages +merge_request: 21965 +author: +type: added diff --git a/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml b/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml new file mode 100644 index 00000000000..62676cdad62 --- /dev/null +++ b/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml @@ -0,0 +1,5 @@ +--- +title: Add support for pipeline only/except policy for modified paths +merge_request: 21981 +author: +type: added diff --git a/changelogs/unreleased/features-unauth-access-ssh-keys.yml b/changelogs/unreleased/features-unauth-access-ssh-keys.yml new file mode 100644 index 00000000000..bae2bcfaabd --- /dev/null +++ b/changelogs/unreleased/features-unauth-access-ssh-keys.yml @@ -0,0 +1,5 @@ +--- +title: Enable unauthenticated access to public SSH keys via the API +merge_request: 20118 +author: Ronald Claveau +type: changed diff --git a/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml b/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml new file mode 100644 index 00000000000..c2e828eb697 --- /dev/null +++ b/changelogs/unreleased/gt-remove-duplicate-button-from-the-md-header-toolbar.yml @@ -0,0 +1,5 @@ +--- +title: Remove duplicate button from the markdown header toolbar +merge_request: 22192 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/load_project_features.yml b/changelogs/unreleased/load_project_features.yml new file mode 100644 index 00000000000..0cf7f0e3a74 --- /dev/null +++ b/changelogs/unreleased/load_project_features.yml @@ -0,0 +1,5 @@ +--- +title: Mitigate N+1 queries when parsing commit references in comments. +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/mao-48221-issues_show_sql_count.yml b/changelogs/unreleased/mao-48221-issues_show_sql_count.yml new file mode 100644 index 00000000000..d634d15946e --- /dev/null +++ b/changelogs/unreleased/mao-48221-issues_show_sql_count.yml @@ -0,0 +1,5 @@ +--- +title: Banzai label ref finder - minimize SQL calls by sharing context more aggresively +merge_request: 22070 +author: +type: performance diff --git a/changelogs/unreleased/more-frozen-string-enable-lib.yml b/changelogs/unreleased/more-frozen-string-enable-lib.yml new file mode 100644 index 00000000000..9598c53b7fd --- /dev/null +++ b/changelogs/unreleased/more-frozen-string-enable-lib.yml @@ -0,0 +1,5 @@ +--- +title: Enable more frozen string in lib/**/*.rb +merge_request: +author: gfyoung +type: performance diff --git a/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml b/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml new file mode 100644 index 00000000000..5dde22d3158 --- /dev/null +++ b/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml @@ -0,0 +1,5 @@ +--- +title: Fix LFS uploaded images not being rendered +merge_request: 22092 +author: +type: fixed diff --git a/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml b/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml new file mode 100644 index 00000000000..d4e2641daf5 --- /dev/null +++ b/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml @@ -0,0 +1,5 @@ +--- +title: Removes expensive dead code on main MR page request +merge_request: 22153 +author: +type: performance diff --git a/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml b/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml new file mode 100644 index 00000000000..3a399bb836e --- /dev/null +++ b/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml @@ -0,0 +1,6 @@ +--- +title: 'Rails5: fix artifacts controller download spec Rails5 has params[:file_type] + as '''' if file_type is included as nil in the request' +merge_request: 22123 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/rails5-mysql-schedule-build.yml b/changelogs/unreleased/rails5-mysql-schedule-build.yml new file mode 100644 index 00000000000..cbc481fbf89 --- /dev/null +++ b/changelogs/unreleased/rails5-mysql-schedule-build.yml @@ -0,0 +1,5 @@ +--- +title: 'Rails 5: fix mysql milliseconds problems in scheduled build specs' +merge_request: 22170 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/rails5-user-status-spec.yml b/changelogs/unreleased/rails5-user-status-spec.yml new file mode 100644 index 00000000000..818d480e9fc --- /dev/null +++ b/changelogs/unreleased/rails5-user-status-spec.yml @@ -0,0 +1,5 @@ +--- +title: 'Rails5: fix user edit profile clear status spec' +merge_request: 22169 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml b/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml new file mode 100644 index 00000000000..e0231b7962f --- /dev/null +++ b/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml @@ -0,0 +1,5 @@ +--- +title: Markdown API no longer displays confidential title references unless authorized +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml b/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml new file mode 100644 index 00000000000..589d16c0c35 --- /dev/null +++ b/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml @@ -0,0 +1,5 @@ +--- +title: Properly filter private references from system notes +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-osw-user-info-leak-discussions.yml b/changelogs/unreleased/security-osw-user-info-leak-discussions.yml new file mode 100644 index 00000000000..5acbb80fc3d --- /dev/null +++ b/changelogs/unreleased/security-osw-user-info-leak-discussions.yml @@ -0,0 +1,5 @@ +--- +title: Filter user sensitive data from discussions JSON +merge_request: 2536 +author: +type: security diff --git a/changelogs/unreleased/sh-handle-invalid-comparison.yml b/changelogs/unreleased/sh-handle-invalid-comparison.yml new file mode 100644 index 00000000000..30b5b3d8198 --- /dev/null +++ b/changelogs/unreleased/sh-handle-invalid-comparison.yml @@ -0,0 +1,5 @@ +--- +title: Reject invalid branch names in repository compare controller +merge_request: 22186 +author: +type: fixed diff --git a/changelogs/unreleased/update-operations-metrics-empty-state.yml b/changelogs/unreleased/update-operations-metrics-empty-state.yml new file mode 100644 index 00000000000..51f3935b769 --- /dev/null +++ b/changelogs/unreleased/update-operations-metrics-empty-state.yml @@ -0,0 +1,5 @@ +--- +title: Update operations metrics empty state +merge_request: 21974 +author: George Tsiolis +type: other diff --git a/changelogs/unreleased/zj-render-log-artifacts.yml b/changelogs/unreleased/zj-render-log-artifacts.yml new file mode 100644 index 00000000000..82f29b62300 --- /dev/null +++ b/changelogs/unreleased/zj-render-log-artifacts.yml @@ -0,0 +1,5 @@ +--- +title: Render log artifact files in GitLab +merge_request: +author: +type: added diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 67337f4b82f..749cdd0f869 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -210,6 +210,7 @@ production: &base ## GitLab Pages pages: enabled: false + access_control: false # The location where pages are stored (default: shared/pages). # path: shared/pages diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0caa4962128..bd02b85c7ce 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -200,6 +200,7 @@ Settings.registry['path'] = Settings.absolute(Settings.registry['path # Settings['pages'] ||= Settingslogic.new({}) Settings.pages['enabled'] = false if Settings.pages['enabled'].nil? +Settings.pages['access_control'] = false if Settings.pages['access_control'].nil? Settings.pages['path'] = Settings.absolute(Settings.pages['path'] || File.join(Settings.shared['path'], "pages")) Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['host'] ||= "example.com" diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 6c1079faad1..bc6b7aed6aa 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -40,6 +40,10 @@ Sidekiq.configure_server do |config| ActiveRecord::Base.clear_all_connections! end + if Feature.enabled?(:gitlab_sidekiq_reliable_fetcher) + Sidekiq::ReliableFetcher.setup_reliable_fetch!(config) + end + # Sidekiq-cron: load recurring jobs from gitlab.yml # UGLY Hack to get nested hash from settingslogic cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json) @@ -57,10 +61,10 @@ Sidekiq.configure_server do |config| Gitlab::SidekiqVersioning.install! - config = Gitlab::Database.config || + db_config = Gitlab::Database.config || Rails.application.config.database_configuration[Rails.env] - config['pool'] = Sidekiq.options[:concurrency] - ActiveRecord::Base.establish_connection(config) + db_config['pool'] = Sidekiq.options[:concurrency] + ActiveRecord::Base.establish_connection(db_config) Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") # Avoid autoload issue such as 'Mail::Parsers::AddressStruct' diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 7489b01ded6..7cdaa2daa14 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -107,7 +107,7 @@ namespace :admin do resource :application_settings, only: [:show, :update] do resources :services, only: [:index, :edit, :update] get :usage_data - put :reset_runners_token + put :reset_registration_token put :reset_health_check_token put :clear_repository_check_states get :integrations, :repository, :templates, :ci_cd, :reporting, :metrics_and_profiling, :network, :geo, :preferences diff --git a/config/routes/group.rb b/config/routes/group.rb index 893ec8a4e58..602bbe837cf 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -27,7 +27,9 @@ constraints(::Constraints::GroupUrlConstrainer.new) do as: :group, constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do namespace :settings do - resource :ci_cd, only: [:show], controller: 'ci_cd' + resource :ci_cd, only: [:show], controller: 'ci_cd' do + put :reset_registration_token + end end resource :variables, only: [:show, :update] diff --git a/config/routes/project.rb b/config/routes/project.rb index 04a270c5cc9..9cbd5b644f6 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -32,6 +32,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get 'labels' get 'milestones' get 'commands' + get 'snippets' end end @@ -366,7 +367,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get :discussions, format: :json end collection do - post :bulk_update + post :bulk_update end end @@ -438,6 +439,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get :members, to: redirect("%{namespace_id}/%{project_id}/project_members") resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do post :reset_cache + put :reset_registration_token end resource :integrations, only: [:show] resource :repository, only: [:show], controller: :repository do diff --git a/config/routes/user.rb b/config/routes/user.rb index bc7df5e7584..e0ae264e2c0 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -45,6 +45,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d get :contributed, as: :contributed_projects get :snippets get :exists + get :activity get '/', to: redirect('%{username}'), as: nil end diff --git a/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb b/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb index 55bf40ba24d..cc5cb355579 100644 --- a/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb +++ b/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb @@ -13,7 +13,7 @@ class AddForeignKeyPipelineSchedulesAndPipelines < ActiveRecord::Migration 'SET NULL' end - add_concurrent_foreign_key :ci_pipelines, :ci_pipeline_schedules, + add_concurrent_foreign_key :ci_pipelines, :ci_pipeline_schedules, column: :pipeline_schedule_id, on_delete: on_delete end diff --git a/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb b/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb new file mode 100644 index 00000000000..1d2f8cf9c76 --- /dev/null +++ b/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb @@ -0,0 +1,16 @@ +class AddPagesAccessLevelToProjectFeature < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + add_column_with_default(:project_features, :pages_access_level, :integer, default: ProjectFeature::PUBLIC, allow_null: false) + + change_column_default(:project_features, :pages_access_level, ProjectFeature::ENABLED) + end + + def down + remove_column :project_features, :pages_access_level + end +end diff --git a/db/migrate/20180924201039_add_partial_index_to_scheduled_at.rb b/db/migrate/20180924201039_add_partial_index_to_scheduled_at.rb index c2e62dede8a..81bf0d94e11 100644 --- a/db/migrate/20180924201039_add_partial_index_to_scheduled_at.rb +++ b/db/migrate/20180924201039_add_partial_index_to_scheduled_at.rb @@ -9,7 +9,7 @@ class AddPartialIndexToScheduledAt < ActiveRecord::Migration disable_ddl_transaction! def up - add_concurrent_index(:ci_builds, [:scheduled_at, :id], where: "scheduled_at IS NOT NULL", name: INDEX_NAME) + add_concurrent_index(:ci_builds, :scheduled_at, where: "scheduled_at IS NOT NULL AND type = 'Ci::Build' AND status = 'scheduled'", name: INDEX_NAME) end def down diff --git a/db/migrate/20181002172433_remove_restricted_todos_with_cte.rb b/db/migrate/20181002172433_remove_restricted_todos_with_cte.rb new file mode 100644 index 00000000000..0a8f4a12266 --- /dev/null +++ b/db/migrate/20181002172433_remove_restricted_todos_with_cte.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +# rescheduling of the revised RemoveRestrictedTodos background migration +class RemoveRestrictedTodosWithCte < ActiveRecord::Migration + DOWNTIME = false + disable_ddl_transaction! + + MIGRATION = 'RemoveRestrictedTodos'.freeze + BATCH_SIZE = 1000 + DELAY_INTERVAL = 5.minutes.to_i + + class Project < ActiveRecord::Base + include EachBatch + + self.table_name = 'projects' + end + + def up + Project.where('EXISTS (SELECT 1 FROM todos WHERE todos.project_id = projects.id)') + .each_batch(of: BATCH_SIZE) do |batch, index| + range = batch.pluck('MIN(id)', 'MAX(id)').first + + BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, range) + end + end + + def down + # nothing to do + end +end diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb index 017c58477ac..08d7f499eec 100644 --- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb +++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb @@ -1,5 +1,3 @@ -require 'thread' - class RenameReservedProjectNames < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers include Gitlab::ShellAdapter diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb index 3e8ccfdb899..43a37667250 100644 --- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb +++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb @@ -1,5 +1,3 @@ -require 'thread' - class RenameMoreReservedProjectNames < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers include Gitlab::ShellAdapter diff --git a/db/post_migrate/20181008145341_steal_encrypt_columns.rb b/db/post_migrate/20181008145341_steal_encrypt_columns.rb new file mode 100644 index 00000000000..c107ac72913 --- /dev/null +++ b/db/post_migrate/20181008145341_steal_encrypt_columns.rb @@ -0,0 +1,15 @@ +class StealEncryptColumns < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + Gitlab::BackgroundMigration.steal('EncryptColumns') + end + + def down + # no-op + end +end diff --git a/db/post_migrate/20181008145359_remove_web_hooks_token_and_url.rb b/db/post_migrate/20181008145359_remove_web_hooks_token_and_url.rb new file mode 100644 index 00000000000..0c44bca5f1a --- /dev/null +++ b/db/post_migrate/20181008145359_remove_web_hooks_token_and_url.rb @@ -0,0 +1,10 @@ +class RemoveWebHooksTokenAndUrl < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + remove_column :web_hooks, :token, :string + remove_column :web_hooks, :url, :string, limit: 2000 + end +end diff --git a/db/schema.rb b/db/schema.rb index f14859d9eac..d47156c6da4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180924201039) do +ActiveRecord::Schema.define(version: 20181008145359) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -347,7 +347,7 @@ ActiveRecord::Schema.define(version: 20180924201039) do add_index "ci_builds", ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree - add_index "ci_builds", ["scheduled_at", "id"], name: "partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs", where: "(scheduled_at IS NOT NULL)", using: :btree + add_index "ci_builds", ["scheduled_at"], name: "partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs", where: "((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text))", using: :btree add_index "ci_builds", ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)", using: :btree add_index "ci_builds", ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree @@ -1580,6 +1580,7 @@ ActiveRecord::Schema.define(version: 20180924201039) do t.datetime "created_at" t.datetime "updated_at" t.integer "repository_access_level", default: 20, null: false + t.integer "pages_access_level", default: 20, null: false end add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree @@ -2255,7 +2256,6 @@ ActiveRecord::Schema.define(version: 20180924201039) do add_index "web_hook_logs", ["web_hook_id"], name: "index_web_hook_logs_on_web_hook_id", using: :btree create_table "web_hooks", force: :cascade do |t| - t.string "url", limit: 2000 t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" @@ -2268,7 +2268,6 @@ ActiveRecord::Schema.define(version: 20180924201039) do t.boolean "note_events", default: false, null: false t.boolean "enable_ssl_verification", default: true t.boolean "wiki_page_events", default: false, null: false - t.string "token" t.boolean "pipeline_events", default: false, null: false t.boolean "confidential_issues_events", default: false, null: false t.boolean "repository_update_events", default: false, null: false diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md index 9b0fabb9259..c58ced7d520 100644 --- a/doc/administration/custom_hooks.md +++ b/doc/administration/custom_hooks.md @@ -50,6 +50,9 @@ Hooks can be also placed in `hooks/<hook_name>.d` (global) or `custom_hooks/<hook_name>.d` (per project) directories supporting chained execution of the hooks. +NOTE: **Note:** `<hook_name>.d` would need to be either `pre-receive.d`, +`post-receive.d`, or `update.d` to work properly. Any other names will be ignored. + To look in a different directory for the global custom hooks (those in `hooks/<hook_name.d>`), set `custom_hooks_dir` in gitlab-shell config. For Omnibus installations, this can be set in `gitlab.rb`; and in source diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md index 54adb99386a..ec11a92db1b 100644 --- a/doc/administration/operations/moving_repositories.md +++ b/doc/administration/operations/moving_repositories.md @@ -22,9 +22,8 @@ However, it is not possible to resume an interrupted tar pipe: if that happens then all data must be copied again. ``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - tar -C /mnt/gitlab/repositories -xf - +sudo -u git sh -c 'tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + tar -C /mnt/gitlab/repositories -xf -' ``` If you want to see progress, replace `-xf` with `-xvf`. @@ -36,9 +35,8 @@ You can also use a tar pipe to copy data to another server. If your can pipe the data through SSH. ``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - ssh git@newserver tar -C /mnt/gitlab/repositories -xf - +sudo -u git sh -c 'tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + ssh git@newserver tar -C /mnt/gitlab/repositories -xf -' ``` If you want to compress the data before it goes over the network @@ -53,9 +51,8 @@ is either already installed on your system or easily installable via apt, yum etc. ``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - /mnt/gitlab/repositories +sudo -u git sh -c 'rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + /mnt/gitlab/repositories' ``` The `/.` in the command above is very important, without it you can @@ -68,9 +65,8 @@ If the 'git' user on your source system has SSH access to the target server you can send the repositories over the network with rsync. ``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - git@newserver:/mnt/gitlab/repositories +sudo -u git sh -c 'rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + git@newserver:/mnt/gitlab/repositories' ``` ## Thousands of Git repositories: use one rsync per repository @@ -125,7 +121,7 @@ sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-lo Now we can start the transfer. The command below is idempotent, and the number of jobs done by GNU Parallel should converge to zero. If it -does not some repositories listed in all-repos-1234.txt may have been +does not, some repositories listed in `all-repos-1234.txt` may have been deleted/renamed before they could be copied. ``` @@ -155,8 +151,8 @@ cat /home/git/transfer-logs/* | sort | uniq -u |\ Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. Then you might only want to sync repositories that were changed via GitLab -_after_ that time. You can use the 'SINCE' variable to tell 'rake -gitlab:list_repos' to only print repositories with recent activity. +_after_ that time. You can use the `SINCE` variable to tell `rake +gitlab:list_repos` to only print repositories with recent activity. ``` # Omnibus diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 3af0a5759a7..2952a98626a 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -92,9 +92,8 @@ where `example.io` is the domain under which GitLab Pages will be served and `192.0.2.1` is the IPv4 address of your GitLab instance and `2001::1` is the IPv6 address. If you don't have IPv6, you can omit the AAAA record. -> **Note:** -You should not use the GitLab domain to serve user pages. For more information -see the [security section](#security). +NOTE: **Note:** +You should not use the GitLab domain to serve user pages. For more information see the [security section](#security). [wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record @@ -107,12 +106,13 @@ since that is needed in all configurations. ### Wildcard domains -> **Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> -> --- -> -> URL scheme: `http://page.example.io` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) + +--- + +URL scheme: `http://page.example.io` This is the minimum setup that you can use Pages with. It is the base for all other setups as described below. Nginx will proxy all requests to the daemon. @@ -126,18 +126,18 @@ The Pages daemon doesn't listen to the outside world. 1. [Reconfigure GitLab][reconfigure] - Watch the [video tutorial][video-admin] for this configuration. ### Wildcard domains with TLS support -> **Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> - Wildcard TLS certificate -> -> --- -> -> URL scheme: `https://page.example.io` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) +- Wildcard TLS certificate + +--- + +URL scheme: `https://page.example.io` Nginx will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. @@ -168,13 +168,14 @@ you have IPv6 as well as IPv4 addresses, you can use them both. ### Custom domains -> **Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> - Secondary IP -> -> --- -> -> URL scheme: `http://page.example.io` and `http://domain.com` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) +- Secondary IP + +--- + +URL scheme: `http://page.example.io` and `http://domain.com` In that case, the Pages daemon is running, Nginx still proxies requests to the daemon but the daemon is also able to receive requests from the outside @@ -197,14 +198,15 @@ world. Custom domains are supported, but no TLS. ### Custom domains with TLS support -> **Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> - Wildcard TLS certificate -> - Secondary IP -> -> --- -> -> URL scheme: `https://page.example.io` and `https://domain.com` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) +- Wildcard TLS certificate +- Secondary IP + +--- + +URL scheme: `https://page.example.io` and `https://domain.com` In that case, the Pages daemon is running, Nginx still proxies requests to the daemon but the daemon is also able to receive requests from the outside @@ -320,12 +322,12 @@ latest previous version. --- -**GitLab 8.17 ([documentation][8-17-docs])** +**GitLab 8.17 ([documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable/doc/administration/pages/index.md))** - GitLab Pages were ported to Community Edition in GitLab 8.17. - Documentation was refactored to be more modular and easy to follow. -**GitLab 8.5 ([documentation][8-5-docs])** +**GitLab 8.5 ([documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md))** - In GitLab 8.5 we introduced the [gitlab-pages][] daemon which is now the recommended way to set up GitLab Pages. @@ -334,13 +336,10 @@ latest previous version. - Custom CNAME and TLS certificates support. - Documentation was moved to one place. -**GitLab 8.3 ([documentation][8-3-docs])** +**GitLab 8.3 ([documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md))** - GitLab Pages feature was introduced. -[8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md -[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md -[8-17-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable-ce/doc/administration/pages/index.md [backup]: ../../raketasks/backup_restore.md [ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605 [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 diff --git a/doc/administration/raketasks/github_import.md b/doc/administration/raketasks/github_import.md index 6b8ad1b039b..ccd9c0afb1d 100644 --- a/doc/administration/raketasks/github_import.md +++ b/doc/administration/raketasks/github_import.md @@ -1,37 +1,41 @@ # GitHub import ->**Note:** -> -> - [Introduced][ce-10308] in GitLab 9.1. -> - You need a personal access token in order to retrieve and import GitHub -> projects. You can get it from: https://github.com/settings/tokens -> - You also need to pass an username as the second argument to the rake task -> which will become the owner of the project. -> - You can also resume an import with the same command. +> [Introduced]( https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10308) in GitLab 9.1. + +In order to retrieve and import GitHub repositories, you will need a +[GitHub personal access token](https://github.com/settings/tokens). +A username should be passed as the second argument to the rake task +which will become the owner of the project. You can resume an import +with the same command. + +Bear in mind that the syntax is very specific. Remove any spaces within the argument block and +before/after the brackets. Also, Some shells (e.g., zsh) can interpret the open/close brackets +(`[]`) separately. You may need to either escape the brackets or use double quotes. + +## Importing multiple projects To import a project from the list of your GitHub projects available: ```bash # Omnibus installations -sudo gitlab-rake import:github[access_token,root,foo/bar] +sudo gitlab-rake "import:github[access_token,root,foo/bar]" # Installations from source -bundle exec rake import:github[access_token,root,foo/bar] RAILS_ENV=production +bundle exec rake "import:github[access_token,root,foo/bar]" RAILS_ENV=production ``` In this case, `access_token` is your GitHub personal access token, `root` is your GitLab username, and `foo/bar` is the new GitLab namespace/project that will get created from your GitHub project. Subgroups are also possible: `foo/foo/bar`. +## Importing a single project To import a specific GitHub project (named `foo/github_repo` here): ```bash # Omnibus installations -sudo gitlab-rake import:github[access_token,root,foo/bar,foo/github_repo] +sudo gitlab-rake "import:github[access_token,root,foo/bar,foo/github_repo]" # Installations from source -bundle exec rake import:github[access_token,root,foo/bar,foo/github_repo] RAILS_ENV=production +bundle exec rake "import:github[access_token,root,foo/bar,foo/github_repo]" RAILS_ENV=production ``` - -[ce-10308]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10308 diff --git a/doc/api/README.md b/doc/api/README.md index a3589377e9d..a351db99bbd 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -20,10 +20,11 @@ following locations: - [Custom Attributes](custom_attributes.md) - [Deployments](deployments.md) - [Deploy Keys](deploy_keys.md) +- [Dockerfile templates](templates/dockerfiles.md) - [Environments](environments.md) - [Events](events.md) - [Feature flags](features.md) -- [Gitignores templates](templates/gitignores.md) +- [Gitignore templates](templates/gitignores.md) - [GitLab CI Config templates](templates/gitlab_ci_ymls.md) - [Groups](groups.md) - [Group Access Requests](access_requests.md) @@ -55,6 +56,7 @@ following locations: - [Project import/export](project_import_export.md) - [Project Members](members.md) - [Project Snippets](project_snippets.md) +- [Project Templates](project_templates.md) - [Protected Branches](protected_branches.md) - [Protected Tags](protected_tags.md) - [Repositories](repositories.md) diff --git a/doc/api/commits.md b/doc/api/commits.md index 5ff1e1f60e0..9b7ca4b6e70 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -79,6 +79,7 @@ POST /projects/:id/repository/commits | `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. | | `author_email` | string | no | Specify the commit author's email address | | `author_name` | string | no | Specify the commit author's name | +| `stats` | boolean | no | Include commit stats. Default is true | | `actions[]` Attribute | Type | Required | Description | diff --git a/doc/api/events.md b/doc/api/events.md index fb5ebb71a86..cd84b32029e 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -44,7 +44,7 @@ YYYY-MM-DD ### Event Time Period Limit -GitLab removes events older than 1 year from the events table for performance reasons. The range of 1 year was chosen because user contribution calendars only show contributions of the past year. +GitLab removes events older than 2 years from the events table for performance reasons. ## List currently authenticated user's events diff --git a/doc/api/issues.md b/doc/api/issues.md index f4c0f4ea65b..cc1d6834a20 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | -| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone | +| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone. `Any+Milestone` lists all issues that have an assigned milestone | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`<br> For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.<br> _([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index b37e7698ab4..862ee398a84 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -54,35 +54,38 @@ Parameters: { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", "created_at": "2017-04-29T08:46:00Z", "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, - "labels": [ ], - "description": "fixed login page css paddings", + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, @@ -93,23 +96,28 @@ Parameters: "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false } ] ``` @@ -169,35 +177,38 @@ Parameters: { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", "created_at": "2017-04-29T08:46:00Z", "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, - "labels": [ ], - "description": "fixed login page css paddings", + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, @@ -208,24 +219,28 @@ Parameters: "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false } ] ``` @@ -275,35 +290,38 @@ Parameters: { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", "created_at": "2017-04-29T08:46:00Z", "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, - "labels": [ ], - "description": "fixed login page css paddings", + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, @@ -314,23 +332,26 @@ Parameters: "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-10-22", + "start_date": "2018-09-08", + "web_url": "gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false } ] ``` @@ -359,35 +380,38 @@ Parameters: { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", - "state": "merged", + "description": "fixed login page css paddings", + "state": "opened", "created_at": "2017-04-29T08:46:00Z", "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { - "state" : "active", - "web_url" : "https://gitlab.example.com/root", - "avatar_url" : null, - "username" : "root", - "id" : 1, - "name" : "Administrator" + "id": 1, + "name": "Administrator", + "username": "admin", + "state": "active", + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { - "state" : "active", - "web_url" : "https://gitlab.example.com/root", - "avatar_url" : null, - "username" : "root", - "id" : 1, - "name" : "Administrator" + "id": 1, + "name": "Administrator", + "username": "admin", + "state": "active", + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, - "labels": [ ], - "description": "fixed login page css paddings", + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, @@ -398,50 +422,55 @@ Parameters: "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": "9999999999999999999999999999999999999999", + "merge_commit_sha": null, "user_notes_count": 1, - "changes_count": "1", + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null }, - "closed_at": "2018-01-19T14:36:11.086Z", - "latest_build_started_at": null, - "latest_build_finished_at": null, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", "first_deployed_to_production_at": null, "pipeline": { - "id": 8, - "ref": "master", - "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0", - "status": "created" - }, - "merged_by": null, - "merged_at": null, - "closed_by": { - "state" : "active", - "web_url" : "https://gitlab.example.com/root", - "avatar_url" : null, - "username" : "root", - "id" : 1, - "name" : "Administrator" + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" }, "diff_refs": { - "base_sha": "1111111111111111111111111111111111111111", - "head_sha": "2222222222222222222222222222222222222222", - "start_sha": "3333333333333333333333333333333333333333" + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, "diverged_commits_count": 2 } @@ -663,65 +692,99 @@ POST /projects/:id/merge_requests { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", - "project_id": 4, + "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 3, - "target_project_id": 4, - "labels": [ ], - "description": "fixed login page css paddings", + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, "iid": 1, - "project_id": 4, + "project_id": 3, "title": "v2.0", "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, - "user_notes_count": 0, - "changes_count": "1", + "user_notes_count": 1, + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, "allow_collaboration": false, "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -756,64 +819,99 @@ Must include at least one non-required attribute from above. { "id": 1, "iid": 1, - "target_branch": "master", - "project_id": 4, + "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 3, - "target_project_id": 4, - "labels": [ ], - "description": "description1", + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, "iid": 1, - "project_id": 4, + "project_id": 3, "title": "v2.0", "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, - "changes_count": "1", + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, "allow_collaboration": false, "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -857,70 +955,106 @@ Parameters: - `merge_request_iid` (required) - Internal ID of MR - `merge_commit_message` (optional) - Custom merge commit message - `should_remove_source_branch` (optional) - if `true` removes the source branch -- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds +- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds - `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail ```json { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", - "state": "merged", + "description": "fixed login page css paddings", + "state": "opened", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 4, - "target_project_id": 4, - "labels": [ ], - "description": "fixed login page css paddings", + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, "iid": 1, - "project_id": 4, + "project_id": 3, "title": "v2.0", "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": "9999999999999999999999999999999999999999", + "merge_commit_sha": null, "user_notes_count": 1, - "changes_count": "1", + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -937,69 +1071,105 @@ PUT /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_s Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user -- `merge_request_iid` (required) - Internal ID of MR +- `merge_request_iid` (required) - Internal ID of MR ```json { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 4, - "target_project_id": 4, - "labels": [ ], - "description": "fixed login page css paddings", + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, "iid": 1, - "project_id": 4, + "project_id": 3, "title": "v2.0", "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, - "merge_when_pipeline_succeeds": true, + "merge_when_pipeline_succeeds": false, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, - "changes_count": "1", + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -1067,7 +1237,7 @@ Example response when the GitLab issue tracker is used: "labels" : [], "user_notes_count": 1, "changes_count": "1" - }, + } ] ``` @@ -1104,54 +1274,101 @@ Example response: ```json { - "id": 17, + "id": 1, "iid": 1, - "project_id": 5, - "title": "Et et sequi est impedit nulla ut rem et voluptatem.", - "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.", + "project_id": 3, + "title": "test1", + "description": "fixed login page css paddings", "state": "opened", - "created_at": "2016-04-05T21:42:23.233Z", - "updated_at": "2016-04-05T22:11:52.900Z", - "target_branch": "ui-dev-kit", - "source_branch": "version-1-9", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { - "name": "Eileen Skiles", - "username": "leila", - "id": 19, + "id": 1, + "name": "Administrator", + "username": "admin", "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", - "web_url": "https://gitlab.example.com/leila" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { - "name": "Celine Wehner", - "username": "carli", - "id": 16, + "id": 1, + "name": "Administrator", + "username": "admin", "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", - "web_url": "https://gitlab.example.com/carli" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 5, - "target_project_id": 5, - "labels": [], + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { - "id": 7, + "id": 5, "iid": 1, - "project_id": 5, + "project_id": 3, "title": "v2.0", - "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.", + "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", - "created_at": "2016-04-05T21:41:40.905Z", - "updated_at": "2016-04-05T21:41:40.905Z", - "due_date": null + "created_at": "2015-02-02T19:49:26.013Z", + "updated_at": "2015-02-02T19:49:26.013Z", + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, - "merge_when_pipeline_succeeds": false, - "merge_status": "cannot_be_merged", - "subscribed": true, + "merge_when_pipeline_succeeds": true, + "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": null + "merge_commit_sha": null, + "user_notes_count": 1, + "discussion_locked": null, + "should_remove_source_branch": true, + "force_remove_source_branch": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -1178,54 +1395,101 @@ Example response: ```json { - "id": 17, + "id": 1, "iid": 1, - "project_id": 5, - "title": "Et et sequi est impedit nulla ut rem et voluptatem.", - "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.", + "project_id": 3, + "title": "test1", + "description": "fixed login page css paddings", "state": "opened", - "created_at": "2016-04-05T21:42:23.233Z", - "updated_at": "2016-04-05T22:11:52.900Z", - "target_branch": "ui-dev-kit", - "source_branch": "version-1-9", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { - "name": "Eileen Skiles", - "username": "leila", - "id": 19, + "id": 1, + "name": "Administrator", + "username": "admin", "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", - "web_url": "https://gitlab.example.com/leila" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { - "name": "Celine Wehner", - "username": "carli", - "id": 16, + "id": 1, + "name": "Administrator", + "username": "admin", "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", - "web_url": "https://gitlab.example.com/carli" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 5, - "target_project_id": 5, - "labels": [], + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { - "id": 7, + "id": 5, "iid": 1, - "project_id": 5, + "project_id": 3, "title": "v2.0", - "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.", + "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", - "created_at": "2016-04-05T21:41:40.905Z", - "updated_at": "2016-04-05T21:41:40.905Z", - "due_date": null + "created_at": "2015-02-02T19:49:26.013Z", + "updated_at": "2015-02-02T19:49:26.013Z", + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, - "merge_when_pipeline_succeeds": false, - "merge_status": "cannot_be_merged", - "subscribed": false, + "merge_when_pipeline_succeeds": true, + "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": null + "merge_commit_sha": null, + "user_notes_count": 1, + "discussion_locked": null, + "should_remove_source_branch": true, + "force_remove_source_branch": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` diff --git a/doc/api/project_templates.md b/doc/api/project_templates.md new file mode 100644 index 00000000000..ebdfa975849 --- /dev/null +++ b/doc/api/project_templates.md @@ -0,0 +1,135 @@ +# Project templates API + +This API is a project-specific implementation of these endpoints: + +- [Dockerfile templates](templates/dockerfiles.md) +- [Gitignore templates](templates/gitignores.md) +- [GitLab CI Config templates](templates/gitlab_ci_ymls.md) +- [Open source license templates](templates/licenses.md) + +It deprecates those endpoints, which will be removed for API version 5. + +Project-specific templates will be added to this API in time. This includes, but +is not limited to: + +- [Issue and Merge Request templates](../user/project/description_templates.html) +- [Group level file templates](https://gitlab.com/gitlab-org/gitlab-ee/issues/5987) **(Premium)** + +## Get all templates of a particular type + +``` +GET /projects/:id/templates/:type +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `id ` | integer / string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `type` | string | yes| The type `(dockerfiles|gitignores|gitlab_ci_ymls|licenses)` of the template | + +Example response (licenses): + +```json +[ + { + "key": "epl-1.0", + "name": "Eclipse Public License 1.0" + }, + { + "key": "lgpl-3.0", + "name": "GNU Lesser General Public License v3.0" + }, + { + "key": "unlicense", + "name": "The Unlicense" + }, + { + "key": "agpl-3.0", + "name": "GNU Affero General Public License v3.0" + }, + { + "key": "gpl-3.0", + "name": "GNU General Public License v3.0" + }, + { + "key": "bsd-3-clause", + "name": "BSD 3-clause \"New\" or \"Revised\" License" + }, + { + "key": "lgpl-2.1", + "name": "GNU Lesser General Public License v2.1" + }, + { + "key": "mit", + "name": "MIT License" + }, + { + "key": "apache-2.0", + "name": "Apache License 2.0" + }, + { + "key": "bsd-2-clause", + "name": "BSD 2-clause \"Simplified\" License" + }, + { + "key": "mpl-2.0", + "name": "Mozilla Public License 2.0" + }, + { + "key": "gpl-2.0", + "name": "GNU General Public License v2.0" + } +] +``` + +## Get one template of a particular type + +``` +GET /projects/:id/templates/:type/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `id ` | integer / string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `type` | string | yes| The type `(dockerfiles|gitignores|gitlab_ci_ymls|licenses)` of the template | +| `key` | string | yes | The key of the template, as obtained from the collection endpoint | +| `project` | string | no | The project name to use when expanding placeholders in the template. Only affects licenses | +| `fullname` | string | no | The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses | + +Example response (Dockerfile): + + +```json +{ + "name": "Binary", + "content": "# This file is a template, and might need editing before it works on your project.\n# This Dockerfile installs a compiled binary into a bare system.\n# You must either commit your compiled binary into source control (not recommended)\n# or build the binary first as part of a CI/CD pipeline.\n\nFROM buildpack-deps:jessie\n\nWORKDIR /usr/local/bin\n\n# Change `app` to whatever your binary is called\nAdd app .\nCMD [\"./app\"]\n" +} + +``` + +Example response (license): + +```json +{ + "key": "mit", + "name": "MIT License", + "nickname": null, + "popular": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "https://opensource.org/licenses/MIT", + "description": "A short and simple permissive license with conditions only requiring preservation of copyright and license notices. Licensed works, modifications, and larger works may be distributed under different terms and without source code.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "liability", + "warranty" + ], + "content": "MIT License\n\nCopyright (c) 2018 [fullname]\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n" +} +``` diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 0623a6b02ae..c3624f1a535 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -97,7 +97,10 @@ POST /projects/:id/repository/files/:file_path ``` ```bash -curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fprojectrb%2E?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20content&commit_message=create%20a%20new%20file' +curl --request POST --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' --header "Content-Type: application/json" \ + --data '{"branch": "master", "author_email": "author@example.com", "author_name": "Firstname Lastname", \ + "content": "some content", "commit_message": "create a new file"}' \ + 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb' ``` Example response: @@ -129,7 +132,10 @@ PUT /projects/:id/repository/files/:file_path ``` ```bash -curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&content=some%20other%20content&commit_message=update%20file' +curl --request PUT --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' --header "Content-Type: application/json" \ + --data '{"branch": "master", "author_email": "author@example.com", "author_name": "Firstname Lastname", \ + "content": "some content", "commit_message": "update file"}' \ + 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb' ``` Example response: @@ -171,7 +177,10 @@ DELETE /projects/:id/repository/files/:file_path ``` ```bash -curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb?branch=master&author_email=author%40example.com&author_name=Firstname%20Lastname&commit_message=delete%20file' +curl --request DELETE --header 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' --header "Content-Type: application/json" \ + --data '{"branch": "master", "author_email": "author@example.com", "author_name": "Firstname Lastname", \ + "commit_message": "delete file"}' \ + 'https://gitlab.example.com/api/v4/projects/13083/repository/files/app%2Fproject%2Erb' ``` Parameters: diff --git a/doc/api/templates/dockerfiles.md b/doc/api/templates/dockerfiles.md new file mode 100644 index 00000000000..a08b8d33693 --- /dev/null +++ b/doc/api/templates/dockerfiles.md @@ -0,0 +1,113 @@ +# Dockerfiles API + +## List Dockerfile templates + +Get all Dockerfile templates. + +``` +GET /templates/dockerfiles +``` + +```bash +curl https://gitlab.example.com/api/v4/templates/dockerfiles +``` + +Example response: + +```json +[ + { + "key": "Binary", + "name": "Binary" + }, + { + "key": "Binary-alpine", + "name": "Binary-alpine" + }, + { + "key": "Binary-scratch", + "name": "Binary-scratch" + }, + { + "key": "Golang", + "name": "Golang" + }, + { + "key": "Golang-alpine", + "name": "Golang-alpine" + }, + { + "key": "Golang-scratch", + "name": "Golang-scratch" + }, + { + "key": "HTTPd", + "name": "HTTPd" + }, + { + "key": "Node", + "name": "Node" + }, + { + "key": "Node-alpine", + "name": "Node-alpine" + }, + { + "key": "OpenJDK", + "name": "OpenJDK" + }, + { + "key": "OpenJDK-alpine", + "name": "OpenJDK-alpine" + }, + { + "key": "PHP", + "name": "PHP" + }, + { + "key": "Python", + "name": "Python" + }, + { + "key": "Python-alpine", + "name": "Python-alpine" + }, + { + "key": "Python2", + "name": "Python2" + }, + { + "key": "Ruby", + "name": "Ruby" + }, + { + "key": "Ruby-alpine", + "name": "Ruby-alpine" + } +] +``` + +## Single Dockerfile template + +Get a single Dockerfile template. + +``` +GET /templates/dockerfiles/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the Dockerfile template | + +```bash +curl https://gitlab.example.com/api/v4/templates/dockerfiles/Binary +``` + +Example response: + +```json +{ + "name": "Binary", + "content": "# This file is a template, and might need editing before it works on your project.\n# This Dockerfile installs a compiled binary into a bare system.\n# You must either commit your compiled binary into source control (not recommended)\n# or build the binary first as part of a CI/CD pipeline.\n\nFROM buildpack-deps:jessie\n\nWORKDIR /usr/local/bin\n\n# Change `app` to whatever your binary is called\nAdd app .\nCMD [\"./app\"]\n" +} +``` diff --git a/doc/api/templates/gitignores.md b/doc/api/templates/gitignores.md index d3f5c88ca90..3804855129c 100644 --- a/doc/api/templates/gitignores.md +++ b/doc/api/templates/gitignores.md @@ -17,538 +17,84 @@ Example response: ```json [ { - "name": "AppEngine" - }, - { - "name": "Laravel" - }, - { - "name": "Elisp" - }, - { - "name": "SketchUp" + "key": "Actionscript", + "name": "Actionscript" }, { + "key": "Ada", "name": "Ada" }, { - "name": "Ruby" - }, - { - "name": "Kohana" - }, - { - "name": "Nanoc" - }, - { - "name": "Erlang" - }, - { - "name": "OCaml" - }, - { - "name": "Lithium" - }, - { - "name": "Fortran" - }, - { - "name": "Scala" - }, - { - "name": "Node" - }, - { - "name": "Fancy" - }, - { - "name": "Perl" - }, - { - "name": "Zephir" - }, - { - "name": "WordPress" - }, - { - "name": "Symfony" - }, - { - "name": "FuelPHP" - }, - { - "name": "DM" - }, - { - "name": "Sdcc" - }, - { - "name": "Rust" - }, - { - "name": "C" - }, - { - "name": "Umbraco" - }, - { - "name": "Actionscript" + "key": "Agda", + "name": "Agda" }, { + "key": "Android", "name": "Android" }, { - "name": "Grails" - }, - { - "name": "Composer" - }, - { - "name": "ExpressionEngine" - }, - { - "name": "Gcov" - }, - { - "name": "Qt" + "key": "AppEngine", + "name": "AppEngine" }, { - "name": "Phalcon" + "key": "AppceleratorTitanium", + "name": "AppceleratorTitanium" }, { + "key": "ArchLinuxPackages", "name": "ArchLinuxPackages" }, { - "name": "TeX" - }, - { - "name": "SCons" - }, - { - "name": "Lilypond" - }, - { - "name": "CommonLisp" - }, - { - "name": "Rails" - }, - { - "name": "Mercury" - }, - { - "name": "Magento" - }, - { - "name": "ChefCookbook" - }, - { - "name": "GitBook" - }, - { - "name": "C++" - }, - { - "name": "Eagle" - }, - { - "name": "Go" - }, - { - "name": "OpenCart" - }, - { - "name": "Scheme" - }, - { - "name": "Typo3" - }, - { - "name": "SeamGen" - }, - { - "name": "Swift" - }, - { - "name": "Elm" - }, - { - "name": "Unity" - }, - { - "name": "Agda" - }, - { - "name": "CUDA" - }, - { - "name": "VVVV" - }, - { - "name": "Finale" - }, - { - "name": "LemonStand" - }, - { - "name": "Textpattern" - }, - { - "name": "Julia" - }, - { - "name": "Packer" - }, - { - "name": "Scrivener" - }, - { - "name": "Dart" - }, - { - "name": "Plone" - }, - { - "name": "Jekyll" - }, - { - "name": "Xojo" - }, - { - "name": "LabVIEW" - }, - { + "key": "Autotools", "name": "Autotools" }, { - "name": "KiCad" - }, - { - "name": "Prestashop" - }, - { - "name": "ROS" - }, - { - "name": "Smalltalk" - }, - { - "name": "GWT" - }, - { - "name": "OracleForms" - }, - { - "name": "SugarCRM" - }, - { - "name": "Nim" - }, - { - "name": "SymphonyCMS" + "key": "C", + "name": "C" }, { - "name": "Maven" + "key": "C++", + "name": "C++" }, { + "key": "CFWheels", "name": "CFWheels" }, { - "name": "Python" - }, - { - "name": "ZendFramework" - }, - { - "name": "CakePHP" - }, - { - "name": "Concrete5" - }, - { - "name": "PlayFramework" - }, - { - "name": "Terraform" - }, - { - "name": "Elixir" - }, - { + "key": "CMake", "name": "CMake" }, { - "name": "Joomla" - }, - { - "name": "Coq" - }, - { - "name": "Delphi" - }, - { - "name": "Haskell" - }, - { - "name": "Yii" - }, - { - "name": "Java" - }, - { - "name": "UnrealEngine" - }, - { - "name": "AppceleratorTitanium" - }, - { - "name": "CraftCMS" - }, - { - "name": "ForceDotCom" - }, - { - "name": "ExtJs" - }, - { - "name": "MetaProgrammingSystem" - }, - { - "name": "D" - }, - { - "name": "Objective-C" - }, - { - "name": "RhodesRhomobile" - }, - { - "name": "R" - }, - { - "name": "EPiServer" - }, - { - "name": "Yeoman" - }, - { - "name": "VisualStudio" - }, - { - "name": "Processing" - }, - { - "name": "Leiningen" - }, - { - "name": "Stella" - }, - { - "name": "Opa" - }, - { - "name": "Drupal" - }, - { - "name": "TurboGears2" - }, - { - "name": "Idris" - }, - { - "name": "Jboss" - }, - { - "name": "CodeIgniter" - }, - { - "name": "Qooxdoo" - }, - { - "name": "Waf" + "key": "CUDA", + "name": "CUDA" }, { - "name": "Sass" + "key": "CakePHP", + "name": "CakePHP" }, { - "name": "Lua" + "key": "ChefCookbook", + "name": "ChefCookbook" }, { + "key": "Clojure", "name": "Clojure" }, { - "name": "IGORPro" - }, - { - "name": "Gradle" - }, - { - "name": "Archives" - }, - { - "name": "SynopsysVCS" - }, - { - "name": "Ninja" - }, - { - "name": "Tags" - }, - { - "name": "OSX" - }, - { - "name": "Dreamweaver" - }, - { - "name": "CodeKit" - }, - { - "name": "NotepadPP" - }, - { - "name": "VisualStudioCode" - }, - { - "name": "Mercurial" - }, - { - "name": "BricxCC" - }, - { - "name": "DartEditor" - }, - { - "name": "Eclipse" - }, - { - "name": "Cloud9" - }, - { - "name": "TortoiseGit" - }, - { - "name": "NetBeans" - }, - { - "name": "GPG" - }, - { - "name": "Espresso" - }, - { - "name": "Redcar" - }, - { - "name": "Xcode" - }, - { - "name": "Matlab" - }, - { - "name": "LyX" - }, - { - "name": "SlickEdit" - }, - { - "name": "Dropbox" - }, - { - "name": "CVS" - }, - { - "name": "Calabash" - }, - { - "name": "JDeveloper" - }, - { - "name": "Vagrant" - }, - { - "name": "IPythonNotebook" - }, - { - "name": "TextMate" - }, - { - "name": "Ensime" - }, - { - "name": "WebMethods" - }, - { - "name": "VirtualEnv" - }, - { - "name": "Emacs" - }, - { - "name": "Momentics" - }, - { - "name": "JetBrains" - }, - { - "name": "SublimeText" - }, - { - "name": "Kate" - }, - { - "name": "ModelSim" - }, - { - "name": "Redis" - }, - { - "name": "KDevelop4" - }, - { - "name": "Bazaar" - }, - { - "name": "Linux" - }, - { - "name": "Windows" - }, - { - "name": "XilinxISE" - }, - { - "name": "Lazarus" - }, - { - "name": "EiffelStudio" - }, - { - "name": "Anjuta" - }, - { - "name": "Vim" - }, - { - "name": "Otto" - }, - { - "name": "MicrosoftOffice" - }, - { - "name": "LibreOffice" - }, - { - "name": "SBT" + "key": "CodeIgniter", + "name": "CodeIgniter" }, { - "name": "MonoDevelop" + "key": "CommonLisp", + "name": "CommonLisp" }, { - "name": "SVN" + "key": "Composer", + "name": "Composer" }, { - "name": "FlexBuilder" + "key": "Concrete5", + "name": "Concrete5" } ] ``` diff --git a/doc/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md index bdb128fc336..cecfc8cd9b9 100644 --- a/doc/api/templates/gitlab_ci_ymls.md +++ b/doc/api/templates/gitlab_ci_ymls.md @@ -17,79 +17,84 @@ Example response: ```json [ { - "name": "C++" - }, - { - "name": "Docker" - }, - { - "name": "Elixir" - }, - { - "name": "LaTeX" - }, - { - "name": "Grails" - }, - { - "name": "Rust" + "key": "Android", + "name": "Android" }, { - "name": "Nodejs" + "key": "Auto-DevOps", + "name": "Auto-DevOps" }, { - "name": "Ruby" + "key": "Bash", + "name": "Bash" }, { - "name": "Scala" + "key": "C++", + "name": "C++" }, { - "name": "Maven" + "key": "Chef", + "name": "Chef" }, { - "name": "Harp" + "key": "Clojure", + "name": "Clojure" }, { - "name": "Pelican" + "key": "Crystal", + "name": "Crystal" }, { - "name": "Hyde" + "key": "Django", + "name": "Django" }, { - "name": "Nanoc" + "key": "Docker", + "name": "Docker" }, { - "name": "Octopress" + "key": "Elixir", + "name": "Elixir" }, { - "name": "JBake" + "key": "Go", + "name": "Go" }, { - "name": "HTML" + "key": "Gradle", + "name": "Gradle" }, { - "name": "Hugo" + "key": "Grails", + "name": "Grails" }, { - "name": "Metalsmith" + "key": "Julia", + "name": "Julia" }, { - "name": "Hexo" + "key": "LaTeX", + "name": "LaTeX" }, { - "name": "Lektor" + "key": "Laravel", + "name": "Laravel" }, { - "name": "Doxygen" + "key": "Maven", + "name": "Maven" }, { - "name": "Brunch" + "key": "Mono", + "name": "Mono" }, { - "name": "Jekyll" + "key": "Nodejs", + "name": "Nodejs" }, { - "name": "Middleman" + "key": "OpenShift", + "name": "OpenShift" } ] ``` diff --git a/doc/api/users.md b/doc/api/users.md index 3b41e0f7ec6..07f03f9c827 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -558,7 +558,7 @@ Parameters: ## List SSH keys for user -Get a list of a specified user's SSH keys. Available only for admin +Get a list of a specified user's SSH keys. ``` GET /users/:id/keys diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md index f479dc74d1f..758ab37861b 100644 --- a/doc/ci/caching/index.md +++ b/doc/ci/caching/index.md @@ -253,7 +253,7 @@ image: python:latest # Change pip's cache directory to be inside the project directory since we can # only cache local items. variables: - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache" + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" # Pip's cache doesn't store the python packages # https://pip.pypa.io/en/stable/reference/pip_install/#caching @@ -262,7 +262,7 @@ variables: # them in a virtualenv and cache it as well. cache: paths: - - .cache/ + - .cache/pip - venv/ before_script: diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 9abedcc6acb..31649ee2792 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -461,25 +461,25 @@ that runner. > - If the repository is private you need to authenticate your GitLab Runner in the > registry. Learn more about how [GitLab Runner works in this case][runner-priv-reg]. -As an example, let's assume that you want to use the `registry.example.com/private/image:latest` +As an example, let's assume that you want to use the `registry.example.com:5000/private/image:latest` image which is private and requires you to login into a private container registry. Let's also assume that these are the login credentials: -| Key | Value | -|----------|----------------------| -| registry | registry.example.com | -| username | my_username | -| password | my_password | +| Key | Value | +|----------|---------------------------| +| registry | registry.example.com:5000 | +| username | my_username | +| password | my_password | -To configure access for `registry.example.com`, follow these steps: +To configure access for `registry.example.com:5000`, follow these steps: 1. Find what the value of `DOCKER_AUTH_CONFIG` should be. There are two ways to accomplish this: - **First way -** Do a `docker login` on your local machine: ```bash - docker login registry.example.com --username my_username --password my_password + docker login registry.example.com:5000 --username my_username --password my_password ``` Then copy the content of `~/.docker/config.json`. @@ -503,7 +503,7 @@ To configure access for `registry.example.com`, follow these steps: ```json { "auths": { - "registry.example.com": { + "registry.example.com:5000": { "auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=" } } @@ -515,22 +515,28 @@ To configure access for `registry.example.com`, follow these steps: registry from it: ```bash - docker logout registry.example.com + docker logout registry.example.com:5000 ``` -1. You can now use any private image from `registry.example.com` defined in +1. You can now use any private image from `registry.example.com:5000` defined in `image` and/or `services` in your `.gitlab-ci.yml` file: ```yaml - image: my.registry.tld:5000/namespace/image:tag + image: registry.example.com:5000/namespace/image:tag ``` - In the example above, GitLab Runner will look at `my.registry.tld:5000` for the + In the example above, GitLab Runner will look at `registry.example.com:5000` for the image `namespace/image:tag`. You can add configuration for as many registries as you want, adding more registries to the `"auths"` hash as described above. +NOTE: **Note:** The full `hostname:port` combination is required everywhere +for the Runner to match the `DOCKER_AUTH_CONFIG`. For example, if +`registry.example.com:5000/namespace/image:tag` is specified in `.gitlab-ci.yml`, +then the `DOCKER_AUTH_CONFIG` must also specify `registry.example.com:5000`. +Specifying only `registry.example.com` will not work. + ## Configuring services Many services accept environment variables which allow you to easily change diff --git a/doc/ci/examples/deployment/README.md b/doc/ci/examples/deployment/README.md index bd60d641493..f53f7c50281 100644 --- a/doc/ci/examples/deployment/README.md +++ b/doc/ci/examples/deployment/README.md @@ -5,7 +5,7 @@ continuous deployment that's developed and used by Travis CI, but can also be used with GitLab CI. >**Note:** -We recommend to use Dpl if you're deploying to any of these of these services: +We recommend to use Dpl if you're deploying to any of these services: https://github.com/travis-ci/dpl#supported-providers. ## Requirements diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md index df83f30fbb7..1ddc1bf4d7e 100644 --- a/doc/ci/interactive_web_terminal/index.md +++ b/doc/ci/interactive_web_terminal/index.md @@ -25,6 +25,12 @@ Two things need to be configured for the interactive web terminal to work: NOTE: **Note:** Not all executors are [supported](https://docs.gitlab.com/runner/executors/#compatibility-chart). +NOTE: **Note:** The `docker` executor does not keep running +after the build script is finished. At that point, the terminal will automatically +disconnect and will not wait for the user to finish. Please follow [this +issue](https://gitlab.com/gitlab-org/gitlab-runner/issues/3605) for updates on +improving this behavior. + Sometimes, when a job is running, things don't go as you would expect, and it would be helpful if one can have a shell to aid debugging. When a job is running, on the right panel you can see a button `debug` that will open the terminal diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index f11949da64e..2d23bf6d2fd 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -94,6 +94,9 @@ future GitLab releases.** | **CI_SERVER_NAME** | all | all | The name of CI server that is used to coordinate jobs | | **CI_SERVER_REVISION** | all | all | GitLab revision that is used to schedule jobs | | **CI_SERVER_VERSION** | all | all | GitLab version that is used to schedule jobs | +| **CI_SERVER_VERSION_MAJOR** | 11.4 | all | GitLab version major component | +| **CI_SERVER_VERSION_MINOR** | 11.4 | all | GitLab version minor component | +| **CI_SERVER_VERSION_PATCH** | 11.4 | all | GitLab version patch component | | **CI_SHARED_ENVIRONMENT** | all | 10.1 | Marks that the job is executed in a shared environment (something that is persisted across CI invocations like `shell` or `ssh` executor). If the environment is shared, it is set to true, otherwise it is not defined at all. | | **GET_SOURCES_ATTEMPTS** | 8.15 | 1.9 | Number of attempts to fetch sources running a job | | **GITLAB_CI** | all | all | Mark that job is executed in GitLab CI environment | @@ -194,7 +197,7 @@ Likewise, group-level variables can be added by going to your group's **Settings > CI/CD**, then finding the section called **Variables**. Any variables of [subgroups] will be inherited recursively. -![Variables](img/secret_variables.png) +![Variables](img/variables.png) Once you set them, they will be available for all subsequent pipelines. You can also [protect your variables](#protected-variables). @@ -323,6 +326,12 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach ++ CI_SERVER_NAME='GitLab CI' ++ export CI_SERVER_VERSION= ++ CI_SERVER_VERSION= +++ export CI_SERVER_VERSION_MAJOR= +++ CI_SERVER_VERSION_MAJOR= +++ export CI_SERVER_VERSION_MINOR= +++ CI_SERVER_VERSION_MINOR= +++ export CI_SERVER_VERSION_PATCH= +++ CI_SERVER_VERSION_PATCH= ++ export CI_SERVER_REVISION= ++ CI_SERVER_REVISION= ++ export GITLAB_CI=true @@ -468,6 +477,9 @@ export CI_SERVER="yes" export CI_SERVER_NAME="GitLab" export CI_SERVER_REVISION="70606bf" export CI_SERVER_VERSION="8.9.0" +export CI_SERVER_VERSION_MAJOR="8" +export CI_SERVER_VERSION_MINOR="9" +export CI_SERVER_VERSION_PATCH="0" export GITLAB_USER_ID="42" export GITLAB_USER_EMAIL="user@example.com" export CI_REGISTRY_USER="gitlab-ci-token" diff --git a/doc/ci/variables/img/secret_variables.png b/doc/ci/variables/img/secret_variables.png Binary files differdeleted file mode 100644 index 3c1aa361dc2..00000000000 --- a/doc/ci/variables/img/secret_variables.png +++ /dev/null diff --git a/doc/ci/variables/img/variables.png b/doc/ci/variables/img/variables.png Binary files differnew file mode 100644 index 00000000000..d2dc99bbac0 --- /dev/null +++ b/doc/ci/variables/img/variables.png diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md index b0d2ea6484d..4e8ce10c9cb 100644 --- a/doc/ci/variables/where_variables_can_be_used.md +++ b/doc/ci/variables/where_variables_can_be_used.md @@ -17,8 +17,8 @@ There are two places defined variables can be used. On the: | Definition | Can be expanded? | Expansion place | Description | |--------------------------------------|-------------------|-----------------|--------------| -| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<ul><li>**Supported:** all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)</li><li>**Not suported:** variables defined in Runner's `config.toml` and variables created in job's `script`</li></ul> | -| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion **doesn't support**: <ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> | +| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).<ul><li>Supported: all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)</li><li>Not suported: variables defined in Runner's `config.toml` and variables created in job's `script`</li></ul> | +| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support: <ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> | | `variables` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | | `image` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | | `services:[]` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | @@ -26,7 +26,7 @@ There are two places defined variables can be used. On the: | `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | | `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment | | `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) | -| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.<br/>**Not supported:**<ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> | +| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.<br/>Not supported:<ul><li>variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)</li><li>any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)</li><li>[persisted variables](#persisted-variables)</li></ul> | ### `config.toml` file @@ -55,9 +55,9 @@ since the expansion is done in GitLab before any Runner will get the job. ### GitLab Runner internal variable expansion mechanism -- **Supported:** project/group variables, `.gitlab-ci.yml` variables, `config.toml` variables, and +- Supported: project/group variables, `.gitlab-ci.yml` variables, `config.toml` variables, and variables from triggers, pipeline schedules, and manual pipelines. -- **Not supported:** variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`). +- Not supported: variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`). The Runner uses Go's `os.Expand()` method for variable expansion. It means that it will handle only variables defined as `$variable` and `${variable}`. What's also important, is that @@ -73,7 +73,7 @@ by bash/sh (leaving empty strings or some values depending whether the variables defined or not), but will not work with Windows' cmd/PowerShell, since these shells are using a different variables syntax. -**Supported:** +Supported: - The `script` may use all available variables that are default for the shell (e.g., `$PATH` which should be present in all bash/sh shells) and all variables defined by GitLab CI/CD (project/group variables, @@ -106,7 +106,9 @@ The following variables are known as "persisted": They are: -- **Supported** for all definitions as [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "Runner". -- **Not supported:** - - By the definitions [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "GitLab". +- Supported for definitions where the ["Expansion place"](#gitlab-ci-yml-file) is: + - Runner. + - Script execution shell. +- Not supported: + - For definitions where the ["Expansion place"](#gitlab-ci-yml-file) is GitLab. - In the `only` and `except` [variables expressions](README.md#variables-expressions). diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index e38628b288b..8b770495853 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -102,10 +102,13 @@ rspec: - $RSPEC ``` -In the example above, the `rspec` job is going to inherit from the `.tests` -template job. GitLab will perform a reverse deep merge, which means that it will -merge the `rspec` contents into `.tests` recursively, and this is going to result in -the following `rspec` job: +In the example above, the `rspec` job inherits from the `.tests` template job. +GitLab will perform a reverse deep merge based on the keys. GitLab will: + +- Merge the `rspec` contents into `.tests` recursively. +- Not merge the values of the keys. + +This results in the following `rspec` job: ```yaml rspec: @@ -118,6 +121,11 @@ rspec: - $RSPEC ``` +NOTE: **Note:** +Note that `script: rake test` has been overwritten by `script: rake rspec`. + +If you do want to include the `rake test`, have a look at [before_script-and-after_script](#before_script-and-after_script). + `.tests` in this example is a [hidden key](#hidden-keys-jobs), but it's possible to inherit from regular jobs as well. @@ -387,6 +395,8 @@ except master. > `refs` and `kubernetes` policies introduced in GitLab 10.0 > > `variables` policy introduced in 10.7 +> +> `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in 11.4 CAUTION: **Warning:** This an _alpha_ feature, and it it subject to change at any time without @@ -398,10 +408,15 @@ policy configuration. GitLab now supports both, simple and complex strategies, so it is possible to use an array and a hash configuration scheme. -Three keys are now available: `refs`, `kubernetes` and `variables`. +Four keys are now available: `refs`, `kubernetes` and `variables` and `changes`. + +### `refs` and `kubernetes` + Refs strategy equals to simplified only/except configuration, whereas kubernetes strategy accepts only `active` keyword. +### `variables` + `variables` keyword is used to define variables expressions. In other words you can use predefined variables / project / group or environment-scoped variables to define an expression GitLab is going to @@ -445,6 +460,46 @@ end-to-end: Learn more about variables expressions on [a separate page][variables-expressions]. +### `changes` + +Using `changes` keyword with `only` or `except` makes it possible to define if +a job should be created based on files modified by a git push event. + +For example: + +```yaml +docker build: + script: docker build -t my-image:$CI_COMMIT_REF_SLUG . + only: + changes: + - Dockerfile + - docker/scripts/* +``` + +In the scenario above, if you are pushing multiple commits to GitLab to an +existing branch, GitLab creates and triggers `docker build` job, provided that +one of the commits contains changes to either: + +- The `Dockerfile` file. +- Any of the files inside `docker/scripts/` directory. + +CAUTION: **Warning:** +There are some caveats when using this feature with new branches and tags. See +the section below. + +#### Using `changes` with new branches and tags + +If you are pushing a **new** branch or a **new** tag to GitLab, the policy +always evaluates to true and GitLab will create a job. This feature is not +connected with merge requests yet, and because GitLab is creating pipelines +before an user can create a merge request we don't know a target branch at +this point. + +Without a target branch, it is not possible to know what the common ancestor is, +thus we always create a job in that case. This feature works best for stable +branches like `master` because in that case GitLab uses the previous commit +that is present in a branch to compare against the latest SHA that was pushed. + ## `tags` `tags` is used to select specific Runners from the list of all Runners that are diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md index fed29d37b26..2f06677bfec 100644 --- a/doc/development/contributing/issue_workflow.md +++ b/doc/development/contributing/issue_workflow.md @@ -60,7 +60,7 @@ people. The current team labels are: -- ~Configuration +- ~Configure - ~"CI/CD" - ~Create - ~Distribution diff --git a/doc/development/database_merge_request_checklist.md b/doc/development/database_merge_request_checklist.md index 75c395b61ef..48864c81592 100644 --- a/doc/development/database_merge_request_checklist.md +++ b/doc/development/database_merge_request_checklist.md @@ -1,15 +1,15 @@ # Merge Request Checklist When creating a merge request that performs database related changes (schema -changes, adjusting queries to optimise performance, etc) you should use the -merge request template called "Database Changes". This template contains a +changes, adjusting queries to optimize performance, etc) you should use the +merge request template called "Database changes". This template contains a checklist of steps to follow to make sure the changes are up to snuff. To use the checklist, create a new merge request and click on the "Choose a -template" dropdown, then click "Database Changes". +template" dropdown, then click "Database changes". An example of this checklist can be found at -https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12463. +<https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12463>. The source code of the checklist can be found in at -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20Changes.md +<https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20changes.md> diff --git a/doc/development/documentation/structure.md b/doc/development/documentation/structure.md index 1002836096a..01068e23082 100644 --- a/doc/development/documentation/structure.md +++ b/doc/development/documentation/structure.md @@ -21,21 +21,21 @@ Before getting started, read through the following docs: Every document should include the following content in the following sequence: - **Feature name**: defines an intuitive name for the feature that clearly -states what it is and is consistent with any relevant UI text. + states what it is and is consistent with any relevant UI text. - **Feature overview** and description: describe what it is, what it does, and in what context it should be used. - **Use cases**: describes real use case scenarios for that feature. - **Requirements**: describes what software and/or configuration is required to be able to -use the feature and, if applicable, prerequisite knowledge for being able to follow/implement the tutorial. -For example, familiarity with GitLab CI/CD, an account on a third-party service, dependencies installed, etc. -Link each one to its most relevant resource; i.e., where the reader can go to begin to fullfil that requirement. -(Another doc page, a third party application's site, etc.) + use the feature and, if applicable, prerequisite knowledge for being able to follow/implement the tutorial. + For example, familiarity with GitLab CI/CD, an account on a third-party service, dependencies installed, etc. + Link each one to its most relevant resource; i.e., where the reader can go to begin to fullfil that requirement. + (Another doc page, a third party application's site, etc.) - **Instructions**: clearly describes the steps to use the feature, leaving no gaps. - **Troubleshooting** guide (recommended but not required): if you know beforehand what issues -one might have when setting it up, or when something is changed, or on upgrading, it's -important to describe those too. Think of things that may go wrong and include them in the -docs. This is important to minimize requests for support, and to avoid doc comments with -questions that you know someone might ask. Answering them beforehand only makes your -document better and more approachable. + one might have when setting it up, or when something is changed, or on upgrading, it's + important to describe those too. Think of things that may go wrong and include them in the + docs. This is important to minimize requests for support, and to avoid doc comments with + questions that you know someone might ask. Answering them beforehand only makes your + document better and more approachable. For additional details, see the subsections below, as well as the [Documentation template for new docs](#Documentation-template-for-new-docs). @@ -55,10 +55,11 @@ You should answer this question: what can you do with this feature/change? Use c are examples of how this feature or change can be used in real life. Examples: -- CE and EE: [Issues](../user/project/issues/index.md#use-cases) -- CE and EE: [Merge Requests](../user/project/merge_requests/index.md#overview) -- EE-only: [Geo](https://docs.gitlab.com/ee/gitlab-geo/README.html#overview) -- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.md#overview) + +- CE and EE: [Issues](../../user/project/issues/index.md#use-cases) +- CE and EE: [Merge Requests](../../user/project/merge_requests/index.md) +- EE-only: [Geo](https://docs.gitlab.com/ee/administration/geo/replication/index.html) +- EE-only: [Jenkins integration](https://docs.gitlab.com/ee/integration/jenkins.html) Note that if you don't have anything to add between the doc title (`<h1>`) and the header `## Overview`, you can omit the header, but keep the content of the @@ -72,14 +73,14 @@ and for every **major** feature present in Community Edition. Your new document will be discoverable by the user only if: - Crosslinked from the higher-level index (e.g., Issue Boards docs -should be linked from Issues; Prometheus docs should be linked from -Monitoring; CI/CD tutorials should be linked from CI/CD examples). + should be linked from Issues; Prometheus docs should be linked from + Monitoring; CI/CD tutorials should be linked from CI/CD examples). - When referencing other GitLab products and features, link to their -respective docs; when referencing third-party products or technologies, -link out to their external sites, documentation, and resources. + respective docs; when referencing third-party products or technologies, + link out to their external sites, documentation, and resources. - The headings are clear. E.g., "App testing" is a bad heading, "Testing -an application with GitLab CI/CD" is much better. Think of something -someone will search for and use these keywords in the headings. + an application with GitLab CI/CD" is much better. Think of something + someone will search for and use these keywords in the headings. ## Documentation template for new docs @@ -133,7 +134,7 @@ is simple and the document is short. - Be clear, concise, and stick to the goal of the doc: explain how to use that feature. - Use inclusive language and avoid jargons, as well as uncommon and -fancy words. The docs should be clear and very easy to understand. +fancy words. The docs should be clear and easy to understand. - Write in the 3rd person (use "we", "you", "us", "one", instead of "I" or "me"). - Always provide internal and external reference links. - Always link the doc from its higher-level index. diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md index 905aa26a40b..dada59ce242 100644 --- a/doc/development/rolling_out_changes_using_feature_flags.md +++ b/doc/development/rolling_out_changes_using_feature_flags.md @@ -151,3 +151,27 @@ most cases this will translate to a feature (with a feature flag) being shipped in RC1, followed by the feature flag being removed in RC2. This in turn means the feature will be stable by the time we publish a stable package around the 22nd of the month. + +## Undefined feature flags default to "on" + +By default, the [`Project#feature_available?`][project-fa], +[`Namespace#feature_available?`][namespace-fa] (EE), and +[`License.feature_available?`][license-fa] (EE) methods will check if the +specified feature is behind a feature flag. Unless the feature is explicitly +disabled or limited to a percentage of users, the feature flag check will +default to `true`. + +As an example, if you were to ship the backend half of a feature behind a flag, +you'd want to explicitly disable that flag until the frontend half is also ready +to be shipped. You can do this via ChatOps: + +``` +/chatops run feature set some_feature 0 +``` + +Note that you can do this at any time, even before the merge request using the +flag has been merged! + +[project-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/app/models/project_feature.rb#L63-68 +[namespace-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/ee/namespace.rb#L71-85 +[license-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/license.rb#L293-300 diff --git a/doc/install/installation.md b/doc/install/installation.md index 7df81fbc46f..25aa5d3369d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -12,7 +12,7 @@ Since installations from source don't have Runit, Sidekiq can't be terminated an ## Select Version to Install -Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install (e.g., `11-3-stable`). +Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install (e.g., `11-4-stable`). You can select the branch in the version dropdown in the top left corner of GitLab (below the menu bar). If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version. @@ -300,9 +300,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-3-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-4-stable gitlab -**Note:** You can change `11-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `11-4-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md index a645f65938f..9546f43eea8 100644 --- a/doc/topics/authentication/index.md +++ b/doc/topics/authentication/index.md @@ -23,7 +23,7 @@ This page gathers all the resources for the topic **Authentication** within GitL - [How to Configure LDAP with GitLab CE](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md) - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/articles/how_to_configure_ldap_gitlab_ee/) - [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/) - - [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/ldap/debugging_ldap.html) + - [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/support-engineering/ldap/debugging_ldap.html) - **Integrations:** - [OmniAuth](../../integration/omniauth.md) - [Authentiq OmniAuth Provider](../../administration/auth/authentiq.md#authentiq-omniauth-provider) @@ -42,7 +42,7 @@ This page gathers all the resources for the topic **Authentication** within GitL ## Third-party resources -- [Kanboard Plugin GitLab Authentication](https://kanboard.net/plugin/gitlab-auth) +- [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth) - [Jenkins GitLab OAuth Plugin](https://wiki.jenkins-ci.org/display/JENKINS/GitLab+OAuth+Plugin) - [Set up Gitlab CE with Active Directory authentication](https://www.caseylabs.com/setup-gitlab-ce-with-active-directory-authentication/) - [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/) diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index b5a9e469965..0d1ba3e8f9a 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -239,14 +239,19 @@ project's **Settings > CI/CD > Auto DevOps**. The available options are: -- **Continuous deployment to production** - enables [Auto Deploy](#auto-deploy) - by setting the [`STAGING_ENABLED`](#deploy-policy-for-staging-and-production-environments) and - [`INCREMENTAL_ROLLOUT_ENABLED`](#incremental-rollout-to-production) variables - to false. -- **Automatic deployment to staging, manual deployment to production** - sets the +- **Continuous deployment to production**: Enables [Auto Deploy](#auto-deploy) + with `master` branch directly deployed to production. +- **Continuous deployment to production using timed incremental rollout**: Sets the + [`INCREMENTAL_ROLLOUT_MODE`](#timed-incremental-rollout-to-production) variable + to `timed`, and production deployment will be executed with a 5 minute delay between + each increment in rollout. +- **Automatic deployment to staging, manual deployment to production**: Sets the [`STAGING_ENABLED`](#deploy-policy-for-staging-and-production-environments) and - [`INCREMENTAL_ROLLOUT_ENABLED`](#incremental-rollout-to-production) variables - to true, and the user is responsible for manually deploying to staging and production. + [`INCREMENTAL_ROLLOUT_MODE`](#incremental-rollout-to-production) variables + to `1` and `manual`. This means: + + - `master` branch is directly deployed to staging. + - Manual actions are provided for incremental rollout to production. ## Stages of Auto DevOps @@ -609,7 +614,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. | | `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | | `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). | -| `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. | +| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> | | `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. | | `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. | | `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. | @@ -730,9 +735,8 @@ to use an incremental rollout to replace just a few pods with the latest code. This will allow you to first check how the app is behaving, and later manually increasing the rollout up to 100%. -If `INCREMENTAL_ROLLOUT_ENABLED` is defined in your project (e.g., set -`INCREMENTAL_ROLLOUT_ENABLED` to `1` as a secret variable), then instead of the -standard `production` job, 4 different +If `INCREMENTAL_ROLLOUT_MODE` is set to `manual` in your project, then instead +of the standard `production` job, 4 different [manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph) will be created: @@ -756,21 +760,45 @@ environment page. Below, you can see how the pipeline will look if the rollout or staging variables are defined. -- **Without `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`** +Without `INCREMENTAL_ROLLOUT_MODE` and without `STAGING_ENABLED`: + +![Staging and rollout disabled](img/rollout_staging_disabled.png) + +Without `INCREMENTAL_ROLLOUT_MODE` and with `STAGING_ENABLED`: - ![Staging and rollout disabled](img/rollout_staging_disabled.png) +![Staging enabled](img/staging_enabled.png) -- **Without `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`** +With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and without `STAGING_ENABLED`: - ![Staging enabled](img/staging_enabled.png) +![Rollout enabled](img/rollout_enabled.png) -- **With `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`** +With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and with `STAGING_ENABLED` + +![Rollout and staging enabled](img/rollout_staging_enabled.png) + +CAUTION: **Caution:** +Before GitLab 11.4 this feature was enabled by the presence of the +`INCREMENTAL_ROLLOUT_ENABLED` environment variable. +This configuration is deprecated and will be removed in the future. + +#### Timed incremental rollout to production **[PREMIUM]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7545) in GitLab 11.4. + +TIP: **Tip:** +You can also set this inside your [project's settings](#deployment-strategy). - ![Rollout enabled](img/rollout_enabled.png) +This configuration based on +[incremental rollout to production](#incremental-rollout-to-production). -- **With `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`** +Everything behaves the same way, except: - ![Rollout and staging enabled](img/rollout_staging_enabled.png) +- It's enabled by setting the `INCREMENTAL_ROLLOUT_MODE` variable to `timed`. +- Instead of the standard `production` job, the following jobs with a 5 minute delay between each are created: + 1. `timed rollout 10%` + 1. `timed rollout 25%` + 1. `timed rollout 50%` + 1. `timed rollout 100%` ## Currently supported languages diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md index 7ca441a2f74..b12e86cb0a9 100644 --- a/doc/topics/autodevops/quick_start_guide.md +++ b/doc/topics/autodevops/quick_start_guide.md @@ -15,7 +15,7 @@ need to ensure your own [Runners are configured](../../ci/runners/README.md) and Before creating and connecting your Kubernetes cluster to your GitLab project, you need a Google Cloud Platform account. If you don't already have one, -sign up at https://console.cloud.google.com. You'll need to either sign in with an existing +sign up at <https://console.cloud.google.com>. You'll need to either sign in with an existing Google account (for example, one that you use to access Gmail, Drive, etc.) or create a new one. 1. Follow the steps as outlined in the ["Before you begin" section of the Kubernetes Engine docs](https://cloud.google.com/kubernetes-engine/docs/quickstart#before-you-begin) @@ -205,7 +205,7 @@ applications. In the rightmost column for the production environment, you can ma application is running. Right below, there is the -[Deploy Board](https://docs.gitlab.com/ee/user/project/deploy_boards.md). +[Deploy Board](https://docs.gitlab.com/ee/user/project/deploy_boards.html). The squares represent pods in your Kubernetes cluster that are associated with the given environment. Hovering above each square you can see the state of a deployment and clicking a square will take you to the pod's logs page. @@ -264,8 +264,8 @@ Let's fix that: to stage the changes. 1. Write a commit message and click **Commit**. -Now, if you go back to the merge request you should not only see the test passing, -but also the application deployed as a [review app](index.md#auto-review-apps). You +Now, if you go back to the merge request you should not only see the test passing, but +also the application deployed as a [review app](index.md#auto-review-apps). You can visit it by following the URL in the merge request. The changes that we previously made should be there. diff --git a/doc/update/11.2-to-11-3.md b/doc/update/11.2-to-11.3.md index d77f879ee57..d77f879ee57 100644 --- a/doc/update/11.2-to-11-3.md +++ b/doc/update/11.2-to-11.3.md diff --git a/doc/update/11.3-to-11.4.md b/doc/update/11.3-to-11.4.md new file mode 100644 index 00000000000..985239369d7 --- /dev/null +++ b/doc/update/11.3-to-11.4.md @@ -0,0 +1,378 @@ +--- +comments: false +--- + +# From 11.3 to 11.4 + +Make sure you view this update guide from the branch (version) of GitLab you would +like to install (e.g., `11-4-stable`. You can select the branch in the version +dropdown at the top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Backup + +NOTE: If you installed GitLab from source, make sure `rsync` is installed. + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Update Ruby + +NOTE: GitLab 11.0 and higher only support Ruby 2.4.x and dropped support for Ruby 2.3.x. Be +sure to upgrade your interpreter if necessary. + +You can check which version you are running with `ruby -v`. + +Download Ruby and compile it: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.4.tar.gz +echo 'ec82b0d53bd0adad9b19e6b45e44d54e9ec3f10c ruby-2.4.4.tar.gz' | shasum -c - && tar xzf ruby-2.4.4.tar.gz +cd ruby-2.4.4 + +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +### 4. Update Node + +GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets. +This requires a minimum version of node v6.0.0. + +You can check which version you are running with `node -v`. If you are running +a version older than `v6.0.0` you will need to update to a newer version. You +can find instructions to install from community maintained packages or compile +from source at the nodejs.org website. + +<https://nodejs.org/en/download/> + +GitLab also requires the use of yarn `>= v1.2.0` to manage JavaScript +dependencies. + +```bash +curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - +echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list +sudo apt-get update +sudo apt-get install yarn +``` + +More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install). + +### 5. Update Go + +NOTE: GitLab 11.0 and higher only supports Go 1.9.x and newer, and dropped support for Go +1.5.x through 1.8.x. Be sure to upgrade your installation if necessary. + +You can check which version you are running with `go version`. + +Download and install Go: + +```bash +# Remove former Go installation folder +sudo rm -rf /usr/local/go + +curl --remote-name --progress https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz +echo 'fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035 go1.10.3.linux-amd64.tar.gz' | shasum -a256 -c - && \ + sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz +sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/ +rm go1.10.3.linux-amd64.tar.gz +``` + +### 6. Get latest code + +```bash +cd /home/git/gitlab + +sudo -u git -H git fetch --all --prune +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +sudo -u git -H git checkout -- locale +``` + +For GitLab Community Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 11-4-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +cd /home/git/gitlab + +sudo -u git -H git checkout 11-4-stable-ee +``` + +### 7. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell + +sudo -u git -H git fetch --all --tags --prune +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION) +sudo -u git -H bin/compile +``` + +### 8. Update gitlab-workhorse + +Install and compile gitlab-workhorse. GitLab-Workhorse uses +[GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. + +```bash +cd /home/git/gitlab-workhorse + +sudo -u git -H git fetch --all --tags --prune +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION) +sudo -u git -H make +``` + +### 9. Update Gitaly + +#### New Gitaly configuration options required + +In order to function Gitaly needs some additional configuration information. Below we assume you installed Gitaly in `/home/git/gitaly` and GitLab Shell in `/home/git/gitlab-shell`. + +```shell +echo ' +[gitaly-ruby] +dir = "/home/git/gitaly/ruby" + +[gitlab-shell] +dir = "/home/git/gitlab-shell" +' | sudo -u git tee -a /home/git/gitaly/config.toml +``` + +#### Check Gitaly configuration + +Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly +configuration file may contain syntax errors. The block name +`[[storages]]`, which may occur more than once in your `config.toml` +file, should be `[[storage]]` instead. + +```shell +sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml +``` + +#### Compile Gitaly + +```shell +cd /home/git/gitaly +sudo -u git -H git fetch --all --tags --prune +sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION) +sudo -u git -H make +``` + +### 10. Update gitlab-pages + +#### Only needed if you use GitLab Pages. + +Install and compile gitlab-pages. GitLab-Pages uses +[GNU Make](https://www.gnu.org/software/make/). +If you are not using Linux you may have to run `gmake` instead of +`make` below. + +```bash +cd /home/git/gitlab-pages + +sudo -u git -H git fetch --all --tags --prune +sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION) +sudo -u git -H make +``` + +### 11. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';" +``` + +If you use MySQL with replication, or just have MySQL configured with binary logging, +you will need to also run the following on all of your MySQL servers: + +```bash +mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;" +``` + +You can make this setting permanent by adding it to your `my.cnf`: + +``` +log_bin_trust_function_creators=1 +``` + +### 12. Update configuration files + +#### New configuration options for `gitlab.yml` + +There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +cd /home/git/gitlab + +git diff origin/11-1-stable:config/gitlab.yml.example origin/11-4-stable:config/gitlab.yml.example +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +cd /home/git/gitlab + +# For HTTPS configurations +git diff origin/11-1-stable:lib/support/nginx/gitlab-ssl origin/11-4-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/11-1-stable:lib/support/nginx/gitlab origin/11-4-stable:lib/support/nginx/gitlab +``` + +If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx +configuration as GitLab application no longer handles setting it. + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-4-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-4-stable/config/initializers/smtp_settings.rb.sample#L13 + +#### Init script + +There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`: + +```sh +cd /home/git/gitlab + +git diff origin/11-1-stable:lib/support/init.d/gitlab.default.example origin/11-4-stable:lib/support/init.d/gitlab.default.example +``` + +Ensure you're still up-to-date with the latest init script changes: + +```bash +cd /home/git/gitlab + +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +For Ubuntu 16.04.1 LTS: + +```bash +sudo systemctl daemon-reload +``` + +### 13. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Compile GetText PO files + +sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production + +# Update node dependencies and recompile assets +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production + +# Clean up cache +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). + +### 14. Start application + +```bash +sudo service gitlab start +sudo service nginx restart +``` + +### 15. Check application status + +Check if GitLab and its environment are configured correctly: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production +``` + +To make sure you didn't miss anything run a more thorough check: + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +``` + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (11.3) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 11.2 to 11.3](11.2-to-11.3.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab + +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. + +[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-4-stable/config/gitlab.yml.example +[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-4-stable/lib/support/init.d/gitlab.default.example diff --git a/doc/user/admin_area/settings/terms.md b/doc/user/admin_area/settings/terms.md index aa817c9a209..e2290bf0598 100644 --- a/doc/user/admin_area/settings/terms.md +++ b/doc/user/admin_area/settings/terms.md @@ -35,17 +35,17 @@ continue their registration afterwards. ## Accepting terms -When this feature was enabled, the users that have not accepted the +When this feature is enabled, the users that have not accepted the terms of service will be presented with a screen where they can either accept or decline the terms. ![Respond to terms](img/respond_to_terms.png) -When the user accepts the terms, they will be directed to where they +If the user accepts the terms, they will be directed to where they were going. After a sign-in or sign-up this will most likely be the dashboard. -When the user was already logged in when the feature was turned on, +If the user was already logged in when the feature was turned on, they will be asked to accept the terms on their next interaction. -When a user declines the terms, they will be signed out. +If a user declines the terms, they will be signed out. diff --git a/doc/user/img/color_inline_colorchip_render_gfm.png b/doc/user/img/color_inline_colorchip_render_gfm.png Binary files differnew file mode 100644 index 00000000000..6a8a674d6e0 --- /dev/null +++ b/doc/user/img/color_inline_colorchip_render_gfm.png diff --git a/doc/user/img/math_inline_sup_render_gfm.png b/doc/user/img/math_inline_sup_render_gfm.png Binary files differnew file mode 100644 index 00000000000..bf1464457bc --- /dev/null +++ b/doc/user/img/math_inline_sup_render_gfm.png diff --git a/doc/user/img/mermaid_diagram_render_gfm.png b/doc/user/img/mermaid_diagram_render_gfm.png Binary files differnew file mode 100644 index 00000000000..3b3eb3a738a --- /dev/null +++ b/doc/user/img/mermaid_diagram_render_gfm.png diff --git a/doc/user/img/task_list_ordered_render_gfm.png b/doc/user/img/task_list_ordered_render_gfm.png Binary files differnew file mode 100644 index 00000000000..fdff8a9886c --- /dev/null +++ b/doc/user/img/task_list_ordered_render_gfm.png diff --git a/doc/user/img/unordered_check_list_render_gfm.png b/doc/user/img/unordered_check_list_render_gfm.png Binary files differnew file mode 100644 index 00000000000..2e3fb7cbb79 --- /dev/null +++ b/doc/user/img/unordered_check_list_render_gfm.png diff --git a/doc/user/markdown.md b/doc/user/markdown.md index fb132f0613b..f9bdaea185b 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -112,8 +112,8 @@ GFM will autolink almost any URL you copy and paste into your text: * https://www.google.com * https://google.com/ * ftp://ftp.us.debian.org/debian/ -* smb://foo/bar/baz -* irc://irc.freenode.net/gitlab +* <a href="smb://foo/bar/baz">smb://foo/bar/baz</a> +* <a href="irc://irc.freenode.net/gitlab">irc://irc.freenode.net/gitlab</a> * http://localhost:3000 ### Multiline Blockquote @@ -138,17 +138,13 @@ you can quote that without having to manually prepend `>` to every line! >>> ``` ->>> -If you paste a message from somewhere else - -that - -spans - -multiple lines, - -you can quote that without having to manually prepend `>` to every line! ->>> +<blockquote dir="auto"> +<p>If you paste a message from somewhere else</p> +<p>that</p> +<p>spans</p> +<p>multiple lines,</p> +<p>you can quote that without having to manually prepend <code>></code> to every line!</p> +</blockquote> ### Code and Syntax Highlighting @@ -269,15 +265,15 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. -Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: +Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px">. Well we have a gift for you: -:zap: You can use emoji anywhere GFM is supported. :v: +<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/zap.png" width="20px" height="20px">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/v.png" width="20px" height="20px"> -You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. +You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/bug.png" width="20px" height="20px"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speak_no_evil.png" width="20px" height="20px"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/snail.png" width="20px" height="20px"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/birthday.png" width="20px" height="20px">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/heart.png" width="20px" height="20px"> you for that. -If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up one of the supported codes. +If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px">. All you need to do is to look up one of the supported codes. -Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: +Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px"> Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. @@ -286,7 +282,6 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. - ### Special GitLab References GFM recognizes special references. @@ -356,11 +351,7 @@ You can add task lists to issues, merge requests and comments. To create a task - [ ] Sub-task 3 ``` -- [x] Completed task -- [ ] Incomplete task - - [ ] Sub-task 1 - - [x] Sub-task 2 - - [ ] Sub-task 3 +![alt unordered-check-list-render-gfm](img/unordered_check_list_render_gfm.png) Tasks formatted as ordered lists are supported as well: @@ -371,10 +362,7 @@ Tasks formatted as ordered lists are supported as well: 1. [x] Sub-task 2 ``` -1. [x] Completed task -1. [ ] Incomplete task - 1. [ ] Sub-task 1 - 1. [x] Sub-task 2 +![alt task-list-ordered-render-gfm](img/task_list_ordered_render_gfm.png) Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. @@ -393,7 +381,10 @@ The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`. Here's a sample video: -![Sample Video](img/markdown_video.mp4) +<div class="video-container"> + <video src="img/markdown_video.mp4" width="400" controls="true" data-setup="{}" data-title="Sample Video"></video> + <p><a href="img/markdown_video.mp4" target="_blank" rel="noopener noreferrer" title="Download 'Sample Video'">Sample Video</a></p> +</div> ### Math @@ -417,12 +408,11 @@ Example: Becomes: -This math is inline $`a^2+b^2=c^2`$. +This math is inline ![alt text](img/math_inline_sup_render_gfm.png). This is on a separate line -```math -a^2+b^2=c^2 -``` + +<div align="center"><img src="./img/math_inline_sup_render_gfm.png" ></div> _Be advised that KaTeX only supports a [subset][katex-subset] of LaTeX._ @@ -452,15 +442,7 @@ Examples: Become: -`#F00` -`#F00A` -`#FF0000` -`#FF0000AA` -`RGB(0,255,0)` -`RGB(0%,100%,0%)` -`RGBA(0,255,0,0.7)` -`HSL(540,70%,50%)` -`HSLA(540,70%,50%,0.7)` +![alt color-inline-colorchip-render-gfm](img/color_inline_colorchip_render_gfm.png) #### Supported formats: @@ -492,13 +474,7 @@ Example: Becomes: -```mermaid -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; -``` +<img src="./img/mermaid_diagram_render_gfm.png" width="200px" height="400px"> For details see the [Mermaid official page][mermaid]. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 8369cff2386..4359592905d 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -42,6 +42,8 @@ The following table depicts the various user permission levels in a project. | See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | +| View license management reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | +| View Security reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ | | Download project | [^1] | ✓ | ✓ | ✓ | ✓ | | Assign issues | | ✓ | ✓ | ✓ | ✓ | @@ -57,6 +59,7 @@ The following table depicts the various user permission levels in a project. | See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | | Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ | | Lock issue discussions | | ✓ | ✓ | ✓ | ✓ | +| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | | Lock merge request discussions | | | ✓ | ✓ | ✓ | | Create new environments | | | ✓ | ✓ | ✓ | | Stop environments | | | ✓ | ✓ | ✓ | @@ -73,6 +76,9 @@ The following table depicts the various user permission levels in a project. | Update a container registry | | | ✓ | ✓ | ✓ | | Remove a container registry image | | | ✓ | ✓ | ✓ | | Create/edit/delete project milestones | | | ✓ | ✓ | ✓ | +| View approved/blacklisted licenses **[ULTIMATE]** | | | ✓ | ✓ | ✓ | +| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ | +| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ | | Use environment terminals | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | @@ -90,6 +96,7 @@ The following table depicts the various user permission levels in a project. | Manage GitLab Pages domains and certificates | | | | ✓ | ✓ | | Remove GitLab Pages | | | | | ✓ | | Manage clusters | | | | ✓ | ✓ | +| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ | | Edit comments (posted by any user) | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | @@ -98,7 +105,7 @@ The following table depicts the various user permission levels in a project. | Remove pages | | | | | ✓ | | Force push to protected branches [^4] | | | | | | | Remove protected branches [^4] | | | | | | -| View project Audit Events | | | | ✓ | ✓ | +| View project Audit Events | | | | ✓ | ✓ | ## Project features permissions @@ -110,6 +117,7 @@ which visibility level you select on project settings. - Disabled: disabled for everyone - Only team members: only team members will see even if your project is public or internal - Everyone with access: everyone can see depending on your project visibility level +- Everyone: enabled for everyone (only available for GitLab Pages) ### Protected branches @@ -242,6 +250,7 @@ which visibility level you select on project settings. - Disabled: disabled for everyone - Only team members: only team members will see even if your project is public or internal - Everyone with access: everyone can see depending on your project visibility level +- Everyone: enabled for everyone (only available for GitLab Pages) ## GitLab CI/CD permissions diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index e5411662511..bc6ecdf4f32 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -2,18 +2,18 @@ Two-factor Authentication (2FA) provides an additional level of security to your GitLab account. Once enabled, in addition to supplying your username and -password to login, you'll be prompted for a code generated by an application on -your phone. +password to login, you'll be prompted for a code generated by your one time password +authenticator. For example, a password manager on one of your devices. By enabling 2FA, the only way someone other than you can log into your account -is to know your username and password *and* have access to your phone. +is to know your username and password *and* have access to your one time password secret. ## Overview > **Note:** When you enable 2FA, don't forget to back up your recovery codes. -In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as +In addition to one time authenticators (TOTP), GitLab supports U2F (universal 2nd factor) devices as the second factor of authentication. Once enabled, in addition to supplying your username and password to login, you'll be prompted to activate your U2F device (usually by pressing a button on it), and it will perform secure authentication on your behalf. @@ -24,10 +24,10 @@ from other browsers. ## Enabling 2FA -There are two ways to enable two-factor authentication: via a mobile application +There are two ways to enable two-factor authentication: via a one time password authenticator or a U2F device. -### Enable 2FA via mobile application +### Enable 2FA via one time password authenticator **In GitLab:** @@ -82,7 +82,7 @@ Click on **Register U2F Device** to complete the process. > **Note:** Recovery codes are not generated for U2F devices. -Should you ever lose access to your phone, you can use one of the ten provided +Should you ever lose access to your one time password authenticator, you can use one of the ten provided backup codes to login to your account. We suggest copying or printing them for storage in a safe place. **Each code can be used only once** to log in to your account. @@ -98,7 +98,7 @@ be presented with a second prompt, depending on which type of 2FA you've enabled ### Log in via mobile application -Enter the pin from your phone's application or a recovery code to log in. +Enter the pin from your one time password authenticator's application or a recovery code to log in. ![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png) diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index 8604ea27f99..ab62762f343 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -115,6 +115,13 @@ Please be aware that your status is publicly visible even if your [profile is pr To set your current status: +1. Open the user menu in the top-right corner of the navigation bar. +1. Hit **Set status**, or **Edit status** if you have already set a status. +1. Set the emoji and/or status message to your liking. +1. Hit **Set status**. Alternatively, you can also hit **Remove status** to remove your user status entirely. + +or + 1. Navigate to your personal [profile settings](#profile-settings). 1. In the text field below `Your status`, enter your status message. 1. Select an emoji from the dropdown if you like. diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 41768998a59..3ec17806490 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -134,36 +134,11 @@ authorization is [experimental](#role-based-access-control-rbac). > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21401) in GitLab 11.4. CAUTION: **Warning:** -The RBAC authorization is experimental. To enable it you need access to the -server where GitLab is installed. +The RBAC authorization is experimental. -The support for RBAC-enabled clusters is hidden behind a feature flag. Once -the feature flag is enabled, GitLab will create the necessary service accounts +Once RBAC is enabled for a cluster, GitLab will create the necessary service accounts and privileges in order to install and run [GitLab managed applications](#installing-applications). -To enable the feature flag: - -1. SSH into the server where GitLab is installed. -1. Enter the Rails console: - - **For Omnibus GitLab** - - ```sh - sudo gitlab-rails console - ``` - - **For installations from source** - - ```sh - sudo -u git -H bundle exec rails console - ``` - -1. Enable the RBAC authorization: - - ```ruby - Feature.enable('rbac_clusters') - ``` - If you are creating a [new GKE cluster via GitLab](#adding-and-creating-a-new-gke-cluster-via-gitlab), you will be asked if you would like to create an RBAC-enabled cluster. Enabling this @@ -240,7 +215,7 @@ twice, which can lead to confusion during deployments. | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) | | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) | | [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) | -| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | +| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with [Rubix](https://github.com/amit1rrr/rubix). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | ## Getting the external IP address diff --git a/doc/user/project/import/svn.md b/doc/user/project/import/svn.md index 16bc5121027..a5923986292 100644 --- a/doc/user/project/import/svn.md +++ b/doc/user/project/import/svn.md @@ -29,7 +29,7 @@ directly in a filesystem level. 1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). -1. Download SubGit from https://subgit.com/download/. +1. Download SubGit from <https://subgit.com/download/>. 1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit` command will be available at `/opt/subgit-VERSION/bin/subgit`. @@ -71,7 +71,7 @@ edit $GIT_REPO_PATH/subgit/config ``` For more information regarding the SubGit configuration options, refer to -[SubGit's documentation](https://subgit.com/documentation.html) website. +[SubGit's documentation](https://subgit.com/documentation/) website. ### Initial translation @@ -97,7 +97,7 @@ subgit import $GIT_REPO_PATH ### SubGit licensing Running SubGit in a mirror mode requires a -[registration](https://subgit.com/pricing.html). Registration is free for open +[registration](https://subgit.com/pricing/). Registration is free for open source, academic and startup projects. We're currently working on deeper GitLab/SubGit integration. You may track our @@ -179,5 +179,6 @@ git push --tags origin ``` ## Contribute to this guide + We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems. diff --git a/doc/user/project/issues/create_new_issue.md b/doc/user/project/issues/create_new_issue.md index 1688edc1ee2..c33d1365001 100644 --- a/doc/user/project/issues/create_new_issue.md +++ b/doc/user/project/issues/create_new_issue.md @@ -58,3 +58,21 @@ body becomes the issue description. [Markdown] and [quick actions] are supported. ![Bottom of a project issues page](img/new_issue_from_email.png) + +## New issue via URL with prefilled fields + +You can link directly to the new issue page for a given project, with prefilled +field values using query string parameters in a URL. This is useful for embedding +a URL in an external HTML page, and also certain scenarios where you want the user to +create an issue with certain fields prefilled. + +The title, description, and description template fields can be prefilled using +this method. The description and description template fields cannot be pre-entered +in the same URL (since a description template just populates the description field). + +Follow these examples to form your new issue URL with prefilled fields. + +- For a new issue in the GitLab Community Edition project with a pre-entered title +and a pre-entered description, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issue[description]=Research%20idea` +- For a new issue in the GitLab Community Edition project with a pre-entered title +and a pre-entered description template, the URL would be `https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issue[title]=Validate%20new%20concept&issuable_template=Research%20proposal` diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index df045822740..c2f53540089 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -1,9 +1,9 @@ # GitLab quick actions -Quick actions are textual shortcuts for common actions on issues, epics, merge requests, +Quick actions are textual shortcuts for common actions on issues, epics, merge requests, and commits that are usually done by clicking buttons or dropdowns in GitLab's UI. You can enter these commands while creating a new issue or merge request, or -in comments of issues, epics, merge requests, and commits. Each command should be +in comments of issues, epics, merge requests, and commits. Each command should be on a separate line in order to be properly detected and executed. Once executed, the commands are removed from the text body and not visible to anyone else. @@ -38,7 +38,9 @@ discussions, and descriptions: | `/remove_estimate` | Remove time estimate | ✓ | ✓ | | <code>/spend <time(1h 30m | -1h 5m)> <date(YYYY-MM-DD)></code> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ | | `/remove_time_spent` | Remove time spent | ✓ | ✓ | -| <code>/due <in 2 days | this Friday | December 31st></code>| Set due date | ✓ | +| `/lock` | Lock the discussion | ✓ | ✓ | +| `/unlock` | Unlock the discussion | ✓ | ✓ | +| <code>/due <in 2 days | this Friday | December 31st></code>| Set due date | ✓ | | | `/remove_due_date` | Remove due date | ✓ | | | `/weight 0,1,2, ...` | Set weight **[STARTER]** | ✓ | | | `/clear_weight` | Clears weight **[STARTER]** | ✓ | | @@ -68,7 +70,7 @@ The following quick actions are applicable for epics threads and description: |:---------------------------|:----------------------------------------| | `/tableflip <Comment>` | Append the comment with `(╯°□°)╯︵ â”»â”â”»` | | `/shrug <Comment>` | Append the comment with `¯\_(ツ)_/¯` | -| `/todo` | Add a todo | +| `/todo` | Add a todo | | `/done` | Mark todo as done | | `/subscribe` | Subscribe | | `/unsubscribe` | Unsubscribe | @@ -78,4 +80,4 @@ The following quick actions are applicable for epics threads and description: | `/award :emoji:` | Toggle emoji award | | `/label ~label1 ~label2` | Add label(s) | | `/unlabel ~label1 ~label2` | Remove all or specific label(s) | -| `/relabel ~label1 ~label2` | Replace label |
\ No newline at end of file +| `/relabel ~label1 ~label2` | Replace label | diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md index 4f076ee01b8..c6239c8e41c 100644 --- a/doc/user/project/repository/gpg_signed_commits/index.md +++ b/doc/user/project/repository/gpg_signed_commits/index.md @@ -57,7 +57,7 @@ started: gpg --full-gen-key ``` -_NOTE: In some cases like Gpg4win on Windows and other Mac OS versions the command here may be ` gpg --gen-key`_ + _NOTE: In some cases like Gpg4win on Windows and other Mac OS versions the command here may be ` gpg --gen-key`_ This will spawn a series of questions. diff --git a/lib/after_commit_queue.rb b/lib/after_commit_queue.rb index a4d8507960e..6fb7985f955 100644 --- a/lib/after_commit_queue.rb +++ b/lib/after_commit_queue.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module AfterCommitQueue extend ActiveSupport::Concern diff --git a/lib/api/api.rb b/lib/api/api.rb index 06c8b48b8cc..c49c52213bf 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -132,6 +132,7 @@ module API mount ::API::Projects mount ::API::ProjectSnapshots mount ::API::ProjectSnippets + mount ::API::ProjectTemplates mount ::API::ProtectedBranches mount ::API::ProtectedTags mount ::API::Repositories diff --git a/lib/api/commits.rb b/lib/api/commits.rb index ff927d1aa3c..e59abd3e3d0 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -98,6 +98,7 @@ module API optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from' optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' + optional :stats, type: Boolean, default: true, desc: 'Include commit stats' end post ':id/repository/commits' do authorize_push_to_branch!(params[:branch]) @@ -113,7 +114,7 @@ module API Gitlab::WebIdeCommitsCounter.increment if find_user_from_warden - present commit_detail, with: Entities::CommitDetail + present commit_detail, with: Entities::CommitDetail, stats: params[:stats] else render_api_error!(result[:message], 400) end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a78a93cbfd9..120545792f2 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1221,6 +1221,7 @@ module API end class TemplatesList < Grape::Entity + expose :key expose :name end diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index 50d8a1ac596..de77bef43ce 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -12,7 +12,8 @@ module API detail "This feature was introduced in GitLab 11.0." end post do - context = { only_path: false } + context = { only_path: false, current_user: current_user } + context[:pipeline] = params[:gfm] ? :full : :plain_markdown if params[:project] project = Project.find_by_full_path(params[:project]) @@ -24,9 +25,7 @@ module API context[:skip_project_check] = true end - context[:pipeline] = params[:gfm] ? :full : :plain_markdown - - { html: Banzai.render(params[:text], context) } + { html: Banzai.render_and_post_process(params[:text], context) } end end end diff --git a/lib/api/project_templates.rb b/lib/api/project_templates.rb new file mode 100644 index 00000000000..d05ddad7466 --- /dev/null +++ b/lib/api/project_templates.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module API + class ProjectTemplates < Grape::API + include PaginationParams + + TEMPLATE_TYPES = %w[dockerfiles gitignores gitlab_ci_ymls licenses].freeze + + before { authenticate_non_get! } + + params do + requires :id, type: String, desc: 'The ID of a project' + requires :type, type: String, values: TEMPLATE_TYPES, desc: 'The type (dockerfiles|gitignores|gitlab_ci_ymls|licenses) of the template' + end + resource :projects do + desc 'Get a list of templates available to this project' do + detail 'This endpoint was introduced in GitLab 11.4' + end + params do + use :pagination + end + get ':id/templates/:type' do + templates = TemplateFinder + .build(params[:type], user_project) + .execute + + present paginate(::Kaminari.paginate_array(templates)), with: Entities::TemplatesList + end + + desc 'Download a template available to this project' do + detail 'This endpoint was introduced in GitLab 11.4' + end + params do + requires :name, type: String, desc: 'The name of the template' + + optional :project, type: String, desc: 'The project name to use when expanding placeholders in the template. Only affects licenses' + optional :fullname, type: String, desc: 'The full name of the copyright holder to use when expanding placeholders in the template. Only affects licenses' + end + get ':id/templates/:type/:name', requirements: { name: /[\w\.-]+/ } do + template = TemplateFinder + .build(params[:type], user_project, name: params[:name]) + .execute + + not_found!('Template') unless template.present? + + template.resolve!( + project_name: params[:project].presence, + fullname: params[:fullname].presence || current_user&.name + ) + + if template.is_a?(::LicenseTemplate) + present template, with: Entities::License + else + present template, with: Entities::Template + end + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 00bad49ebdc..ae2d327e45b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -287,6 +287,12 @@ module API present_projects forks end + desc 'Check pages access of this project' + get ':id/pages_access' do + authorize! :read_pages_content, user_project unless user_project.public_pages? + status 200 + end + desc 'Update an existing project' do success Entities::Project end diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 60868821810..ce70460af11 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -113,7 +113,7 @@ module API optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES use :pagination end - get ':id/jobs' do + get ':id/jobs' do runner = get_runner(params[:id]) authenticate_list_runners_jobs!(runner) diff --git a/lib/api/templates.rb b/lib/api/templates.rb index 8ff3b2ac33c..8dab19d50c2 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -35,7 +35,7 @@ module API popular = declared(params)[:popular] popular = to_boolean(popular) if popular.present? - templates = TemplateFinder.build(:licenses, popular: popular).execute + templates = TemplateFinder.build(:licenses, nil, popular: popular).execute present paginate(::Kaminari.paginate_array(templates)), with: ::API::Entities::License end @@ -48,8 +48,7 @@ module API requires :name, type: String, desc: 'The name of the template' end get "templates/licenses/:name", requirements: { name: /[\w\.-]+/ } do - templates = TemplateFinder.build(:licenses).execute - template = templates.find { |template| template.key == params[:name] } + template = TemplateFinder.build(:licenses, nil, name: params[:name]).execute not_found!('License') unless template.present? @@ -72,7 +71,7 @@ module API use :pagination end get "templates/#{template_type}" do - templates = ::Kaminari.paginate_array(TemplateFinder.new(template_type).execute) + templates = ::Kaminari.paginate_array(TemplateFinder.build(template_type, nil).execute) present paginate(templates), with: Entities::TemplatesList end @@ -84,7 +83,7 @@ module API requires :name, type: String, desc: 'The name of the template' end get "templates/#{template_type}/:name" do - finder = TemplateFinder.build(template_type, name: declared(params)[:name]) + finder = TemplateFinder.build(template_type, nil, name: declared(params)[:name]) new_template = finder.execute render_response(template_type, new_template) diff --git a/lib/api/users.rb b/lib/api/users.rb index 11a7f4ef64d..501c5cf1df3 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -256,7 +256,7 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Get the SSH keys of a specified user. Available only for admins.' do + desc 'Get the SSH keys of a specified user.' do success Entities::SSHKey end params do @@ -265,10 +265,8 @@ module API end # rubocop: disable CodeReuse/ActiveRecord get ':id/keys' do - authenticated_as_admin! - user = User.find_by(id: params[:id]) - not_found!('User') unless user + not_found!('User') unless user && can?(current_user, :read_user, user) present paginate(user.keys), with: Entities::SSHKey end diff --git a/lib/backup.rb b/lib/backup.rb index e2c62af23ae..2712b33b4b4 100644 --- a/lib/backup.rb +++ b/lib/backup.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Backup Error = Class.new(StandardError) end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 5d4a7efc456..afdc6f383c1 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -243,6 +243,7 @@ module Backup backup_created_at: Time.now, gitlab_version: Gitlab::VERSION, tar_version: tar_version, + installation_type: Gitlab::INSTALLATION_TYPE, skipped: ENV["SKIP"] } end diff --git a/lib/banzai.rb b/lib/banzai.rb index 5df98f66f3b..1eb41ff7133 100644 --- a/lib/banzai.rb +++ b/lib/banzai.rb @@ -1,4 +1,13 @@ +# frozen_string_literal: true + module Banzai + # if you need to render markdown, then you probably need to post_process as well, + # such as removing references that the current user doesn't have + # permission to make + def self.render_and_post_process(text, context = {}) + post_process(render(text, context), context) + end + def self.render(text, context = {}) Renderer.render(text, context) end diff --git a/lib/banzai/color_parser.rb b/lib/banzai/color_parser.rb index 355c364b07b..6d01d51955c 100644 --- a/lib/banzai/color_parser.rb +++ b/lib/banzai/color_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ColorParser ALPHA = /0(?:\.\d+)?|\.\d+|1(?:\.0+)?/ # 0.0..1.0 diff --git a/lib/banzai/commit_renderer.rb b/lib/banzai/commit_renderer.rb index c351a155ae5..f346151a3c1 100644 --- a/lib/banzai/commit_renderer.rb +++ b/lib/banzai/commit_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module CommitRenderer ATTRIBUTES = [:description, :title].freeze diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb index 3f1e95d4cc0..b7344808989 100644 --- a/lib/banzai/cross_project_reference.rb +++ b/lib/banzai/cross_project_reference.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai # Common methods for ReferenceFilters that support an optional cross-project # reference. @@ -13,6 +15,7 @@ module Banzai # Returns a Project, or nil if the reference can't be found def parent_from_ref(ref) return context[:project] || context[:group] unless ref + return context[:project] if context[:project]&.full_path == ref Project.find_by_full_path(ref) end diff --git a/lib/banzai/filter.rb b/lib/banzai/filter.rb index 3eb544dfef9..7d9766c906c 100644 --- a/lib/banzai/filter.rb +++ b/lib/banzai/filter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter def self.[](name) diff --git a/lib/banzai/filter/epic_reference_filter.rb b/lib/banzai/filter/epic_reference_filter.rb index e06e2fb3870..26bcf5c04b4 100644 --- a/lib/banzai/filter/epic_reference_filter.rb +++ b/lib/banzai/filter/epic_reference_filter.rb @@ -9,6 +9,12 @@ module Banzai def self.object_class Epic end + + private + + def group + context[:group] || context[:project]&.group + end end end end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index b92e9e55bb9..04ec38209c7 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -48,7 +48,7 @@ module Banzai include_ancestor_groups: true, only_group_labels: true } else - { project_id: parent.id, + { project: parent, include_ancestor_groups: true } end diff --git a/lib/banzai/filter/markdown_engines/common_mark.rb b/lib/banzai/filter/markdown_engines/common_mark.rb index dbb25280849..e52c0d15b31 100644 --- a/lib/banzai/filter/markdown_engines/common_mark.rb +++ b/lib/banzai/filter/markdown_engines/common_mark.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # `CommonMark` markdown engine for GitLab's Banzai markdown filter. # This module is used in Banzai::Filter::MarkdownFilter. # Used gem is `commonmarker` which is a ruby wrapper for libcmark (CommonMark parser) diff --git a/lib/banzai/filter/markdown_engines/redcarpet.rb b/lib/banzai/filter/markdown_engines/redcarpet.rb index ac99941fefa..ec150d041ff 100644 --- a/lib/banzai/filter/markdown_engines/redcarpet.rb +++ b/lib/banzai/filter/markdown_engines/redcarpet.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # `Redcarpet` markdown engine for GitLab's Banzai markdown filter. # This module is used in Banzai::Filter::MarkdownFilter. # Used gem is `redcarpet` which is a ruby library for markdown processing. diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb index 4bf80aff418..f4cc8beeb52 100644 --- a/lib/banzai/filter/wiki_link_filter/rewriter.rb +++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Filter class WikiLinkFilter < HTML::Pipeline::Filter diff --git a/lib/banzai/filter_array.rb b/lib/banzai/filter_array.rb index 77835a14027..818af4643a7 100644 --- a/lib/banzai/filter_array.rb +++ b/lib/banzai/filter_array.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai class FilterArray < Array # Insert a value immediately after another value diff --git a/lib/banzai/issuable_extractor.rb b/lib/banzai/issuable_extractor.rb index ae7dc71e7eb..0a05d46db4c 100644 --- a/lib/banzai/issuable_extractor.rb +++ b/lib/banzai/issuable_extractor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai # Extract references to issuables from multiple documents diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb index a176f1e261b..75661ffa233 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai # Class for rendering multiple objects (e.g. Note instances) in a single pass, # using +render_field+ to benefit from caching in the database. Rendering and @@ -38,6 +40,7 @@ module Banzai redacted_data = redacted[index] object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html(save_options).html_safe) # rubocop:disable GitlabSecurity/PublicSend object.user_visible_reference_count = redacted_data[:visible_reference_count] if object.respond_to?(:user_visible_reference_count) + object.total_reference_count = redacted_data[:total_reference_count] if object.respond_to?(:total_reference_count) end end diff --git a/lib/banzai/pipeline.rb b/lib/banzai/pipeline.rb index 142a9962eb1..e8a81bebaa9 100644 --- a/lib/banzai/pipeline.rb +++ b/lib/banzai/pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline def self.[](name) diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb index 1048b927cd3..cc4af280872 100644 --- a/lib/banzai/pipeline/ascii_doc_pipeline.rb +++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class AsciiDocPipeline < BasePipeline diff --git a/lib/banzai/pipeline/atom_pipeline.rb b/lib/banzai/pipeline/atom_pipeline.rb index 9694e4bc23f..13a342351b6 100644 --- a/lib/banzai/pipeline/atom_pipeline.rb +++ b/lib/banzai/pipeline/atom_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class AtomPipeline < FullPipeline diff --git a/lib/banzai/pipeline/base_pipeline.rb b/lib/banzai/pipeline/base_pipeline.rb index 3ae3bed570d..87d1cf9912f 100644 --- a/lib/banzai/pipeline/base_pipeline.rb +++ b/lib/banzai/pipeline/base_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class BasePipeline diff --git a/lib/banzai/pipeline/broadcast_message_pipeline.rb b/lib/banzai/pipeline/broadcast_message_pipeline.rb index 5dd572de3a1..a3d63e0aaf5 100644 --- a/lib/banzai/pipeline/broadcast_message_pipeline.rb +++ b/lib/banzai/pipeline/broadcast_message_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class BroadcastMessagePipeline < DescriptionPipeline diff --git a/lib/banzai/pipeline/combined_pipeline.rb b/lib/banzai/pipeline/combined_pipeline.rb index 60190f8d9dd..56b424dc8e0 100644 --- a/lib/banzai/pipeline/combined_pipeline.rb +++ b/lib/banzai/pipeline/combined_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline module CombinedPipeline diff --git a/lib/banzai/pipeline/commit_description_pipeline.rb b/lib/banzai/pipeline/commit_description_pipeline.rb index 607c2731ed3..e8ec7453f0f 100644 --- a/lib/banzai/pipeline/commit_description_pipeline.rb +++ b/lib/banzai/pipeline/commit_description_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class CommitDescriptionPipeline < SingleLinePipeline diff --git a/lib/banzai/pipeline/description_pipeline.rb b/lib/banzai/pipeline/description_pipeline.rb index 042fb2e6e14..d5ff9b025cc 100644 --- a/lib/banzai/pipeline/description_pipeline.rb +++ b/lib/banzai/pipeline/description_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class DescriptionPipeline < FullPipeline diff --git a/lib/banzai/pipeline/email_pipeline.rb b/lib/banzai/pipeline/email_pipeline.rb index 8f5f144d582..2c08581ce0d 100644 --- a/lib/banzai/pipeline/email_pipeline.rb +++ b/lib/banzai/pipeline/email_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class EmailPipeline < FullPipeline diff --git a/lib/banzai/pipeline/full_pipeline.rb b/lib/banzai/pipeline/full_pipeline.rb index 3c974f73176..a5b1cbdd030 100644 --- a/lib/banzai/pipeline/full_pipeline.rb +++ b/lib/banzai/pipeline/full_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class FullPipeline < CombinedPipeline.new(PlainMarkdownPipeline, GfmPipeline) diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index bd34614f149..be75e34a673 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class GfmPipeline < BasePipeline diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb index c56d908009f..db79a22549c 100644 --- a/lib/banzai/pipeline/markup_pipeline.rb +++ b/lib/banzai/pipeline/markup_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class MarkupPipeline < BasePipeline diff --git a/lib/banzai/pipeline/note_pipeline.rb b/lib/banzai/pipeline/note_pipeline.rb index 7890f20f716..4480d7ede05 100644 --- a/lib/banzai/pipeline/note_pipeline.rb +++ b/lib/banzai/pipeline/note_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class NotePipeline < FullPipeline diff --git a/lib/banzai/pipeline/plain_markdown_pipeline.rb b/lib/banzai/pipeline/plain_markdown_pipeline.rb index 3f45db21869..b64f13cde47 100644 --- a/lib/banzai/pipeline/plain_markdown_pipeline.rb +++ b/lib/banzai/pipeline/plain_markdown_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class PlainMarkdownPipeline < BasePipeline diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb index 0b2e584ef16..63a998a2c1f 100644 --- a/lib/banzai/pipeline/post_process_pipeline.rb +++ b/lib/banzai/pipeline/post_process_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class PostProcessPipeline < BasePipeline diff --git a/lib/banzai/pipeline/pre_process_pipeline.rb b/lib/banzai/pipeline/pre_process_pipeline.rb index 6cf219661d3..c937f783180 100644 --- a/lib/banzai/pipeline/pre_process_pipeline.rb +++ b/lib/banzai/pipeline/pre_process_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class PreProcessPipeline < BasePipeline diff --git a/lib/banzai/pipeline/relative_link_pipeline.rb b/lib/banzai/pipeline/relative_link_pipeline.rb index 270990e7ab4..88651892acc 100644 --- a/lib/banzai/pipeline/relative_link_pipeline.rb +++ b/lib/banzai/pipeline/relative_link_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class RelativeLinkPipeline < BasePipeline diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb index cd5a6c8875c..61ff7b0bcce 100644 --- a/lib/banzai/pipeline/single_line_pipeline.rb +++ b/lib/banzai/pipeline/single_line_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class SingleLinePipeline < GfmPipeline diff --git a/lib/banzai/pipeline/wiki_pipeline.rb b/lib/banzai/pipeline/wiki_pipeline.rb index c37b8e71cb0..97a03895ff3 100644 --- a/lib/banzai/pipeline/wiki_pipeline.rb +++ b/lib/banzai/pipeline/wiki_pipeline.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Pipeline class WikiPipeline < FullPipeline diff --git a/lib/banzai/querying.rb b/lib/banzai/querying.rb index a19a05e8c0d..55aa5fa66c3 100644 --- a/lib/banzai/querying.rb +++ b/lib/banzai/querying.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Querying module_function diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb index 28928d6f376..7db5f5e1f7d 100644 --- a/lib/banzai/redactor.rb +++ b/lib/banzai/redactor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai # Class for removing Markdown references a certain user is not allowed to # view. @@ -37,7 +39,13 @@ module Banzai all_document_nodes.each do |entry| nodes_for_document = entry[:nodes] - doc_data = { document: entry[:document], visible_reference_count: nodes_for_document.count } + + doc_data = { + document: entry[:document], + total_reference_count: nodes_for_document.count, + visible_reference_count: nodes_for_document.count + } + metadata << doc_data nodes_for_document.each do |node| diff --git a/lib/banzai/reference_extractor.rb b/lib/banzai/reference_extractor.rb index 78588299c18..3fc3ae02088 100644 --- a/lib/banzai/reference_extractor.rb +++ b/lib/banzai/reference_extractor.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor diff --git a/lib/banzai/reference_parser.rb b/lib/banzai/reference_parser.rb index 557bec4316e..efe15096f08 100644 --- a/lib/banzai/reference_parser.rb +++ b/lib/banzai/reference_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser # Returns the reference parser class for the given type diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index 3ab154a7b1c..8419769085a 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser # Base class for reference parsing classes. @@ -215,7 +217,7 @@ module Banzai # def projects_for_nodes(nodes) @projects_for_nodes ||= - grouped_objects_for_nodes(nodes, Project, 'data-project') + grouped_objects_for_nodes(nodes, Project.includes(:project_feature), 'data-project') end def can?(user, permission, subject = :global) diff --git a/lib/banzai/reference_parser/commit_parser.rb b/lib/banzai/reference_parser/commit_parser.rb index 30dc87248b4..0bfb6a92020 100644 --- a/lib/banzai/reference_parser/commit_parser.rb +++ b/lib/banzai/reference_parser/commit_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class CommitParser < BaseParser diff --git a/lib/banzai/reference_parser/commit_range_parser.rb b/lib/banzai/reference_parser/commit_range_parser.rb index 2920e886938..480eefd5c4d 100644 --- a/lib/banzai/reference_parser/commit_range_parser.rb +++ b/lib/banzai/reference_parser/commit_range_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class CommitRangeParser < BaseParser diff --git a/lib/banzai/reference_parser/directly_addressed_user_parser.rb b/lib/banzai/reference_parser/directly_addressed_user_parser.rb index 77df9bbd024..1f18f82b916 100644 --- a/lib/banzai/reference_parser/directly_addressed_user_parser.rb +++ b/lib/banzai/reference_parser/directly_addressed_user_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class DirectlyAddressedUserParser < UserParser diff --git a/lib/banzai/reference_parser/epic_parser.rb b/lib/banzai/reference_parser/epic_parser.rb index 08b8a4c9a0f..7f366f0f8ab 100644 --- a/lib/banzai/reference_parser/epic_parser.rb +++ b/lib/banzai/reference_parser/epic_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser # The actual parser is implemented in the EE mixin diff --git a/lib/banzai/reference_parser/external_issue_parser.rb b/lib/banzai/reference_parser/external_issue_parser.rb index 1802cd04854..029b09dcd25 100644 --- a/lib/banzai/reference_parser/external_issue_parser.rb +++ b/lib/banzai/reference_parser/external_issue_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class ExternalIssueParser < BaseParser diff --git a/lib/banzai/reference_parser/issuable_parser.rb b/lib/banzai/reference_parser/issuable_parser.rb index fad127d7e5b..f8c26288017 100644 --- a/lib/banzai/reference_parser/issuable_parser.rb +++ b/lib/banzai/reference_parser/issuable_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class IssuableParser < BaseParser diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index 7b5915899cf..97c7173ac0f 100644 --- a/lib/banzai/reference_parser/issue_parser.rb +++ b/lib/banzai/reference_parser/issue_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class IssueParser < IssuableParser diff --git a/lib/banzai/reference_parser/label_parser.rb b/lib/banzai/reference_parser/label_parser.rb index 30e2a012f09..398cc45fea0 100644 --- a/lib/banzai/reference_parser/label_parser.rb +++ b/lib/banzai/reference_parser/label_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class LabelParser < BaseParser diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb index 9e5d55f72bc..e8147ac591a 100644 --- a/lib/banzai/reference_parser/merge_request_parser.rb +++ b/lib/banzai/reference_parser/merge_request_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class MergeRequestParser < IssuableParser diff --git a/lib/banzai/reference_parser/milestone_parser.rb b/lib/banzai/reference_parser/milestone_parser.rb index 68675abe22a..925d736fb9a 100644 --- a/lib/banzai/reference_parser/milestone_parser.rb +++ b/lib/banzai/reference_parser/milestone_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class MilestoneParser < BaseParser diff --git a/lib/banzai/reference_parser/snippet_parser.rb b/lib/banzai/reference_parser/snippet_parser.rb index 3ade168b566..6f6ac08de04 100644 --- a/lib/banzai/reference_parser/snippet_parser.rb +++ b/lib/banzai/reference_parser/snippet_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class SnippetParser < BaseParser diff --git a/lib/banzai/reference_parser/user_parser.rb b/lib/banzai/reference_parser/user_parser.rb index ceb7f1d165c..067b06b7590 100644 --- a/lib/banzai/reference_parser/user_parser.rb +++ b/lib/banzai/reference_parser/user_parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module ReferenceParser class UserParser < BaseParser diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index 0050295eeda..c7239a5eaa6 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Renderer # Convert a Markdown String into an HTML-safe String of HTML diff --git a/lib/banzai/renderer/common_mark/html.rb b/lib/banzai/renderer/common_mark/html.rb index 0b27316da1b..837665451a1 100644 --- a/lib/banzai/renderer/common_mark/html.rb +++ b/lib/banzai/renderer/common_mark/html.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Renderer module CommonMark diff --git a/lib/banzai/renderer/redcarpet/html.rb b/lib/banzai/renderer/redcarpet/html.rb index 30e815f1224..84931fdc784 100644 --- a/lib/banzai/renderer/redcarpet/html.rb +++ b/lib/banzai/renderer/redcarpet/html.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module Renderer module Redcarpet diff --git a/lib/banzai/request_store_reference_cache.rb b/lib/banzai/request_store_reference_cache.rb index 9a9704f9837..91fb489b72d 100644 --- a/lib/banzai/request_store_reference_cache.rb +++ b/lib/banzai/request_store_reference_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Banzai module RequestStoreReferenceCache def cached_call(request_store_key, cache_key, path: []) diff --git a/lib/bitbucket/client.rb b/lib/bitbucket/client.rb index f8ee7e0f9ae..1343f424c51 100644 --- a/lib/bitbucket/client.rb +++ b/lib/bitbucket/client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket class Client attr_reader :connection diff --git a/lib/bitbucket/collection.rb b/lib/bitbucket/collection.rb index a78495dbf5e..9c496daccaa 100644 --- a/lib/bitbucket/collection.rb +++ b/lib/bitbucket/collection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket class Collection < Enumerator def initialize(paginator) diff --git a/lib/bitbucket/connection.rb b/lib/bitbucket/connection.rb index ba5a9e2f04c..0041634f9e3 100644 --- a/lib/bitbucket/connection.rb +++ b/lib/bitbucket/connection.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket class Connection DEFAULT_API_VERSION = '2.0'.freeze diff --git a/lib/bitbucket/error/unauthorized.rb b/lib/bitbucket/error/unauthorized.rb index efe10542f19..3cde11babee 100644 --- a/lib/bitbucket/error/unauthorized.rb +++ b/lib/bitbucket/error/unauthorized.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket module Error Unauthorized = Class.new(StandardError) diff --git a/lib/bitbucket/page.rb b/lib/bitbucket/page.rb index 2b0a3fe7b1a..7cc1342ad65 100644 --- a/lib/bitbucket/page.rb +++ b/lib/bitbucket/page.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket class Page attr_reader :attrs, :items diff --git a/lib/bitbucket/paginator.rb b/lib/bitbucket/paginator.rb index 135d0d55674..0d004592e67 100644 --- a/lib/bitbucket/paginator.rb +++ b/lib/bitbucket/paginator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket class Paginator PAGE_LENGTH = 50 # The minimum length is 10 and the maximum is 100. diff --git a/lib/bitbucket/representation/base.rb b/lib/bitbucket/representation/base.rb index 800d5a075c6..bb8dcd91ad5 100644 --- a/lib/bitbucket/representation/base.rb +++ b/lib/bitbucket/representation/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket module Representation class Base diff --git a/lib/bitbucket/representation/comment.rb b/lib/bitbucket/representation/comment.rb index 4937aa9728f..1b8dc27793a 100644 --- a/lib/bitbucket/representation/comment.rb +++ b/lib/bitbucket/representation/comment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket module Representation class Comment < Representation::Base diff --git a/lib/bitbucket/representation/issue.rb b/lib/bitbucket/representation/issue.rb index 44bcbc250b3..a88797cdab9 100644 --- a/lib/bitbucket/representation/issue.rb +++ b/lib/bitbucket/representation/issue.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket module Representation class Issue < Representation::Base diff --git a/lib/bitbucket/representation/pull_request.rb b/lib/bitbucket/representation/pull_request.rb index eebf8093380..6a0e8b354bf 100644 --- a/lib/bitbucket/representation/pull_request.rb +++ b/lib/bitbucket/representation/pull_request.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket module Representation class PullRequest < Representation::Base diff --git a/lib/bitbucket/representation/pull_request_comment.rb b/lib/bitbucket/representation/pull_request_comment.rb index c52acbc3ddc..34dbf9ad22d 100644 --- a/lib/bitbucket/representation/pull_request_comment.rb +++ b/lib/bitbucket/representation/pull_request_comment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket module Representation class PullRequestComment < Comment diff --git a/lib/bitbucket/representation/repo.rb b/lib/bitbucket/representation/repo.rb index 59b0fda8e14..c5bfc91e43d 100644 --- a/lib/bitbucket/representation/repo.rb +++ b/lib/bitbucket/representation/repo.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket module Representation class Repo < Representation::Base diff --git a/lib/bitbucket/representation/user.rb b/lib/bitbucket/representation/user.rb index ba6b7667b49..2b45d751e70 100644 --- a/lib/bitbucket/representation/user.rb +++ b/lib/bitbucket/representation/user.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Bitbucket module Representation class User < Representation::Base diff --git a/lib/carrier_wave_string_file.rb b/lib/carrier_wave_string_file.rb index 6c848902e4a..c9a64d9e631 100644 --- a/lib/carrier_wave_string_file.rb +++ b/lib/carrier_wave_string_file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class CarrierWaveStringFile < StringIO def original_filename "" diff --git a/lib/constraints/feature_constrainer.rb b/lib/constraints/feature_constrainer.rb index 05d48b0f25a..ca4376a9d38 100644 --- a/lib/constraints/feature_constrainer.rb +++ b/lib/constraints/feature_constrainer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Constraints class FeatureConstrainer attr_reader :feature diff --git a/lib/constraints/group_url_constrainer.rb b/lib/constraints/group_url_constrainer.rb index 87649c50424..8a3f8d2faaf 100644 --- a/lib/constraints/group_url_constrainer.rb +++ b/lib/constraints/group_url_constrainer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Constraints class GroupUrlConstrainer def matches?(request) diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb index 32aea98f0f7..eadfbf7bc01 100644 --- a/lib/constraints/project_url_constrainer.rb +++ b/lib/constraints/project_url_constrainer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Constraints class ProjectUrlConstrainer def matches?(request) diff --git a/lib/constraints/user_url_constrainer.rb b/lib/constraints/user_url_constrainer.rb index 8afa04d29a4..e763569cb2e 100644 --- a/lib/constraints/user_url_constrainer.rb +++ b/lib/constraints/user_url_constrainer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Constraints class UserUrlConstrainer def matches?(request) diff --git a/lib/container_registry/blob.rb b/lib/container_registry/blob.rb index d5f85f9fcad..837b22c3082 100644 --- a/lib/container_registry/blob.rb +++ b/lib/container_registry/blob.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ContainerRegistry class Blob attr_reader :repository, :config diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb index 010ca1ec27b..c80f49f5ae0 100644 --- a/lib/container_registry/client.rb +++ b/lib/container_registry/client.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'faraday' require 'faraday_middleware' diff --git a/lib/container_registry/config.rb b/lib/container_registry/config.rb index 589f9f4380a..740c0e13da0 100644 --- a/lib/container_registry/config.rb +++ b/lib/container_registry/config.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ContainerRegistry class Config attr_reader :tag, :blob, :data diff --git a/lib/container_registry/path.rb b/lib/container_registry/path.rb index 1ab14c1c155..9b2a61cdedc 100644 --- a/lib/container_registry/path.rb +++ b/lib/container_registry/path.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ContainerRegistry ## # Class responsible for extracting project and repository name from diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb index f90d711474a..523364ac7c7 100644 --- a/lib/container_registry/registry.rb +++ b/lib/container_registry/registry.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ContainerRegistry class Registry attr_reader :uri, :client, :path diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb index c785bca4dad..8633e764f90 100644 --- a/lib/container_registry/tag.rb +++ b/lib/container_registry/tag.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ContainerRegistry class Tag attr_reader :repository, :name diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb index dda6cd38dcd..5e22523e45a 100644 --- a/lib/declarative_policy.rb +++ b/lib/declarative_policy.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_dependency 'declarative_policy/cache' require_dependency 'declarative_policy/condition' require_dependency 'declarative_policy/delegate_dsl' @@ -10,8 +12,6 @@ require_dependency 'declarative_policy/step' require_dependency 'declarative_policy/base' -require 'thread' - module DeclarativePolicy CLASS_CACHE_MUTEX = Mutex.new CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE diff --git a/lib/declarative_policy/base.rb b/lib/declarative_policy/base.rb index da3fabba39b..cd6e1606f22 100644 --- a/lib/declarative_policy/base.rb +++ b/lib/declarative_policy/base.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy class Base # A map of ability => list of rules together with :enable diff --git a/lib/declarative_policy/cache.rb b/lib/declarative_policy/cache.rb index 780d8f707bd..13006e56454 100644 --- a/lib/declarative_policy/cache.rb +++ b/lib/declarative_policy/cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy module Cache class << self diff --git a/lib/declarative_policy/condition.rb b/lib/declarative_policy/condition.rb index 51c4a8b2bbe..b77f40b1093 100644 --- a/lib/declarative_policy/condition.rb +++ b/lib/declarative_policy/condition.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy # A Condition is the data structure that is created by the # `condition` declaration on DeclarativePolicy::Base. It is diff --git a/lib/declarative_policy/delegate_dsl.rb b/lib/declarative_policy/delegate_dsl.rb index ca2eb98e3e8..67e3429b696 100644 --- a/lib/declarative_policy/delegate_dsl.rb +++ b/lib/declarative_policy/delegate_dsl.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy # Used when the name of a delegate is mentioned in # the rule DSL. diff --git a/lib/declarative_policy/policy_dsl.rb b/lib/declarative_policy/policy_dsl.rb index c96049768a1..96741c0478e 100644 --- a/lib/declarative_policy/policy_dsl.rb +++ b/lib/declarative_policy/policy_dsl.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy # The return value of a rule { ... } declaration. # Can call back to register rules with the containing diff --git a/lib/declarative_policy/preferred_scope.rb b/lib/declarative_policy/preferred_scope.rb index c77784cb49d..239780d8626 100644 --- a/lib/declarative_policy/preferred_scope.rb +++ b/lib/declarative_policy/preferred_scope.rb @@ -1,4 +1,7 @@ -module DeclarativePolicy # rubocop:disable Naming/FileName +# rubocop:disable Naming/FileName +# frozen_string_literal: true + +module DeclarativePolicy PREFERRED_SCOPE_KEY = :"DeclarativePolicy.preferred_scope" class << self diff --git a/lib/declarative_policy/rule.rb b/lib/declarative_policy/rule.rb index 407398cc770..f38f4f0a50f 100644 --- a/lib/declarative_policy/rule.rb +++ b/lib/declarative_policy/rule.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy module Rule # A Rule is the object that results from the `rule` declaration, diff --git a/lib/declarative_policy/rule_dsl.rb b/lib/declarative_policy/rule_dsl.rb index 7254b08eda5..85da7f261fa 100644 --- a/lib/declarative_policy/rule_dsl.rb +++ b/lib/declarative_policy/rule_dsl.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy # The DSL evaluation context inside rule { ... } blocks. # Responsible for creating and combining Rule objects. diff --git a/lib/declarative_policy/runner.rb b/lib/declarative_policy/runner.rb index fec672f4b8c..f739fe5e16e 100644 --- a/lib/declarative_policy/runner.rb +++ b/lib/declarative_policy/runner.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy class Runner class State diff --git a/lib/declarative_policy/step.rb b/lib/declarative_policy/step.rb index 3469fe9f991..c289c17cc19 100644 --- a/lib/declarative_policy/step.rb +++ b/lib/declarative_policy/step.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DeclarativePolicy # This object represents one step in the runtime decision of whether # an ability is allowed. It contains a Rule and a context (instance diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb index 7b1533d0d32..c83cec9dc4a 100644 --- a/lib/expand_variables.rb +++ b/lib/expand_variables.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ExpandVariables class << self def expand(value, variables) diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index e02d403f7b1..a340a276640 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Module providing methods for dealing with separating a tree-ish string and a # file path string when combined in a request parameter module ExtractsPath @@ -50,7 +52,9 @@ module ExtractsPath # branches and tags # Append a trailing slash if we only get a ref and no file path - id += '/' unless id.ends_with?('/') + unless id.ends_with?('/') + id = [id, '/'].join + end valid_refs = ref_names.select { |v| id.start_with?("#{v}/") } @@ -151,9 +155,9 @@ module ExtractsPath # overriden in subclasses, do not remove def get_id - id = params[:id] || params[:ref] - id += "/" + params[:path] unless params[:path].blank? - id + id = [params[:id] || params[:ref]] + id << "/" + params[:path] unless params[:path].blank? + id.join end def ref_names diff --git a/lib/feature.rb b/lib/feature.rb index 0e90ad9a333..e048a443abc 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'flipper/adapters/active_record' require 'flipper/adapters/active_support_cache_store' @@ -72,7 +74,11 @@ class Feature end def flipper - @flipper ||= (Gitlab::SafeRequestStore[:flipper] ||= build_flipper_instance) + if Gitlab::SafeRequestStore.active? + Gitlab::SafeRequestStore[:flipper] ||= build_flipper_instance + else + @flipper ||= build_flipper_instance + end end def build_flipper_instance diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb index 53aa8d04e5c..70a145cd5bd 100644 --- a/lib/file_size_validator.rb +++ b/lib/file_size_validator.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class FileSizeValidator < ActiveModel::EachValidator MESSAGES = { is: :wrong_size, minimum: :size_too_small, maximum: :size_too_big }.freeze CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze diff --git a/lib/forever.rb b/lib/forever.rb index 7df17912544..0a37118fe68 100644 --- a/lib/forever.rb +++ b/lib/forever.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class Forever POSTGRESQL_DATE = DateTime.new(3000, 1, 1) MYSQL_DATE = DateTime.new(2038, 01, 19) diff --git a/lib/gitlab.rb b/lib/gitlab.rb index 7790534d5d7..2bb09684441 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_dependency 'gitlab/popen' module Gitlab diff --git a/lib/gitlab/background_migration/remove_restricted_todos.rb b/lib/gitlab/background_migration/remove_restricted_todos.rb index 68f3fa62170..9941c2fe6d9 100644 --- a/lib/gitlab/background_migration/remove_restricted_todos.rb +++ b/lib/gitlab/background_migration/remove_restricted_todos.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true # rubocop:disable Style/Documentation +# rubocop:disable Metrics/ClassLength module Gitlab module BackgroundMigration @@ -49,11 +50,14 @@ module Gitlab private def remove_non_members_todos(project_id) - Todo.where(project_id: project_id) - .where('user_id NOT IN (?)', authorized_users(project_id)) - .each_batch(of: 5000) do |batch| - batch.delete_all - end + if Gitlab::Database.postgresql? + batch_remove_todos_cte(project_id) + else + unauthorized_project_todos(project_id) + .each_batch(of: 5000) do |batch| + batch.delete_all + end + end end def remove_confidential_issue_todos(project_id) @@ -86,10 +90,13 @@ module Gitlab next if target_types.empty? - Todo.where(project_id: project_id) - .where('user_id NOT IN (?)', authorized_users(project_id)) - .where(target_type: target_types) - .delete_all + if Gitlab::Database.postgresql? + batch_remove_todos_cte(project_id, target_types) + else + unauthorized_project_todos(project_id) + .where(target_type: target_types) + .delete_all + end end end @@ -100,6 +107,65 @@ module Gitlab def authorized_users(project_id) ProjectAuthorization.select(:user_id).where(project_id: project_id) end + + def unauthorized_project_todos(project_id) + Todo.where(project_id: project_id) + .where('user_id NOT IN (?)', authorized_users(project_id)) + end + + def batch_remove_todos_cte(project_id, target_types = nil) + loop do + count = remove_todos_cte(project_id, target_types) + + break if count == 0 + end + end + + def remove_todos_cte(project_id, target_types = nil) + sql = [] + sql << with_all_todos_sql(project_id, target_types) + sql << as_deleted_sql + sql << "SELECT count(*) FROM deleted" + + result = Todo.connection.exec_query(sql.join(' ')) + result.rows[0][0].to_i + end + + def with_all_todos_sql(project_id, target_types = nil) + if target_types + table = Arel::Table.new(:todos) + in_target = table[:target_type].in(target_types) + target_types_sql = " AND #{in_target.to_sql}" + end + + <<-SQL + WITH all_todos AS ( + SELECT id + FROM "todos" + WHERE "todos"."project_id" = #{project_id} + AND (user_id NOT IN ( + SELECT "project_authorizations"."user_id" + FROM "project_authorizations" + WHERE "project_authorizations"."project_id" = #{project_id}) + #{target_types_sql} + ) + ), + SQL + end + + def as_deleted_sql + <<-SQL + deleted AS ( + DELETE FROM todos + WHERE id IN ( + SELECT id + FROM all_todos + LIMIT 5000 + ) + RETURNING id + ) + SQL + end end end end diff --git a/lib/gitlab/ci/build/policy/changes.rb b/lib/gitlab/ci/build/policy/changes.rb new file mode 100644 index 00000000000..7bf51519752 --- /dev/null +++ b/lib/gitlab/ci/build/policy/changes.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Build + module Policy + class Changes < Policy::Specification + def initialize(globs) + @globs = Array(globs) + end + + def satisfied_by?(pipeline, seed) + return true unless pipeline.branch_updated? + + pipeline.modified_paths.any? do |path| + @globs.any? do |glob| + File.fnmatch?(glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH) + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 03971254310..f290ff3a565 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -36,7 +36,7 @@ module Gitlab validates :extends, type: String end - validates :start_in, duration: true, if: :delayed? + validates :start_in, duration: { limit: '1 day' }, if: :delayed? validates :start_in, absence: true, unless: :delayed? end diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb index a78a85397bd..a3d4432be82 100644 --- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb +++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb @@ -11,6 +11,15 @@ module Gitlab false end + def validate_duration_limit(value, limit) + return false unless value.is_a?(String) + + ChronicDuration.parse(value).second.from_now < + ChronicDuration.parse(limit).second.from_now + rescue ChronicDuration::DurationParseError + false + end + def validate_array_of_strings(values) values.is_a?(Array) && values.all? { |value| validate_string(value) } end diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index 09e8e52b60f..c92562f8c85 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -25,17 +25,19 @@ module Gitlab include Entry::Validatable include Entry::Attributable - attributes :refs, :kubernetes, :variables + ALLOWED_KEYS = %i[refs kubernetes variables changes].freeze + attributes :refs, :kubernetes, :variables, :changes validations do validates :config, presence: true - validates :config, allowed_keys: %i[refs kubernetes variables] + validates :config, allowed_keys: ALLOWED_KEYS validate :variables_expressions_syntax with_options allow_nil: true do validates :refs, array_of_strings_or_regexps: true validates :kubernetes, allowed_values: %w[active] validates :variables, array_of_strings: true + validates :changes, array_of_strings: true end def variables_expressions_syntax diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb index b3c889ee92f..f6b4ba7843e 100644 --- a/lib/gitlab/ci/config/entry/validators.rb +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -49,6 +49,12 @@ module Gitlab unless validate_duration(value) record.errors.add(attribute, 'should be a duration') end + + if options[:limit] + unless validate_duration_limit(value, options[:limit]) + record.errors.add(attribute, 'should not exceed the limit') + end + end end end diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index a96595b33a5..72547c1b407 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -25,8 +25,9 @@ # level, or manually added below. # # Continuous deployment to production is enabled by default. -# If you want to deploy to staging first, or enable incremental rollouts, -# set STAGING_ENABLED or INCREMENTAL_ROLLOUT_ENABLED environment variables. +# If you want to deploy to staging first, set STAGING_ENABLED environment variable. +# If you want to enable incremental rollout, either manual or time based, +# set INCREMENTAL_ROLLOUT_TYPE environment variable to "manual" or "timed". # If you want to use canary deployments, set CANARY_ENABLED environment variable. # # If Auto DevOps fails to detect the proper buildpack, or if you want to @@ -61,6 +62,10 @@ stages: - staging - canary - production + - incremental rollout 10% + - incremental rollout 25% + - incremental rollout 50% + - incremental rollout 100% - performance - cleanup @@ -282,11 +287,6 @@ stop_review: variables: - $REVIEW_DISABLED -# Keys that start with a dot (.) will not be processed by GitLab CI. -# Staging and canary jobs are disabled by default, to enable them -# remove the dot (.) before the job name. -# https://docs.gitlab.com/ee/ci/yaml/README.html#hidden-keys - # Staging deploys are disabled by default since # continuous deployment to production is enabled by default # If you prefer to automatically deploy to staging and @@ -368,6 +368,7 @@ production: - $STAGING_ENABLED - $CANARY_ENABLED - $INCREMENTAL_ROLLOUT_ENABLED + - $INCREMENTAL_ROLLOUT_MODE production_manual: <<: *production_template @@ -383,11 +384,11 @@ production_manual: except: variables: - $INCREMENTAL_ROLLOUT_ENABLED + - $INCREMENTAL_ROLLOUT_MODE # This job implements incremental rollout on for every push to `master`. .rollout: &rollout_template - stage: production script: - check_kube_domain - install_dependencies @@ -405,52 +406,77 @@ production_manual: artifacts: paths: [environment_url.txt] -rollout 10%: +.manual_rollout_template: &manual_rollout_template <<: *rollout_template - variables: - ROLLOUT_PERCENTAGE: 10 + stage: production when: manual + # This selectors are backward compatible mode with $INCREMENTAL_ROLLOUT_ENABLED (before 11.4) only: refs: - master kubernetes: active variables: + - $INCREMENTAL_ROLLOUT_MODE == "manual" - $INCREMENTAL_ROLLOUT_ENABLED + except: + variables: + - $INCREMENTAL_ROLLOUT_MODE == "timed" -rollout 25%: +.timed_rollout_template: &timed_rollout_template <<: *rollout_template - variables: - ROLLOUT_PERCENTAGE: 25 - when: manual + when: delayed + start_in: 5 minutes only: refs: - master kubernetes: active variables: - - $INCREMENTAL_ROLLOUT_ENABLED + - $INCREMENTAL_ROLLOUT_MODE == "timed" + +timed rollout 10%: + <<: *timed_rollout_template + stage: incremental rollout 10% + variables: + ROLLOUT_PERCENTAGE: 10 + +timed rollout 25%: + <<: *timed_rollout_template + stage: incremental rollout 25% + variables: + ROLLOUT_PERCENTAGE: 25 + +timed rollout 50%: + <<: *timed_rollout_template + stage: incremental rollout 50% + variables: + ROLLOUT_PERCENTAGE: 50 + +timed rollout 100%: + <<: *timed_rollout_template + <<: *production_template + stage: incremental rollout 100% + variables: + ROLLOUT_PERCENTAGE: 100 + +rollout 10%: + <<: *manual_rollout_template + variables: + ROLLOUT_PERCENTAGE: 10 + +rollout 25%: + <<: *manual_rollout_template + variables: + ROLLOUT_PERCENTAGE: 25 rollout 50%: - <<: *rollout_template + <<: *manual_rollout_template variables: ROLLOUT_PERCENTAGE: 50 - when: manual - only: - refs: - - master - kubernetes: active - variables: - - $INCREMENTAL_ROLLOUT_ENABLED rollout 100%: + <<: *manual_rollout_template <<: *production_template - when: manual allow_failure: false - only: - refs: - - master - kubernetes: active - variables: - - $INCREMENTAL_ROLLOUT_ENABLED # --------------------------------------------------------------------------- @@ -689,9 +715,6 @@ rollout 100%: helm version --client tiller -version - helm init --client-only - helm plugin install https://github.com/adamreese/helm-local - curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl" chmod +x /usr/bin/kubectl kubectl version --client @@ -800,9 +823,9 @@ rollout 100%: function initialize_tiller() { echo "Checking Tiller..." - helm local start - helm local status export HELM_HOST=":44134" + tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 & + echo "Tiller is listening on ${HELM_HOST}" if ! helm version --debug; then echo "Failed to init Tiller." diff --git a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml index 0688f77a1d2..d0cad285572 100644 --- a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml @@ -25,9 +25,12 @@ before_script: # Update packages - apt-get update -yqq - # Upgrade to Node 7 - - curl -sL https://deb.nodesource.com/setup_7.x | bash - - + # Prep for Node + - apt-get install gnupg -yqq + + # Upgrade to Node 8 + - curl -sL https://deb.nodesource.com/setup_8.x | bash - + # Install dependencies - apt-get install git nodejs libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml index 2e0589de652..098abe4daf5 100644 --- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml @@ -5,7 +5,7 @@ image: python:latest # Change pip's cache directory to be inside the project directory since we can # only cache local items. variables: - PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache" + PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip" # Pip's cache doesn't store the python packages # https://pip.pypa.io/en/stable/reference/pip_install/#caching diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index fc280f96ec1..f967494199e 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -69,6 +69,10 @@ module Gitlab JSON.generate(formatter.to_h, opts) end + def as_json(opts = nil) + to_h.as_json(opts) + end + def type formatter.line_age end diff --git a/lib/gitlab/git/diff_stats_collection.rb b/lib/gitlab/git/diff_stats_collection.rb index d4033f56387..998c41497a2 100644 --- a/lib/gitlab/git/diff_stats_collection.rb +++ b/lib/gitlab/git/diff_stats_collection.rb @@ -18,6 +18,10 @@ module Gitlab indexed_by_path[path] end + def paths + @collection.map(&:path) + end + private def indexed_by_path diff --git a/lib/gitlab/git/push.rb b/lib/gitlab/git/push.rb new file mode 100644 index 00000000000..b6577ba17f1 --- /dev/null +++ b/lib/gitlab/git/push.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module Gitlab + module Git + class Push + include Gitlab::Utils::StrongMemoize + + attr_reader :ref, :oldrev, :newrev + + def initialize(project, oldrev, newrev, ref) + @project = project + @oldrev = oldrev.presence || Gitlab::Git::BLANK_SHA + @newrev = newrev.presence || Gitlab::Git::BLANK_SHA + @ref = ref + end + + def branch_name + strong_memoize(:branch_name) do + Gitlab::Git.branch_name(@ref) + end + end + + def branch_added? + Gitlab::Git.blank_ref?(@oldrev) + end + + def branch_removed? + Gitlab::Git.blank_ref?(@newrev) + end + + def branch_updated? + branch_push? && !branch_added? && !branch_removed? + end + + def force_push? + Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev) + end + + def branch_push? + strong_memoize(:branch_push) do + Gitlab::Git.branch_ref?(@ref) + end + end + + def modified_paths + unless branch_updated? + raise ArgumentError, 'Unable to calculate modified paths!' + end + + strong_memoize(:modified_paths) do + @project.repository.diff_stats(@oldrev, @newrev).paths + end + end + end + end +end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 30cd09a0ca7..240a0d7d1b8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -24,8 +24,8 @@ module Gitlab cannot_push_to_read_only: "You can't push code to a read-only GitLab instance." }.freeze - DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze - PUSH_COMMANDS = %w{ git-receive-pack }.freeze + DOWNLOAD_COMMANDS = %w{git-upload-pack git-upload-archive}.freeze + PUSH_COMMANDS = %w{git-receive-pack}.freeze ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path, :auth_result_type, :changes diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 500aabcbbb8..4ec87f6a3e7 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -220,7 +220,7 @@ module Gitlab result end - SERVER_FEATURE_FLAGS = %w[gogit_findcommit git_v2].freeze + SERVER_FEATURE_FLAGS = %w[gogit_findcommit].freeze def self.server_feature_flags SERVER_FEATURE_FLAGS.map do |f| diff --git a/lib/gitlab/template/base_template.rb b/lib/gitlab/template/base_template.rb index 3770f3f250b..4456217017f 100644 --- a/lib/gitlab/template/base_template.rb +++ b/lib/gitlab/template/base_template.rb @@ -12,14 +12,21 @@ module Gitlab def name File.basename(@path, self.class.extension) end - alias_method :id, :name + alias_method :key, :name def content @finder.read(@path) end + # Present for compatibility with license templates, which can replace text + # like `[fullname]` with a user-specified string. This is a no-op for + # other templates + def resolve!(_placeholders = {}) + self + end + def to_json - { name: name, content: content } + { key: key, name: name, content: content } end def <=>(other) diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 308a95d2f09..29672d68cad 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -3,7 +3,7 @@ module Gitlab ALLOWED_SCHEMES = %w[http https ssh git].freeze def self.sanitize(content) - regexp = URI::Parser.new.make_regexp(ALLOWED_SCHEMES) + regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES) content.gsub(regexp) { |url| new(url).masked_url } rescue Addressable::URI::InvalidURIError diff --git a/lib/gt_one_coercion.rb b/lib/gt_one_coercion.rb index ef2dc09767c..99be51bc8c6 100644 --- a/lib/gt_one_coercion.rb +++ b/lib/gt_one_coercion.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class GtOneCoercion < Virtus::Attribute def coerce(value) [1, value.to_i].max diff --git a/lib/milestone_array.rb b/lib/milestone_array.rb index 4ed8485b36a..461e73e9670 100644 --- a/lib/milestone_array.rb +++ b/lib/milestone_array.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module MilestoneArray class << self def sort(array, sort_method) diff --git a/lib/mysql_zero_date.rb b/lib/mysql_zero_date.rb index 64634f789da..216560148fa 100644 --- a/lib/mysql_zero_date.rb +++ b/lib/mysql_zero_date.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Disable NO_ZERO_DATE mode for mysql in rails 5. # We use zero date as a default value # (config/initializers/active_record_mysql_timestamp.rb), in diff --git a/lib/static_model.rb b/lib/static_model.rb index 44673c2b5f6..86bf8d62f9a 100644 --- a/lib/static_model.rb +++ b/lib/static_model.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Provides an ActiveRecord-like interface to a model whose data is not persisted to a database. module StaticModel extend ActiveSupport::Concern diff --git a/lib/system_check.rb b/lib/system_check.rb index 466c39904fa..7ffd7c03c5b 100644 --- a/lib/system_check.rb +++ b/lib/system_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Library to perform System Checks # # Every Check is implemented as its own class inherited from SystemCheck::BaseCheck diff --git a/lib/unfold_form.rb b/lib/unfold_form.rb index fcd01503d1b..05bb3ed7f1c 100644 --- a/lib/unfold_form.rb +++ b/lib/unfold_form.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require_relative 'gt_one_coercion' class UnfoldForm diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb index 53e5ac02e42..aae542f02ac 100644 --- a/lib/uploaded_file.rb +++ b/lib/uploaded_file.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "tempfile" require "tmpdir" require "fileutils" diff --git a/lib/version_check.rb b/lib/version_check.rb index 91ad07feee5..ccf7bb493db 100644 --- a/lib/version_check.rb +++ b/lib/version_check.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require "base64" # This class is used to build image URL to diff --git a/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po index 1b03fe9ce28..d196fac6c60 100644 --- a/locale/ar_SA/gitlab.po +++ b/locale/ar_SA/gitlab.po @@ -480,7 +480,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 3a925c27e9b..0d5026c0f4a 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po index 007b2a4d393..1a052348522 100644 --- a/locale/ca_ES/gitlab.po +++ b/locale/ca_ES/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po index 013152917e6..3a2267c4bf7 100644 --- a/locale/cs_CZ/gitlab.po +++ b/locale/cs_CZ/gitlab.po @@ -420,7 +420,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po index ed25f935b9a..1a6e564ed36 100644 --- a/locale/da_DK/gitlab.po +++ b/locale/da_DK/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po index 41848faeb30..c27a0dea04d 100644 --- a/locale/de/gitlab.po +++ b/locale/de/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "Zugriff auf fehlerhafte Speicher wurde vorübergehend deaktiviert, um die Wiederherstellung zu ermöglichen. Für den zukünftigen Zugriff, behebe bitte das Problem und setze danach die Speicherinformationen zurück." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 8bd42855b44..d0a67a1d089 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index 2a8fb756192..6ce5b6a3aff 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po index a9637d4098e..8e4edc84c83 100644 --- a/locale/et_EE/gitlab.po +++ b/locale/et_EE/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po index 0929e3dd7cb..73eeb56bea2 100644 --- a/locale/fil_PH/gitlab.po +++ b/locale/fil_PH/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po index cf00055272c..93b30d0ef31 100644 --- a/locale/fr/gitlab.po +++ b/locale/fr/gitlab.po @@ -360,7 +360,7 @@ msgstr "Accès à « %{classification_label} » non autorisé" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "L’accès aux stockages défaillants a été temporairement désactivé pour permettre la récupération du montage. Réinitialisez les informations de stockage quand le problème sera résolu pour permettre à nouveau l’accès." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "Accédez à votre jeton d’exécuteur, personnalisez la configuration de votre pipeline et affichez l’état de votre pipeline et le rapport de couverture." msgid "Account" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0d36b9b1170..8b7d4b0f17e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -317,9 +317,6 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." -msgstr "" - msgid "Account" msgstr "" @@ -347,6 +344,9 @@ msgstr "" msgid "Add Readme" msgstr "" +msgid "Add a table" +msgstr "" + msgid "Add license" msgstr "" @@ -1084,6 +1084,9 @@ msgstr "" msgid "CICD|Continuous deployment to production" msgstr "" +msgid "CICD|Continuous deployment to production using timed incremental rollout" +msgstr "" + msgid "CICD|Default to Auto DevOps pipeline" msgstr "" @@ -2099,6 +2102,9 @@ msgstr "" msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import." msgstr "" +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." +msgstr "" + msgid "Cycle Analytics" msgstr "" @@ -2746,6 +2752,9 @@ msgstr "" msgid "Failed to check related branches." msgstr "" +msgid "Failed to load emoji list." +msgstr "" + msgid "Failed to remove issue from board, please try again." msgstr "" @@ -3369,6 +3378,9 @@ msgstr "" msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." msgstr "" +msgid "Issues, merge requests, pushes and comments." +msgstr "" + msgid "Jan" msgstr "" @@ -3700,6 +3712,9 @@ msgstr "" msgid "MarkdownToolbar|Add a numbered list" msgstr "" +msgid "MarkdownToolbar|Add a table" +msgstr "" + msgid "MarkdownToolbar|Add a task list" msgstr "" @@ -3733,6 +3748,9 @@ msgstr "" msgid "Median" msgstr "" +msgid "Member since %{date}" +msgstr "" + msgid "Members" msgstr "" @@ -5463,6 +5481,30 @@ msgstr "" msgid "SetPasswordToCloneLink|set a password" msgstr "" +msgid "SetStatusModal|Add status emoji" +msgstr "" + +msgid "SetStatusModal|Clear status" +msgstr "" + +msgid "SetStatusModal|Edit status" +msgstr "" + +msgid "SetStatusModal|Remove status" +msgstr "" + +msgid "SetStatusModal|Set a status" +msgstr "" + +msgid "SetStatusModal|Set status" +msgstr "" + +msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later." +msgstr "" + +msgid "SetStatusModal|What's your status?" +msgstr "" + msgid "Settings" msgstr "" @@ -5777,6 +5819,12 @@ msgstr "" msgid "Subscribe at project level" msgstr "" +msgid "Subscribed" +msgstr "" + +msgid "Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})" +msgstr "" + msgid "Switch branch/tag" msgstr "" @@ -5974,9 +6022,6 @@ msgstr "" msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "" -msgid "The secure token used by the Runner to checkout the project" -msgstr "" - msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "" @@ -6614,6 +6659,51 @@ msgstr "" msgid "User map" msgstr "" +msgid "UserProfile|Activity" +msgstr "" + +msgid "UserProfile|Already reported for abuse" +msgstr "" + +msgid "UserProfile|Contributed projects" +msgstr "" + +msgid "UserProfile|Edit profile" +msgstr "" + +msgid "UserProfile|Groups" +msgstr "" + +msgid "UserProfile|Most Recent Activity" +msgstr "" + +msgid "UserProfile|Overview" +msgstr "" + +msgid "UserProfile|Personal projects" +msgstr "" + +msgid "UserProfile|Recent contributions" +msgstr "" + +msgid "UserProfile|Report abuse" +msgstr "" + +msgid "UserProfile|Snippets" +msgstr "" + +msgid "UserProfile|Subscribe" +msgstr "" + +msgid "UserProfile|This user has a private profile" +msgstr "" + +msgid "UserProfile|View all" +msgstr "" + +msgid "UserProfile|View user in admin area" +msgstr "" + msgid "Users" msgstr "" @@ -6902,9 +6992,6 @@ msgstr "" msgid "You can only edit files when you are on a branch" msgstr "" -msgid "You can reset runners registration token by pressing a button below." -msgstr "" - msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}" msgstr "" @@ -6914,6 +7001,9 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have any subscriptions yet" +msgstr "" + msgid "You don't have any applications" msgstr "" diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po index 2b6dcc6595e..c77dc236458 100644 --- a/locale/gl_ES/gitlab.po +++ b/locale/gl_ES/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po index f34f862b9b1..ab014982a72 100644 --- a/locale/he_IL/gitlab.po +++ b/locale/he_IL/gitlab.po @@ -420,7 +420,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po index ba43d50b726..d5c48520155 100644 --- a/locale/id_ID/gitlab.po +++ b/locale/id_ID/gitlab.po @@ -330,7 +330,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index 375311ccf72..87bcd939fb1 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "L'accesso agli storages è stato temporaneamente disabilitato per consentire il mount di ripristino. Resetta le info d'archiviazione dopo che l'issue è stato risolto per consentire nuovamente l'accesso." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po index a618fa97381..ee5ea023fb5 100644 --- a/locale/ja/gitlab.po +++ b/locale/ja/gitlab.po @@ -330,7 +330,7 @@ msgstr "'%{classification_label}'ã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã¯è¨±å¯ã•ã‚Œã¦ã„ã¾ã›ã‚ msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "mount ã«ã‚ˆã£ã¦å¾©æ—§ã§ãるよã†ã«ã€å¤±æ•—ãŒç™ºç”Ÿã—ã¦ã„るストレージã¸ã®ã‚¢ã‚¯ã‚»ã‚¹ã‚’一時的ã«æŠ‘æ¢ã—ã¾ã—ãŸã€‚å†åº¦ã‚¢ã‚¯ã‚»ã‚¹ã™ã‚‹ãŸã‚ã«ã¯ã€å•é¡Œã‚’解決ã—ã¦ã‹ã‚‰ã‚¹ãƒˆãƒ¬ãƒ¼ã‚¸æƒ…å ±ã‚’ãƒªã‚»ãƒƒãƒˆã—ã¦ãã ã•ã„。" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "Runner トークンã«ã‚¢ã‚¯ã‚»ã‚¹ã—ã€ãƒ‘イプラインã®è¨å®šã‚’カスタマイズã€ãã—ã¦ãƒ‘イプラインã®çŠ¶æ…‹ã¨ã‚«ãƒãƒ¬ãƒƒã‚¸ãƒ¬ãƒãƒ¼ãƒˆã‚’閲覧ã—ã¾ã™ã€‚" msgid "Account" diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po index ce0c069712d..3c3bcf9688a 100644 --- a/locale/ko/gitlab.po +++ b/locale/ko/gitlab.po @@ -330,7 +330,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "오ë™ìž‘ì¤‘ì¸ ì €ìž¥ê³µê°„ì— ëŒ€í•œ ì ‘ê·¼ì´ ë³µêµ¬ ìž‘ì—…ì„ ìœ„í•´ ë§ˆìš´íŠ¸í• ìˆ˜ 있ë„ë¡ ìž„ì‹œë¡œ 허용ë˜ì—ˆìŠµë‹ˆë‹¤. ë¬¸ì œê°€ í•´ê²°ëœ í›„ 다시 ì ‘ê·¼ì„ í—ˆìš©í• ìˆ˜ 있게 ì €ìž¥ê³µê°„ ì •ë³´ë¥¼ 리셋 해주세요." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po index d039b51ce40..f354ca50f32 100644 --- a/locale/nl_NL/gitlab.po +++ b/locale/nl_NL/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po index 3f30108892f..1d6dc4c4399 100644 --- a/locale/pl_PL/gitlab.po +++ b/locale/pl_PL/gitlab.po @@ -420,7 +420,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index bcc7659e5a2..c76c639e8db 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -360,7 +360,7 @@ msgstr "Acesso a '%{classification_label}' não permitido" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "Os acessos à storages com defeito foram temporariamente desabilitados para permitir a sua remontagem. Redefina as informações de armazenamento depois que o problema foi resolvido para permitir o acesso de novo." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "Acesse seu runner token, personalize sua configuração de pipeline e visualize o status do seu pipeline e o relatório de coverage." msgid "Account" diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po index 3fb198ae037..bae64f360fc 100644 --- a/locale/ro_RO/gitlab.po +++ b/locale/ro_RO/gitlab.po @@ -390,7 +390,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po index dc7a0fc9f51..bc2c26da457 100644 --- a/locale/ru/gitlab.po +++ b/locale/ru/gitlab.po @@ -420,7 +420,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "ДоÑтуп к вышедшим из ÑÑ‚Ñ€Ð¾Ñ Ñ…Ñ€Ð°Ð½Ð¸Ð»Ð¸Ñ‰Ð°Ð¼ временно отключен Ð´Ð»Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñти Ð¼Ð¾Ð½Ñ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² целÑÑ… воÑÑтановлениÑ. СброÑьте информацию о хранилищах поÑле уÑÑ‚Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ñ‹, чтобы разрешить доÑтуп." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po index 681827065da..42eeed11534 100644 --- a/locale/sq_AL/gitlab.po +++ b/locale/sq_AL/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po index cb77032cc50..d1087ffd29e 100644 --- a/locale/tr_TR/gitlab.po +++ b/locale/tr_TR/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index 57a131c4ee6..33019a3e5a8 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -420,7 +420,7 @@ msgstr "ДоÑтуп до \"%{classification_label}\" заборонено" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "ДоÑтуп до Ñховищ, що вийшли з ладу, тимчаÑово прибраний Ð·Ð°Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¼Ð¾Ð½Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ. ПіÑÐ»Ñ Ð²Ð¸Ñ€Ñ–ÑˆÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¸ обнуліть інформацію Ñховища Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð´Ð¾Ñтупу." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "Отримайте доÑтуп до Gitlab Runner токену, налаштуйте конфігурацію конвеєра та переглÑньте його ÑтатуÑ, а також звіт про покриттÑ." msgid "Account" diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 9629a63e976..861e459bcac 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -330,7 +330,7 @@ msgstr "ä¸å…许访问%{classification_label}" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "为方便修å¤æŒ‚载问题,访问故障å˜å‚¨å·²è¢«æš‚æ—¶ç¦ç”¨ã€‚在问题解决åŽè¯·é‡ç½®å˜å‚¨è¿è¡ŒçŠ¶å†µä¿¡æ¯ï¼Œä»¥å…许å†æ¬¡è®¿é—®ã€‚" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "访问您的 runner 令牌,自定义æµæ°´çº¿é…置,以åŠæŸ¥çœ‹æµæ°´çº¿çŠ¶æ€å’Œè¦†ç›–率报告。" msgid "Account" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 632da40cd54..3ecd9fc4cd2 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -330,7 +330,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "å› æ¢å¾©å®‰è£ï¼Œè¨ªå•æ•…éšœå˜å„²å·²è¢«æš«æ™‚ç¦ç”¨ã€‚在å•é¡Œè§£æ±ºå¾Œå°‡é‡ç½®å˜å„²ä¿¡æ¯ï¼Œä»¥ä¾¿å†æ¬¡è¨ªå•ã€‚" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 0d6fe4395ef..bb907d9a583 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -330,7 +330,7 @@ msgstr "ä¸å…許å˜å–「%{classification_label}ã€" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "已暫時åœç”¨å¤±æ•—çš„ Git 儲å˜ç©ºé–“。當儲å˜ç©ºé–“æ¢å¾©æ£å¸¸å¾Œï¼Œè«‹é‡ç½®å„²å˜ç©ºé–“å¥åº·æŒ‡æ•¸ã€‚" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "å˜å–您執行器憑è‰ï¼Œè‡ªå®šç¾©æ‚¨çš„æµæ°´ç·šé…ç½®ï¼Œä¸¦æŸ¥çœ‹ä½ çš„æµæ°´ç¾ç‹€æ…‹åŠæ¸¬è©¦æ¶µè“‹çŽ‡å ±å‘Šã€‚" msgid "Account" diff --git a/package.json b/package.json index 35984e6d81f..ac9a73cd2c9 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "commander": "^2.18.0", "eslint": "~5.6.0", "eslint-config-airbnb-base": "^13.1.0", + "eslint-config-prettier": "^3.1.0", "eslint-import-resolver-webpack": "^0.10.1", "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-html": "4.0.5", @@ -144,7 +145,7 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^4.0.0-beta.0", "nodemon": "^1.18.4", - "prettier": "1.12.1", + "prettier": "1.14.3", "webpack-dev-server": "^3.1.8" } } @@ -120,6 +120,7 @@ module QA module Main autoload :Login, 'qa/page/main/login' + autoload :Menu, 'qa/page/main/menu' autoload :OAuth, 'qa/page/main/oauth' autoload :SignUp, 'qa/page/main/sign_up' end @@ -128,13 +129,6 @@ module QA autoload :Common, 'qa/page/settings/common' end - module Menu - autoload :Main, 'qa/page/menu/main' - autoload :Side, 'qa/page/menu/side' - autoload :Admin, 'qa/page/menu/admin' - autoload :Profile, 'qa/page/menu/profile' - end - module Dashboard autoload :Projects, 'qa/page/dashboard/projects' autoload :Groups, 'qa/page/dashboard/groups' @@ -158,6 +152,7 @@ module QA autoload :New, 'qa/page/project/new' autoload :Show, 'qa/page/project/show' autoload :Activity, 'qa/page/project/activity' + autoload :Menu, 'qa/page/project/menu' module Import autoload :Github, 'qa/page/project/import/github' @@ -201,6 +196,11 @@ module QA end module Operations + module Environments + autoload :Index, 'qa/page/project/operations/environments/index' + autoload :Show, 'qa/page/project/operations/environments/show' + end + module Kubernetes autoload :Index, 'qa/page/project/operations/kubernetes/index' autoload :Add, 'qa/page/project/operations/kubernetes/add' @@ -217,6 +217,7 @@ module QA end module Profile + autoload :Menu, 'qa/page/profile/menu' autoload :PersonalAccessTokens, 'qa/page/profile/personal_access_tokens' autoload :SSHKeys, 'qa/page/profile/ssh_keys' end @@ -235,6 +236,8 @@ module QA end module Admin + autoload :Menu, 'qa/page/admin/menu' + module Settings autoload :Repository, 'qa/page/admin/settings/repository' @@ -257,6 +260,9 @@ module QA autoload :Dropzone, 'qa/page/component/dropzone' autoload :GroupsFilter, 'qa/page/component/groups_filter' autoload :Select2, 'qa/page/component/select2' + module Issuable + autoload :Common, 'qa/page/component/issuable/common' + end end end diff --git a/qa/qa/factory/resource/branch.rb b/qa/qa/factory/resource/branch.rb index 60539992073..f3b52565d17 100644 --- a/qa/qa/factory/resource/branch.rb +++ b/qa/qa/factory/resource/branch.rb @@ -43,7 +43,7 @@ module QA # to `allow_to_push` variable. return branch unless @protected - Page::Menu::Side.act do + Page::Project::Menu.act do click_repository_settings end diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb index ea8a3ad687d..4c53c500c27 100644 --- a/qa/qa/factory/resource/deploy_key.rb +++ b/qa/qa/factory/resource/deploy_key.rb @@ -24,7 +24,7 @@ module QA def fabricate! project.visit! - Page::Menu::Side.act do + Page::Project::Menu.act do click_repository_settings end diff --git a/qa/qa/factory/resource/fork.rb b/qa/qa/factory/resource/fork.rb index 1fa47e92983..83dd4000f0a 100644 --- a/qa/qa/factory/resource/fork.rb +++ b/qa/qa/factory/resource/fork.rb @@ -32,7 +32,7 @@ module QA puts "Visited project page" Capybara::Screenshot.screenshot_and_save_page - return if Page::Menu::Main.act { has_personal_area?(wait: 0) } + return if Page::Main::Menu.act { has_personal_area?(wait: 0) } puts "Not signed in. Attempting to sign in again." Capybara::Screenshot.screenshot_and_save_page diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb index 94d7df7128b..cdee35c54e3 100644 --- a/qa/qa/factory/resource/kubernetes_cluster.rb +++ b/qa/qa/factory/resource/kubernetes_cluster.rb @@ -16,7 +16,7 @@ module QA def fabricate! @project.visit! - Page::Menu::Side.act { click_operations_kubernetes } + Page::Project::Menu.act { click_operations_kubernetes } Page::Project::Operations::Kubernetes::Index.perform do |page| page.add_kubernetes_cluster @@ -31,6 +31,7 @@ module QA page.set_api_url(@cluster.api_url) page.set_ca_certificate(@cluster.ca_certificate) page.set_token(@cluster.token) + page.check_rbac! if @cluster.rbac page.add_cluster! end diff --git a/qa/qa/factory/resource/personal_access_token.rb b/qa/qa/factory/resource/personal_access_token.rb index 514e3615d18..166054cfcdc 100644 --- a/qa/qa/factory/resource/personal_access_token.rb +++ b/qa/qa/factory/resource/personal_access_token.rb @@ -12,8 +12,8 @@ module QA end def fabricate! - Page::Menu::Main.act { go_to_profile_settings } - Page::Menu::Profile.act { click_access_tokens } + Page::Main::Menu.act { go_to_profile_settings } + Page::Profile::Menu.act { click_access_tokens } Page::Profile::PersonalAccessTokens.perform do |page| page.fill_token_name(name || 'api-test-token') diff --git a/qa/qa/factory/resource/project_milestone.rb b/qa/qa/factory/resource/project_milestone.rb index 47a5e74204f..1251ae03135 100644 --- a/qa/qa/factory/resource/project_milestone.rb +++ b/qa/qa/factory/resource/project_milestone.rb @@ -17,7 +17,7 @@ module QA def fabricate! project.visit! - Page::Menu::Side.act do + Page::Project::Menu.act do click_issues click_milestones end diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb index 03b69eb1bdf..7ac65fe6913 100644 --- a/qa/qa/factory/resource/runner.rb +++ b/qa/qa/factory/resource/runner.rb @@ -26,7 +26,7 @@ module QA def fabricate! project.visit! - Page::Menu::Side.act { click_ci_cd_settings } + Page::Project::Menu.act { click_ci_cd_settings } Service::Runner.new(name).tap do |runner| Page::Project::Settings::CICD.perform do |settings| diff --git a/qa/qa/factory/resource/sandbox.rb b/qa/qa/factory/resource/sandbox.rb index 4f6039f300f..5249e1755a6 100644 --- a/qa/qa/factory/resource/sandbox.rb +++ b/qa/qa/factory/resource/sandbox.rb @@ -11,7 +11,7 @@ module QA end def fabricate! - Page::Menu::Main.act { go_to_groups } + Page::Main::Menu.act { go_to_groups } Page::Dashboard::Groups.perform do |page| if page.has_group?(@name) diff --git a/qa/qa/factory/resource/secret_variable.rb b/qa/qa/factory/resource/secret_variable.rb index 12a830da116..4084a7fc2cd 100644 --- a/qa/qa/factory/resource/secret_variable.rb +++ b/qa/qa/factory/resource/secret_variable.rb @@ -12,7 +12,7 @@ module QA def fabricate! project.visit! - Page::Menu::Side.act { click_ci_cd_settings } + Page::Project::Menu.act { click_ci_cd_settings } Page::Project::Settings::CICD.perform do |setting| setting.expand_secret_variables do |page| diff --git a/qa/qa/factory/resource/ssh_key.rb b/qa/qa/factory/resource/ssh_key.rb index 6c872f32d16..45236f69de9 100644 --- a/qa/qa/factory/resource/ssh_key.rb +++ b/qa/qa/factory/resource/ssh_key.rb @@ -27,8 +27,8 @@ module QA end def fabricate! - Page::Menu::Main.act { go_to_profile_settings } - Page::Menu::Profile.act { click_ssh_keys } + Page::Main::Menu.act { go_to_profile_settings } + Page::Profile::Menu.act { click_ssh_keys } Page::Profile::SSHKeys.perform do |page| page.add_key(public_key, title) diff --git a/qa/qa/factory/resource/user.rb b/qa/qa/factory/resource/user.rb index 34b52223b2d..e8b9ea2e6b4 100644 --- a/qa/qa/factory/resource/user.rb +++ b/qa/qa/factory/resource/user.rb @@ -38,8 +38,8 @@ module QA def fabricate! # Don't try to log-out if we're not logged-in - if Page::Menu::Main.act { has_personal_area?(wait: 0) } - Page::Menu::Main.perform { |main| main.sign_out } + if Page::Main::Menu.act { has_personal_area?(wait: 0) } + Page::Main::Menu.perform { |main| main.sign_out } end if credentials_given? diff --git a/qa/qa/factory/resource/wiki.rb b/qa/qa/factory/resource/wiki.rb index cc200a512d5..acfe143fa61 100644 --- a/qa/qa/factory/resource/wiki.rb +++ b/qa/qa/factory/resource/wiki.rb @@ -10,7 +10,7 @@ module QA end def fabricate! - Page::Menu::Side.act { click_wiki } + Page::Project::Menu.act { click_wiki } Page::Project::Wiki::New.perform do |page| page.go_to_create_first_page page.set_title(@title) diff --git a/qa/qa/factory/settings/hashed_storage.rb b/qa/qa/factory/settings/hashed_storage.rb index c69ebed3c6b..f2e58a3ea38 100644 --- a/qa/qa/factory/settings/hashed_storage.rb +++ b/qa/qa/factory/settings/hashed_storage.rb @@ -6,8 +6,8 @@ module QA raise ArgumentError unless traits.include?(:enabled) Page::Main::Login.act { sign_in_using_credentials } - Page::Menu::Main.act { go_to_admin_area } - Page::Menu::Admin.act { go_to_settings } + Page::Main::Menu.act { go_to_admin_area } + Page::Admin::Menu.act { go_to_repository_settings } Page::Admin::Settings::Main.perform do |setting| setting.expand_repository_storage do |page| @@ -16,7 +16,7 @@ module QA end end - QA::Page::Menu::Main.act { sign_out } + QA::Page::Main::Menu.act { sign_out } end end end diff --git a/qa/qa/page/README.md b/qa/qa/page/README.md index 2dbc59846e7..dfad460a9a5 100644 --- a/qa/qa/page/README.md +++ b/qa/qa/page/README.md @@ -86,8 +86,12 @@ module Page end ``` -It is possible to use `element` DSL method without value, with a String value -or with a Regexp. +The `view` DSL method declares the filename of the view where an +`element` is implmented. + +The `element` DSL method in turn declares an element and defines a value +to match to the actual view code. It is possible to use `element` with value, +with a String value or with a Regexp. ```ruby view 'app/views/my/view.html.haml' do diff --git a/qa/qa/page/menu/admin.rb b/qa/qa/page/admin/menu.rb index bf05a912bc6..e8c7d274966 100644 --- a/qa/qa/page/menu/admin.rb +++ b/qa/qa/page/admin/menu.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module QA module Page - module Menu - class Admin < Page::Base + module Admin + class Menu < Page::Base view 'app/views/layouts/nav/sidebar/_admin.html.haml' do element :admin_sidebar element :admin_sidebar_submenu diff --git a/qa/qa/page/component/issuable/common.rb b/qa/qa/page/component/issuable/common.rb new file mode 100644 index 00000000000..cfd8ac1e7c8 --- /dev/null +++ b/qa/qa/page/component/issuable/common.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module QA + module Page + module Component + module Issuable + module Common + def self.included(base) + base.view 'app/assets/javascripts/issue_show/components/title.vue' do + element :edit_button + end + + base.view 'app/assets/javascripts/issue_show/components/fields/title.vue' do + element :title_input + end + + base.view 'app/assets/javascripts/issue_show/components/fields/description.vue' do + element :description_textarea + end + + base.view 'app/assets/javascripts/issue_show/components/edit_actions.vue' do + element :save_button + element :delete_button + end + + base.view 'app/assets/javascripts/issue_show/components/edit_actions.vue' do + element :save_button + element :delete_button + end + end + end + end + end + end +end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 9b3183ba328..eab7a85ff04 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -37,13 +37,13 @@ module QA # we are already logged-in so we check both cases here. wait(max: 500) do has_css?('.login-page') || - Page::Menu::Main.act { has_personal_area?(wait: 0) } + Page::Main::Menu.act { has_personal_area?(wait: 0) } end end def sign_in_using_credentials(user = nil) # Don't try to log-in if we're already logged-in - return if Page::Menu::Main.act { has_personal_area?(wait: 0) } + return if Page::Main::Menu.act { has_personal_area?(wait: 0) } using_wait_time 0 do set_initial_password_if_present @@ -57,7 +57,7 @@ module QA end end - Page::Menu::Main.act { has_personal_area? } + Page::Main::Menu.act { has_personal_area? } end def sign_in_using_admin_credentials @@ -72,7 +72,7 @@ module QA sign_in_using_gitlab_credentials(admin) end - Page::Menu::Main.act { has_personal_area? } + Page::Main::Menu.act { has_personal_area? } end def self.path diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/main/menu.rb index 2ae86bbc7dc..e18b95bde9f 100644 --- a/qa/qa/page/menu/main.rb +++ b/qa/qa/page/main/menu.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module QA module Page - module Menu - class Main < Page::Base + module Main + class Menu < Page::Base view 'app/views/layouts/header/_current_user_dropdown.html.haml' do element :user_sign_out_link, 'link_to _("Sign out")' element :settings_link, 'link_to s_("CurrentUser|Settings")' diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb index 64cd395de78..dddda4f2bdf 100644 --- a/qa/qa/page/main/sign_up.rb +++ b/qa/qa/page/main/sign_up.rb @@ -19,7 +19,7 @@ module QA fill_in :new_user_password, with: user.password click_button 'Register' - Page::Menu::Main.act { assert_has_personal_area } + Page::Main::Menu.act { assert_has_personal_area } end end end diff --git a/qa/qa/page/menu/profile.rb b/qa/qa/page/profile/menu.rb index 7e24fa85c33..f8a7d64e016 100644 --- a/qa/qa/page/menu/profile.rb +++ b/qa/qa/page/profile/menu.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module QA module Page - module Menu - class Profile < Page::Base + module Profile + class Menu < Page::Base view 'app/views/layouts/nav/sidebar/_profile.html.haml' do element :access_token_link, 'link_to profile_personal_access_tokens_path' element :access_token_title, 'Access Tokens' diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 587a02163b9..9a738f56202 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -5,6 +5,8 @@ module QA module Project module Issue class Show < Page::Base + include Page::Component::Issuable::Common + view 'app/views/projects/issues/show.html.haml' do element :issue_details, '.issue-details' element :title, '.title' diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/project/menu.rb index a1eedfea42e..d9f01c50f19 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/project/menu.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + module QA module Page - module Menu - class Side < Page::Base + module Project + class Menu < Page::Base view 'app/views/layouts/nav/sidebar/_project.html.haml' do element :settings_item element :settings_link, 'link_to edit_project_path' @@ -9,6 +11,7 @@ module QA element :link_pipelines element :pipelines_settings_link, "title: _('CI / CD')" element :operations_kubernetes_link, "title: _('Kubernetes')" + element :operations_environments_link element :issues_link, /link_to.*shortcuts-issues/ element :issues_link_text, "Issues" element :merge_requests_link, /link_to.*shortcuts-merge_requests/ @@ -40,6 +43,14 @@ module QA end end + def click_operations_environments + hover_operations do + within_submenu do + click_element(:operations_environments_link) + end + end + end + def click_operations_kubernetes hover_operations do within_submenu do diff --git a/qa/qa/page/project/operations/environments/index.rb b/qa/qa/page/project/operations/environments/index.rb new file mode 100644 index 00000000000..63965a57edd --- /dev/null +++ b/qa/qa/page/project/operations/environments/index.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Operations + module Environments + class Index < Page::Base + view 'app/assets/javascripts/environments/components/environment_item.vue' do + element :environment_link + end + + def go_to_environment(environment_name) + wait(reload: false) do + find(element_selector_css(:environment_link), text: environment_name).click + end + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/operations/environments/show.rb b/qa/qa/page/project/operations/environments/show.rb new file mode 100644 index 00000000000..aa88c218c89 --- /dev/null +++ b/qa/qa/page/project/operations/environments/show.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module QA + module Page + module Project + module Operations + module Environments + class Show < Page::Base + view 'app/views/projects/environments/_external_url.html.haml' do + element :view_deployment + end + + def view_deployment(&block) + new_window = window_opened_by { click_element(:view_deployment) } + + within_window(new_window, &block) if block + end + end + end + end + end + end +end diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb index eef82b5f329..38f8527b9b4 100644 --- a/qa/qa/page/project/operations/kubernetes/add_existing.rb +++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb @@ -10,6 +10,7 @@ module QA element :ca_certificate, 'text_area :ca_cert' element :token, 'text_field :token' element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')" + element :rbac_checkbox end def set_cluster_name(name) @@ -31,6 +32,10 @@ module QA def add_cluster! click_on 'Add Kubernetes cluster' end + + def check_rbac! + check_element :rbac_checkbox + end end end end diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index 27ba915961d..5bebb5ccec0 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -5,13 +5,17 @@ module QA # set to 'false' to have Chrome run visibly instead of headless def chrome_headless? - (ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i) != 0 + enabled?(ENV['CHROME_HEADLESS']) end def running_in_ci? ENV['CI'] || ENV['CI_SERVER'] end + def signup_disabled? + enabled?(ENV['SIGNUP_DISABLED'], default: false) + end + # specifies token that can be used for the api def personal_access_token ENV['PERSONAL_ACCESS_TOKEN'] @@ -83,6 +87,14 @@ module QA raise ArgumentError, "Please provide GITHUB_ACCESS_TOKEN" end + + private + + def enabled?(value, default: true) + return default if value.nil? + + (value =~ /^(false|no|0)$/i) != 0 + end end end end diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb index abd9d53554f..c5f12255d72 100644 --- a/qa/qa/service/kubernetes_cluster.rb +++ b/qa/qa/service/kubernetes_cluster.rb @@ -1,12 +1,17 @@ require 'securerandom' require 'mkmf' +require 'pathname' module QA module Service class KubernetesCluster include Service::Shellout - attr_reader :api_url, :ca_certificate, :token + attr_reader :api_url, :ca_certificate, :token, :rbac + + def initialize(rbac: false) + @rbac = rbac + end def cluster_name @cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}" @@ -19,7 +24,8 @@ module QA shell <<~CMD.tr("\n", ' ') gcloud container clusters create #{cluster_name} - --enable-legacy-authorization + #{auth_options} + --enable-basic-auth --zone #{Runtime::Env.gcloud_zone} && gcloud container clusters get-credentials @@ -28,8 +34,30 @@ module QA CMD @api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'` - @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`) - @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`) + + @admin_user = "#{cluster_name}-admin" + master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --zone #{Runtime::Env.gcloud_zone} --format 'json(masterAuth.username, masterAuth.password)'`) + shell <<~CMD.tr("\n", ' ') + kubectl config set-credentials #{@admin_user} + --username #{master_auth['masterAuth']['username']} + --password #{master_auth['masterAuth']['password']} + CMD + + if rbac + create_service_account + + secrets = JSON.parse(`kubectl get secrets -o json`) + gitlab_account = secrets['items'].find do |item| + item['metadata']['annotations']['kubernetes.io/service-account.name'] == 'gitlab-account' + end + + @ca_certificate = Base64.decode64(gitlab_account['data']['ca.crt']) + @token = Base64.decode64(gitlab_account['data']['token']) + else + @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`) + @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`) + end + self end @@ -44,6 +72,42 @@ module QA private + def create_service_account + shell('kubectl create -f -', stdin_data: service_account) + shell("kubectl --user #{@admin_user} create -f -", stdin_data: service_account_role_binding) + end + + def service_account + <<~YAML + apiVersion: v1 + kind: ServiceAccount + metadata: + name: gitlab-account + namespace: default + YAML + end + + def service_account_role_binding + <<~YAML + kind: ClusterRoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: gitlab-account-binding + subjects: + - kind: ServiceAccount + name: gitlab-account + namespace: default + roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io + YAML + end + + def auth_options + "--enable-legacy-authorization" unless rbac + end + def validate_dependencies find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.") find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.") diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 1ca9504bb33..43dc0851571 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -11,10 +11,12 @@ module QA # TODO, make it possible to use generic QA framework classes # as a library - gitlab-org/gitlab-qa#94 # - def shell(command) + def shell(command, stdin_data: nil) puts "Executing `#{command}`" - Open3.popen2e(*command) do |_in, out, wait| + Open3.popen2e(*command) do |stdin, out, wait| + stdin.puts(stdin_data) if stdin_data + stdin.close if stdin_data out.each { |line| puts line } if wait.value.exited? && wait.value.exitstatus.nonzero? diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb index 1c7da930567..ae196349c6b 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb @@ -8,7 +8,7 @@ module QA # TODO, since `Signed in successfully` message was removed # this is the only way to tell if user is signed in correctly. # - Page::Menu::Main.perform do |menu| + Page::Main::Menu.perform do |menu| expect(menu).to have_personal_area end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb index c296296def6..217870531da 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb @@ -10,7 +10,7 @@ module QA # TODO, since `Signed in successfully` message was removed # this is the only way to tell if user is signed in correctly. # - Page::Menu::Main.perform do |menu| + Page::Main::Menu.perform do |menu| expect(menu).to have_personal_area end end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb index 478a5cb9c4c..fb6b4937554 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/register_spec.rb @@ -10,19 +10,19 @@ module QA # TODO, since `Signed in successfully` message was removed # this is the only way to tell if user is signed in correctly. # - Page::Menu::Main.perform do |menu| + Page::Main::Menu.perform do |menu| expect(menu).to have_personal_area end end end - context :manage do + context :manage, :skip_signup_disabled do describe 'standard' do it_behaves_like 'registration and login' end end - context :manage, :orchestrated, :ldap do + context :manage, :orchestrated, :ldap, :skip_signup_disabled do describe 'while LDAP is enabled' do it_behaves_like 'registration and login' end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb index 2ef8de61441..d1cd9865aef 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb @@ -27,7 +27,7 @@ module QA imported_project # import the project - Page::Menu::Main.act { go_to_projects } + Page::Main::Menu.act { go_to_projects } Page::Dashboard::Projects.perform do |dashboard| dashboard.go_to_project(imported_project.name) end @@ -48,7 +48,7 @@ module QA end def verify_issues_import - Page::Menu::Side.act { click_issues } + Page::Project::Menu.act { click_issues } expect(page).to have_content('This is a sample issue') click_link 'This is a sample issue' @@ -66,7 +66,7 @@ module QA end def verify_merge_requests_import - Page::Menu::Side.act { click_merge_requests } + Page::Project::Menu.act { click_merge_requests } expect(page).to have_content('Improve README.md') click_link 'Improve README.md' @@ -101,7 +101,7 @@ module QA end def verify_wiki_import - Page::Menu::Side.act { click_wiki } + Page::Project::Menu.act { click_wiki } expect(page).to have_content('Welcome to the test-project wiki!') end diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb index 34bb6f1c197..97ac35e8dba 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb @@ -13,7 +13,7 @@ module QA push.commit_message = 'Add README.md' end - Page::Menu::Side.act { go_to_activity } + Page::Project::Menu.act { go_to_activity } Page::Project::Activity.act { go_to_push_events } diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb index 542f532a629..49d76f31e3a 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb @@ -17,7 +17,7 @@ module QA it 'user creates an issue' do create_issue - Page::Menu::Side.act { click_issues } + Page::Project::Menu.act { click_issues } expect(page).to have_content(issue_title) end diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb index 407a15800ab..922feadb4e1 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb @@ -11,7 +11,7 @@ module QA merge_request.fork_branch = 'feature-branch' end - Page::Menu::Main.perform { |main| main.sign_out } + Page::Main::Menu.perform { |main| main.sign_out } Page::Main::Login.perform { |login| login.sign_in_using_credentials } merge_request.visit! diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index ddcbc94b1b1..984cea8ca10 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -11,7 +11,7 @@ module QA project.name = "only-fast-forward" end - Page::Menu::Side.act { go_to_settings } + Page::Project::Menu.act { go_to_settings } Page::Project::Settings::MergeRequest.act { enable_ff_only } merge_request = Factory::Resource::MergeRequest.fabricate! do |merge_request| diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb index 84f663c4866..b163ca896a7 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb @@ -16,8 +16,8 @@ module QA expect(page).to have_content("Title: #{key_title}") expect(page).to have_content(key.fingerprint) - Page::Menu::Main.act { go_to_profile_settings } - Page::Menu::Profile.act { click_ssh_keys } + Page::Main::Menu.act { go_to_profile_settings } + Page::Profile::Menu.act { click_ssh_keys } Page::Profile::SSHKeys.perform do |ssh_keys| ssh_keys.remove_key(key_title) diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb index 7c989bfd8cc..563393b3d07 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb @@ -28,8 +28,8 @@ module QA expect(page).to have_content('README.md') expect(page).to have_content('Test Use SSH Key') - Page::Menu::Main.act { go_to_profile_settings } - Page::Menu::Profile.act { click_ssh_keys } + Page::Main::Menu.act { go_to_profile_settings } + Page::Profile::Menu.act { click_ssh_keys } Page::Profile::SSHKeys.perform do |ssh_keys| ssh_keys.remove_key(key_title) diff --git a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb index 8009b9e8609..44dd85c1746 100644 --- a/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb @@ -40,7 +40,7 @@ module QA push.file_content = '# My Third Wiki Content' push.commit_message = 'Update Home.md' end - Page::Menu::Side.act { click_wiki } + Page::Project::Menu.act { click_wiki } expect(page).to have_content('My Third Wiki Content') end diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb index cdfe9b90e15..e901531b1bf 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/create_and_process_pipeline_spec.rb @@ -64,7 +64,7 @@ module QA expect(page).to have_content('Add .gitlab-ci.yml') - Page::Menu::Side.act { click_ci_cd_pipelines } + Page::Project::Menu.act { click_ci_cd_pipelines } expect(page).to have_content('All 1') expect(page).to have_content('Add .gitlab-ci.yml') diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb index 8352d13b06d..73af24e7f50 100644 --- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb +++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb @@ -28,7 +28,7 @@ module QA resource.image = 'gitlab/gitlab-runner:ubuntu' end - Page::Menu::Main.act { sign_out } + Page::Main::Menu.act { sign_out } end after(:all) do @@ -90,7 +90,7 @@ module QA sha1sum = Digest::SHA1.hexdigest(gitlab_ci) Page::Project::Show.act { wait_for_push } - Page::Menu::Side.act { click_ci_cd_pipelines } + Page::Project::Menu.act { click_ci_cd_pipelines } Page::Project::Pipeline::Index.act { go_to_latest_pipeline } Page::Project::Pipeline::Show.act { go_to_first_job } diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 844cc1236c7..785897f4a97 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -9,59 +9,73 @@ module QA @cluster&.remove! end - it 'user creates a new project and runs auto devops' do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + [true, false].each do |rbac| + context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do + it 'user creates a new project and runs auto devops' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } - project = Factory::Resource::Project.fabricate! do |p| - p.name = 'project-with-autodevops' - p.description = 'Project with Auto Devops' - end + project = Factory::Resource::Project.fabricate! do |p| + p.name = 'project-with-autodevops' + p.description = 'Project with Auto Devops' + end - # Disable code_quality check in Auto DevOps pipeline as it takes - # too long and times out the test - Factory::Resource::SecretVariable.fabricate! do |resource| - resource.project = project - resource.key = 'CODE_QUALITY_DISABLED' - resource.value = '1' - end + # Disable code_quality check in Auto DevOps pipeline as it takes + # too long and times out the test + Factory::Resource::SecretVariable.fabricate! do |resource| + resource.project = project + resource.key = 'CODE_QUALITY_DISABLED' + resource.value = '1' + end - # Create Auto Devops compatible repo - Factory::Repository::ProjectPush.fabricate! do |push| - push.project = project - push.directory = Pathname - .new(__dir__) - .join('../../../../../fixtures/auto_devops_rack') - push.commit_message = 'Create Auto DevOps compatible rack application' - end + # Create Auto Devops compatible repo + Factory::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.directory = Pathname + .new(__dir__) + .join('../../../../../fixtures/auto_devops_rack') + push.commit_message = 'Create Auto DevOps compatible rack application' + end - Page::Project::Show.act { wait_for_push } + Page::Project::Show.act { wait_for_push } - # Create and connect K8s cluster - @cluster = Service::KubernetesCluster.new.create! - kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster| - cluster.project = project - cluster.cluster = @cluster - cluster.install_helm_tiller = true - cluster.install_ingress = true - cluster.install_prometheus = true - cluster.install_runner = true - end + # Create and connect K8s cluster + @cluster = Service::KubernetesCluster.new(rbac: rbac).create! + kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster| + cluster.project = project + cluster.cluster = @cluster + cluster.install_helm_tiller = true + cluster.install_ingress = true + cluster.install_prometheus = true + cluster.install_runner = true + end - project.visit! - Page::Menu::Side.act { click_ci_cd_settings } - Page::Project::Settings::CICD.perform do |p| - p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io") - end + project.visit! + Page::Project::Menu.act { click_ci_cd_settings } + Page::Project::Settings::CICD.perform do |p| + p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io") + end + + project.visit! + Page::Project::Menu.act { click_ci_cd_pipelines } + Page::Project::Pipeline::Index.act { go_to_latest_pipeline } - project.visit! - Page::Menu::Side.act { click_ci_cd_pipelines } - Page::Project::Pipeline::Index.act { go_to_latest_pipeline } + Page::Project::Pipeline::Show.perform do |pipeline| + expect(pipeline).to have_build('build', status: :success, wait: 600) + expect(pipeline).to have_build('test', status: :success, wait: 600) + expect(pipeline).to have_build('production', status: :success, wait: 1200) + end - Page::Project::Pipeline::Show.perform do |pipeline| - expect(pipeline).to have_build('build', status: :success, wait: 600) - expect(pipeline).to have_build('test', status: :success, wait: 600) - expect(pipeline).to have_build('production', status: :success, wait: 1200) + Page::Menu::Side.act { click_operations_environments } + Page::Project::Operations::Environments::Index.perform do |index| + index.go_to_environment('production') + end + Page::Project::Operations::Environments::Show.perform do |show| + show.view_deployment do + expect(page).to have_content('Hello World!') + end + end + end end end end diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb index 6ffdc55538a..af24b36b734 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb @@ -6,7 +6,7 @@ module QA it 'user creates a group with a mattermost team' do Runtime::Browser.visit(:gitlab, Page::Main::Login) Page::Main::Login.act { sign_in_using_credentials } - Page::Menu::Main.act { go_to_groups } + Page::Main::Menu.act { go_to_groups } Page::Dashboard::Groups.perform do |page| page.go_to_new_group diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index fea0ef94df3..ad397c13f0c 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -23,6 +23,8 @@ module QA args.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any? end + args.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled? + args.push(options) args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} } diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb index d889d185a45..fda955f6600 100644 --- a/qa/spec/runtime/env_spec.rb +++ b/qa/spec/runtime/env_spec.rb @@ -1,39 +1,47 @@ describe QA::Runtime::Env do include Support::StubENV - describe '.chrome_headless?' do + shared_examples 'boolean method' do |method, env_key, default| context 'when there is an env variable set' do it 'returns false when falsey values specified' do - stub_env('CHROME_HEADLESS', 'false') - expect(described_class.chrome_headless?).to be_falsey + stub_env(env_key, 'false') + expect(described_class.public_send(method)).to be_falsey - stub_env('CHROME_HEADLESS', 'no') - expect(described_class.chrome_headless?).to be_falsey + stub_env(env_key, 'no') + expect(described_class.public_send(method)).to be_falsey - stub_env('CHROME_HEADLESS', '0') - expect(described_class.chrome_headless?).to be_falsey + stub_env(env_key, '0') + expect(described_class.public_send(method)).to be_falsey end it 'returns true when anything else specified' do - stub_env('CHROME_HEADLESS', 'true') - expect(described_class.chrome_headless?).to be_truthy + stub_env(env_key, 'true') + expect(described_class.public_send(method)).to be_truthy - stub_env('CHROME_HEADLESS', '1') - expect(described_class.chrome_headless?).to be_truthy + stub_env(env_key, '1') + expect(described_class.public_send(method)).to be_truthy - stub_env('CHROME_HEADLESS', 'anything') - expect(described_class.chrome_headless?).to be_truthy + stub_env(env_key, 'anything') + expect(described_class.public_send(method)).to be_truthy end end context 'when there is no env variable set' do - it 'returns the default, true' do - stub_env('CHROME_HEADLESS', nil) - expect(described_class.chrome_headless?).to be_truthy + it "returns the default, #{default}" do + stub_env(env_key, nil) + expect(described_class.public_send(method)).to be(default) end end end + describe '.signup_disabled?' do + it_behaves_like 'boolean method', :signup_disabled?, 'SIGNUP_DISABLED', false + end + + describe '.chrome_headless?' do + it_behaves_like 'boolean method', :chrome_headless?, 'CHROME_HEADLESS', true + end + describe '.running_in_ci?' do context 'when there is an env variable set' do it 'returns true if CI' do diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index cf22d1c9395..9ddaf7ab1b3 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -62,6 +62,20 @@ describe QA::Specs::Runner do end end + context 'when SIGNUP_DISABLED is true' do + before do + allow(QA::Runtime::Env).to receive(:signup_disabled?).and_return(true) + end + + subject { described_class.new } + + it 'it includes default args and excludes the skip_signup_disabled tag' do + expect_rspec_runner_arguments(['--tag', '~orchestrated', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS]) + + subject.perform + end + end + def expect_rspec_runner_arguments(arguments) expect(RSpec::Core::Runner).to receive(:run) .with(arguments, $stderr, $stdout) diff --git a/scripts/trigger-build b/scripts/trigger-build index 0b5fd5995dd..4534fcadebf 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# frozen_string_literal: true require 'gitlab' @@ -6,38 +7,27 @@ require 'gitlab' # Configure credentials to be used with gitlab gem # Gitlab.configure do |config| - config.endpoint = 'https://gitlab.com/api/v4' - config.private_token = ENV['GITLAB_QA_ACCESS_TOKEN'] # gitlab-qa bot access token + config.endpoint = 'https://gitlab.com/api/v4' end module Trigger - TOKEN = ENV['BUILD_TRIGGER_TOKEN'] - def self.ee? ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('CHANGELOG-EE.md') end class Base - def initialize(api_token) - Gitlab.private_token = api_token - end - def invoke!(post_comment: false) pipeline = Gitlab.run_trigger( downstream_project_path, - Trigger::TOKEN, + trigger_token, ref, variables) - puts "Triggered #{pipeline.web_url}" + puts "Triggered downstream pipeline: #{pipeline.web_url}\n" puts "Waiting for downstream pipeline status" - begin - Trigger::CommitComment.post!(downstream_project_path, pipeline) if post_comment - rescue Gitlab::Error::Error => error - puts "Ignoring the following error: #{error}" - end - Trigger::Pipeline.new(downstream_project_path, pipeline.id) + Trigger::CommitComment.post!(pipeline, access_token) if post_comment + Trigger::Pipeline.new(downstream_project_path, pipeline.id, access_token) end private @@ -52,6 +42,16 @@ module Trigger raise NotImplementedError end + # Must be overriden + def trigger_token + raise NotImplementedError + end + + # Must be overriden + def access_token + raise NotImplementedError + end + # Can be overriden def extra_variables {} @@ -68,7 +68,10 @@ module Trigger def base_variables { - 'TRIGGERED_USER' => ENV['GITLAB_USER_NAME'], + 'TOP_UPSTREAM_TRIGGER_PROJECT' => ENV['TOP_UPSTREAM_TRIGGER_PROJECT'] || ENV['CI_PROJECT_PATH'], + 'UPSTREAM_TRIGGER_PROJECT' => ENV['CI_PROJECT_PATH'], + 'UPSTREAM_TRIGGER_SOURCE' => ENV['TRIGGER_SOURCE'], + 'TRIGGERED_USER' => ENV['TRIGGERED_USER'] || ENV['GITLAB_USER_NAME'], 'TRIGGER_SOURCE' => ENV['CI_JOB_URL'] } end @@ -85,13 +88,21 @@ module Trigger private def downstream_project_path - 'gitlab-org/omnibus-gitlab'.freeze + 'gitlab-org/omnibus-gitlab' end def ref ENV['OMNIBUS_BRANCH'] || 'master' end + def trigger_token + ENV['BUILD_TRIGGER_TOKEN'] + end + + def access_token + ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] + end + def extra_variables { 'GITLAB_VERSION' => ENV['CI_COMMIT_SHA'], @@ -112,6 +123,14 @@ module Trigger ENV['CNG_BRANCH'] || 'master' end + def trigger_token + ENV['BUILD_TRIGGER_TOKEN'] + end + + def access_token + ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] + end + def extra_variables edition = Trigger.ee? ? 'EE' : 'CE' @@ -134,11 +153,16 @@ module Trigger end class CommitComment - def self.post!(downstream_project_path, downstream_pipeline) + def self.post!(downstream_pipeline, access_token) + Gitlab.private_token = access_token + Gitlab.create_commit_comment( ENV['CI_PROJECT_PATH'], ENV['CI_COMMIT_SHA'], "The [`#{ENV['CI_JOB_NAME']}`](#{ENV['CI_JOB_URL']}) job from pipeline #{ENV['CI_PIPELINE_URL']} triggered #{downstream_pipeline.web_url} downstream.") + + rescue Gitlab::Error::Error => error + puts "Ignoring the following error: #{error}" end end @@ -146,15 +170,16 @@ module Trigger INTERVAL = 60 # seconds MAX_DURATION = 3600 * 3 # 3 hours - attr_reader :project, :id + attr_reader :project, :id, :api_token - def initialize(project, id) + def initialize(project, id, api_token) @project = project @id = id + @api_token = api_token @start = Time.now.to_i # gitlab-bot's token "GitLab multi-project pipeline polling" - Gitlab.private_token = ENV['GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN'] + Gitlab.private_token = api_token end def wait! @@ -197,9 +222,9 @@ end case ARGV[0] when 'omnibus' - Trigger::Omnibus.new(ENV['GITLAB_QA_ACCESS_TOKEN']).invoke!(post_comment: true).wait! + Trigger::Omnibus.new.invoke!(post_comment: true).wait! when 'cng' - Trigger::CNG.new(ENV['GITLAB_QA_ACCESS_TOKEN']).invoke!.wait! + Trigger::CNG.new.invoke!.wait! else puts "Please provide a valid option: omnibus - Triggers a pipeline that builds the omnibus-gitlab package diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 10e1bfc30f9..2e0f79cd313 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -86,4 +86,22 @@ describe Admin::ApplicationSettingsController do expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) end end + + describe 'PUT #reset_registration_token' do + before do + sign_in(admin) + end + + subject { put :reset_registration_token } + + it 'resets runner registration token' do + expect { subject }.to change { ApplicationSetting.current.runners_registration_token } + end + + it 'redirects the user to admin runners page' do + subject + + expect(response).to redirect_to(admin_runners_path) + end + end end diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index cc200b9fed9..ee1aff09bdf 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -42,4 +42,15 @@ describe Admin::ProjectsController do expect { get :index }.not_to exceed_query_limit(control_count) end end + + describe 'GET /projects/:id' do + render_views + + it 'renders show page' do + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to have_gitlab_http_status(200) + expect(response.body).to match(project.name) + end + end end diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index ea18122e0c3..06ccace8242 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -17,4 +17,18 @@ describe Groups::Settings::CiCdController do expect(response).to render_template(:show) end end + + describe 'PUT #reset_registration_token' do + subject { put :reset_registration_token, group_id: group } + + it 'resets runner registration token' do + expect { subject }.to change { group.reload.runners_token } + end + + it 'redirects the user to admin runners page' do + subject + + expect(response).to redirect_to(group_settings_ci_cd_path) + end + end end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 65d6cd1a295..a099cdafa58 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -202,8 +202,8 @@ describe GroupsController do end describe 'GET #issues' do - let(:issue_1) { create(:issue, project: project) } - let(:issue_2) { create(:issue, project: project) } + let(:issue_1) { create(:issue, project: project, title: 'foo') } + let(:issue_2) { create(:issue, project: project, title: 'bar') } before do create_list(:award_emoji, 3, awardable: issue_2) @@ -224,6 +224,31 @@ describe GroupsController do expect(assigns(:issues)).to eq [issue_2, issue_1] end end + + context 'searching' do + # Remove as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/52271 + before do + stub_feature_flags(use_cte_for_group_issues_search: false) + end + + it 'works with popularity sort' do + get :issues, id: group.to_param, search: 'foo', sort: 'popularity' + + expect(assigns(:issues)).to eq([issue_1]) + end + + it 'works with priority sort' do + get :issues, id: group.to_param, search: 'foo', sort: 'priority' + + expect(assigns(:issues)).to eq([issue_1]) + end + + it 'works with label priority sort' do + get :issues, id: group.to_param, search: 'foo', sort: 'label_priority' + + expect(assigns(:issues)).to eq([issue_1]) + end + end end describe 'GET #merge_requests' do diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index 436f4525093..6091185e252 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -19,15 +19,17 @@ describe Projects::ArtifactsController do end describe 'GET download' do - subject { get :download, namespace_id: project.namespace, project_id: project, job_id: job, file_type: file_type } + def download_artifact(extra_params = {}) + params = { namespace_id: project.namespace, project_id: project, job_id: job }.merge(extra_params) - context 'when no file type is supplied' do - let(:file_type) { nil } + get :download, params + end + context 'when no file type is supplied' do it 'sends the artifacts file' do expect(controller).to receive(:send_file).with(job.artifacts_file.path, hash_including(disposition: 'attachment')).and_call_original - subject + download_artifact end end @@ -36,7 +38,7 @@ describe Projects::ArtifactsController do let(:file_type) { 'invalid' } it 'returns 404' do - subject + download_artifact(file_type: file_type) expect(response).to have_gitlab_http_status(404) end @@ -52,7 +54,7 @@ describe Projects::ArtifactsController do it 'sends the codequality report' do expect(controller).to receive(:send_file).with(job.job_artifacts_codequality.file.path, hash_including(disposition: 'attachment')).and_call_original - subject + download_artifact(file_type: file_type) end end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 8695aa826bb..17883d0fadd 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -97,6 +97,30 @@ describe Projects::CompareController do expect(assigns(:commits)).to eq([]) end end + + context 'when the target ref is invalid' do + let(:target_ref) { "master%' AND 2554=4423 AND '%'='" } + let(:source_ref) { "improve%2Fawesome" } + + it 'shows a flash message and redirects' do + show_request + + expect(flash[:alert]).to eq('Invalid branch name') + expect(response).to have_http_status(302) + end + end + + context 'when the source ref is invalid' do + let(:source_ref) { "master%' AND 2554=4423 AND '%'='" } + let(:target_ref) { "improve%2Fawesome" } + + it 'shows a flash message and redirects' do + show_request + + expect(flash[:alert]).to eq('Invalid branch name') + expect(response).to have_http_status(302) + end + end end describe 'GET diff_for_path' do diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 5b347b1109a..9df77560320 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -637,6 +637,18 @@ describe Projects::IssuesController do project_id: project, id: id end + + it 'avoids (most) N+1s loading labels', :request_store do + label = create(:label, project: project).to_reference + labels = create_list(:label, 10, project: project).map(&:to_reference) + issue = create(:issue, project: project, description: 'Test issue') + + control_count = ActiveRecord::QueryRecorder.new { issue.update(description: [issue.description, label].join(' ')) }.count + + # Follow-up to get rid of this `2 * label.count` requirement: https://gitlab.com/gitlab-org/gitlab-ce/issues/52230 + expect { issue.update(description: [issue.description, labels].join(' ')) } + .not_to exceed_query_limit(control_count + 2 * labels.count) + end end describe 'GET #realtime_changes' do diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 5b09e4a082c..5c8180baf8a 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -225,7 +225,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:ok) expect(json_response).to match_schema('job/job_details') expect(json_response['deployment_status']["status"]).to eq 'creating' - expect(json_response['deployment_status']["icon"]).to eq 'passed' expect(json_response['deployment_status']["environment"]).not_to be_nil end end @@ -600,35 +599,68 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do before do project.add_developer(user) sign_in(user) - - post_cancel end - context 'when job is cancelable' do + context 'when continue url is present' do let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) } - it 'redirects to the canceled job page' do - expect(response).to have_gitlab_http_status(:found) - expect(response).to redirect_to(namespace_project_job_path(id: job.id)) + context 'when continue to is a safe url' do + let(:url) { '/test' } + + before do + post_cancel(continue: { to: url }) + end + + it 'redirects to the continue url' do + expect(response).to have_gitlab_http_status(:found) + expect(response).to redirect_to(url) + end + + it 'transits to canceled' do + expect(job.reload).to be_canceled + end end - it 'transits to canceled' do - expect(job.reload).to be_canceled + context 'when continue to is not a safe url' do + let(:url) { 'http://example.com' } + + it 'raises an error' do + expect { cancel_with_redirect(url) }.to raise_error + end end end - context 'when job is not cancelable' do - let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } + context 'when continue url is not present' do + before do + post_cancel + end - it 'returns unprocessable_entity' do - expect(response).to have_gitlab_http_status(:unprocessable_entity) + context 'when job is cancelable' do + let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) } + + it 'redirects to the builds page' do + expect(response).to have_gitlab_http_status(:found) + expect(response).to redirect_to(builds_namespace_project_pipeline_path(id: pipeline.id)) + end + + it 'transits to canceled' do + expect(job.reload).to be_canceled + end + end + + context 'when job is not cancelable' do + let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } + + it 'returns unprocessable_entity' do + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end end end - def post_cancel - post :cancel, namespace_id: project.namespace, - project_id: project, - id: job.id + def post_cancel(additional_params = {}) + post :cancel, { namespace_id: project.namespace, + project_id: project, + id: job.id }.merge(additional_params) end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 7446e0650f7..73b62dc1151 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -76,28 +76,6 @@ describe Projects::MergeRequestsController do expect(response).to be_success end - context "loads notes" do - let(:first_contributor) { create(:user) } - let(:contributor) { create(:user) } - let(:merge_request) { create(:merge_request, author: first_contributor, target_project: project, source_project: project) } - let(:contributor_merge_request) { create(:merge_request, :merged, author: contributor, target_project: project, source_project: project) } - # the order here is important - # as the controller reloads these from DB, references doesn't correspond after - let!(:first_contributor_note) { create(:note, author: first_contributor, noteable: merge_request, project: project) } - let!(:contributor_note) { create(:note, author: contributor, noteable: merge_request, project: project) } - let!(:owner_note) { create(:note, author: user, noteable: merge_request, project: project) } - - it "with special_role FIRST_TIME_CONTRIBUTOR" do - go(format: :html) - - notes = assigns(:notes) - expect(notes).to match(a_collection_containing_exactly(an_object_having_attributes(special_role: Note::SpecialRole::FIRST_TIME_CONTRIBUTOR), - an_object_having_attributes(special_role: nil), - an_object_having_attributes(special_role: nil) - )) - end - end - context "that is invalid" do let(:merge_request) { create(:invalid_merge_request, target_project: project, source_project: project) } @@ -763,25 +741,35 @@ describe Projects::MergeRequestsController do describe 'GET ci_environments_status' do context 'the environment is from a forked project' do - let!(:forked) { fork_project(project, user, repository: true) } - let!(:environment) { create(:environment, project: forked) } - let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } - let(:admin) { create(:admin) } + let!(:forked) { fork_project(project, user, repository: true) } + let!(:environment) { create(:environment, project: forked) } + let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } + let(:admin) { create(:admin) } let(:merge_request) do create(:merge_request, source_project: forked, target_project: project) end - before do + it 'links to the environment on that project' do + get_ci_environments_status + + expect(json_response.first['url']).to match /#{forked.full_path}/ + end + + # we're trying to reduce the overall number of queries for this method. + # set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/52287 + it 'keeps queries in check' do + control_count = ActiveRecord::QueryRecorder.new { get_ci_environments_status }.count + + expect(control_count).to be <= 137 + end + + def get_ci_environments_status get :ci_environments_status, namespace_id: merge_request.project.namespace.to_param, project_id: merge_request.project, id: merge_request.iid, format: 'json' end - - it 'links to the environment on that project' do - expect(json_response.first['url']).to match /#{forked.full_path}/ - end end end diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index 1f14a0cc381..4629929f9af 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -74,6 +74,19 @@ describe Projects::Settings::CiCdController do end end + describe 'PUT #reset_registration_token' do + subject { put :reset_registration_token, namespace_id: project.namespace, project_id: project } + it 'resets runner registration token' do + expect { subject }.to change { project.reload.runners_token } + end + + it 'redirects the user to admin runners page' do + subject + + expect(response).to redirect_to(namespace_project_settings_ci_cd_path) + end + end + describe 'PATCH update' do let(:params) { { ci_config_path: '' } } diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb index 9a65e7f8a3f..1a2be5e9552 100644 --- a/spec/factories/broadcast_messages.rb +++ b/spec/factories/broadcast_messages.rb @@ -1,17 +1,17 @@ FactoryBot.define do factory :broadcast_message do message "MyText" - starts_at 1.day.ago - ends_at 1.day.from_now + starts_at { 1.day.ago } + ends_at { 1.day.from_now } trait :expired do - starts_at 5.days.ago - ends_at 3.days.ago + starts_at { 5.days.ago } + ends_at { 3.days.ago } end trait :future do - starts_at 5.days.from_now - ends_at 6.days.from_now + starts_at { 5.days.from_now } + ends_at { 6.days.from_now } end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 7a4b1dfafac..85ba7d4097d 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -180,12 +180,12 @@ FactoryBot.define do end trait :erased do - erased_at Time.now + erased_at { Time.now } erased_by factory: :user end trait :queued do - queued_at Time.now + queued_at { Time.now } runner factory: :ci_runner end @@ -215,7 +215,7 @@ FactoryBot.define do end trait :expired do - artifacts_expire_at 1.minute.ago + artifacts_expire_at { 1.minute.ago } end trait :with_commit do diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index 347e4f433e2..f564e7bee47 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -9,7 +9,7 @@ FactoryBot.define do runner_type :instance_type trait :online do - contacted_at Time.now + contacted_at { Time.now } end trait :instance do diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index 5756486df27..3c9ca22a051 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -42,7 +42,7 @@ FactoryBot.define do trait :timeouted do installing - updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago + updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago } end factory :clusters_applications_ingress, class: Clusters::Applications::Ingress do diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb index 36ac2372204..4a0d1b181ea 100644 --- a/spec/factories/clusters/platforms/kubernetes.rb +++ b/spec/factories/clusters/platforms/kubernetes.rb @@ -3,11 +3,10 @@ FactoryBot.define do cluster namespace nil api_url 'https://kubernetes.example.com' - token 'a' * 40 + token { 'a' * 40 } trait :configured do api_url 'https://kubernetes.example.com' - token 'a' * 40 username 'xxxxxx' password 'xxxxxx' diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb index d23ddf9d79b..feacd5ccf15 100644 --- a/spec/factories/emails.rb +++ b/spec/factories/emails.rb @@ -3,7 +3,7 @@ FactoryBot.define do user email { generate(:email_alias) } - trait(:confirmed) { confirmed_at Time.now } + trait(:confirmed) { confirmed_at { Time.now } } trait(:skip_validate) { to_create {|instance| instance.save(validate: false) } } end end diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb index 51b8ddc9934..3c0f43cc1b6 100644 --- a/spec/factories/gpg_keys.rb +++ b/spec/factories/gpg_keys.rb @@ -2,11 +2,11 @@ require_relative '../support/helpers/gpg_helpers' FactoryBot.define do factory :gpg_key do - key GpgHelpers::User1.public_key + key { GpgHelpers::User1.public_key } user factory :gpg_key_with_subkeys do - key GpgHelpers::User1.public_key_with_extra_signing_key + key { GpgHelpers::User1.public_key_with_extra_signing_key } end end end diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb index 47036560b9d..12be63e5d92 100644 --- a/spec/factories/group_members.rb +++ b/spec/factories/group_members.rb @@ -9,7 +9,7 @@ FactoryBot.define do trait(:developer) { access_level GroupMember::DEVELOPER } trait(:maintainer) { access_level GroupMember::MAINTAINER } trait(:owner) { access_level GroupMember::OWNER } - trait(:access_request) { requested_at Time.now } + trait(:access_request) { requested_at { Time.now } } trait(:invited) do user_id nil diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index b8b089b069b..8094c43b065 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -80,7 +80,7 @@ FactoryBot.define do trait :merge_when_pipeline_succeeds do merge_when_pipeline_succeeds true - merge_user author + merge_user { author } end trait :remove_source_branch do diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 6844ed8aa4a..2d1f48bf249 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -90,7 +90,7 @@ FactoryBot.define do noteable nil noteable_type 'Commit' noteable_id nil - commit_id RepoHelpers.sample_commit.id + commit_id { RepoHelpers.sample_commit.id } end trait :legacy_diff_note do diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb index 9e6af24c4eb..02c51cd9899 100644 --- a/spec/factories/oauth_access_grants.rb +++ b/spec/factories/oauth_access_grants.rb @@ -3,7 +3,7 @@ FactoryBot.define do resource_owner_id { create(:user).id } application token { Doorkeeper::OAuth::Helpers::UniqueToken.generate } - expires_in 2.hours + expires_in { 2.hours } redirect_uri { application.redirect_uri } scopes { application.scopes } diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb index b77f702f9e1..75ac7cc7687 100644 --- a/spec/factories/project_auto_devops.rb +++ b/spec/factories/project_auto_devops.rb @@ -5,8 +5,16 @@ FactoryBot.define do domain "example.com" deploy_strategy :continuous - trait :manual do - deploy_strategy :manual + trait :continuous_deployment do + deploy_strategy ProjectAutoDevops.deploy_strategies[:continuous] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically + end + + trait :manual_deployment do + deploy_strategy ProjectAutoDevops.deploy_strategies[:manual] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically + end + + trait :timed_incremental_deployment do + deploy_strategy ProjectAutoDevops.deploy_strategies[:timed_incremental] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically end trait :disabled do diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index 22a8085ea45..c72e0487895 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -8,7 +8,7 @@ FactoryBot.define do trait(:reporter) { access_level ProjectMember::REPORTER } trait(:developer) { access_level ProjectMember::DEVELOPER } trait(:maintainer) { access_level ProjectMember::MAINTAINER } - trait(:access_request) { requested_at Time.now } + trait(:access_request) { requested_at { Time.now } } trait(:invited) do user_id nil diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 80801eb1082..e4823a5adf1 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -1,6 +1,8 @@ require_relative '../support/helpers/test_env' FactoryBot.define do + PAGES_ACCESS_LEVEL_SCHEMA_VERSION = 20180423204600 + # Project without repository # # Project does not have bare repository. @@ -23,6 +25,7 @@ FactoryBot.define do issues_access_level ProjectFeature::ENABLED merge_requests_access_level ProjectFeature::ENABLED repository_access_level ProjectFeature::ENABLED + pages_access_level ProjectFeature::ENABLED # we can't assign the delegated `#ci_cd_settings` attributes directly, as the # `#ci_cd_settings` relation needs to be created first @@ -34,13 +37,20 @@ FactoryBot.define do builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min - project.project_feature.update( + hash = { wiki_access_level: evaluator.wiki_access_level, builds_access_level: builds_access_level, snippets_access_level: evaluator.snippets_access_level, issues_access_level: evaluator.issues_access_level, merge_requests_access_level: merge_requests_access_level, - repository_access_level: evaluator.repository_access_level) + repository_access_level: evaluator.repository_access_level + } + + if ActiveRecord::Migrator.current_version >= PAGES_ACCESS_LEVEL_SCHEMA_VERSION + hash.store("pages_access_level", evaluator.pages_access_level) + end + + project.project_feature.update(hash) # Normally the class Projects::CreateService is used for creating # projects, and this class takes care of making sure the owner and current @@ -244,6 +254,10 @@ FactoryBot.define do trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED } trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED } trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE } + trait(:pages_public) { pages_access_level ProjectFeature::PUBLIC } + trait(:pages_enabled) { pages_access_level ProjectFeature::ENABLED } + trait(:pages_disabled) { pages_access_level ProjectFeature::DISABLED } + trait(:pages_private) { pages_access_level ProjectFeature::PRIVATE } trait :auto_devops do association :auto_devops, factory: :project_auto_devops diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 14486c80341..ed3d87eb76b 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -49,7 +49,7 @@ FactoryBot.define do author user action { Todo::ASSIGNED } - commit_id RepoHelpers.sample_commit.id + commit_id { RepoHelpers.sample_commit.id } target_type "Commit" end end diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index 81c485fba1a..7256f785e1f 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :upload do model { build(:project) } - size 100.kilobytes + size { 100.kilobytes } uploader "AvatarUploader" mount_point :avatar secret nil @@ -19,13 +19,13 @@ FactoryBot.define do uploader "PersonalFileUploader" path { File.join(secret, filename) } model { build(:personal_snippet) } - secret SecureRandom.hex + secret { SecureRandom.hex } end trait :issuable_upload do uploader "FileUploader" path { File.join(secret, filename) } - secret SecureRandom.hex + secret { SecureRandom.hex } end trait :with_file do @@ -43,14 +43,14 @@ FactoryBot.define do model { build(:group) } path { File.join(secret, filename) } uploader "NamespaceFileUploader" - secret SecureRandom.hex + secret { SecureRandom.hex } end trait :favicon_upload do model { build(:appearance) } path { File.join(secret, filename) } uploader "FaviconUploader" - secret SecureRandom.hex + secret { SecureRandom.hex } end trait :attachment_upload do diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index f08946b0593..aa3ca8923ff 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -64,7 +64,7 @@ describe 'Contributions Calendar', :js do end def selected_day_activities(visible: true) - find('.user-calendar-activities', visible: visible).text + find('.tab-pane#activity .user-calendar-activities', visible: visible).text end before do @@ -74,15 +74,16 @@ describe 'Contributions Calendar', :js do describe 'calendar day selection' do before do visit user.username + page.find('.js-activity-tab a').click wait_for_requests end it 'displays calendar' do - expect(page).to have_css('.js-contrib-calendar') + expect(find('.tab-pane#activity')).to have_css('.js-contrib-calendar') end describe 'select calendar day' do - let(:cells) { page.all('.user-contrib-cell') } + let(:cells) { page.all('.tab-pane#activity .user-contrib-cell') } before do cells[0].click @@ -108,6 +109,7 @@ describe 'Contributions Calendar', :js do describe 'deselect calendar day' do before do cells[0].click + page.find('.js-activity-tab a').click wait_for_requests end @@ -122,6 +124,7 @@ describe 'Contributions Calendar', :js do shared_context 'visit user page' do before do visit user.username + page.find('.js-activity-tab a').click wait_for_requests end end @@ -130,12 +133,12 @@ describe 'Contributions Calendar', :js do include_context 'visit user page' it 'displays calendar activity square color for 1 contribution' do - expect(page).to have_selector(get_cell_color_selector(contribution_count), count: 1) + expect(find('.tab-pane#activity')).to have_selector(get_cell_color_selector(contribution_count), count: 1) end it 'displays calendar activity square on the correct date' do today = Date.today.strftime(date_format) - expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1) + expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(contribution_count, today), count: 1) end end @@ -150,7 +153,7 @@ describe 'Contributions Calendar', :js do include_context 'visit user page' it 'displays calendar activity log' do - expect(find('.content_list .event-note')).to have_content issue_title + expect(find('.tab-pane#activity .content_list .event-note')).to have_content issue_title end end end @@ -182,17 +185,17 @@ describe 'Contributions Calendar', :js do include_context 'visit user page' it 'displays calendar activity squares for both days' do - expect(page).to have_selector(get_cell_color_selector(1), count: 2) + expect(find('.tab-pane#activity')).to have_selector(get_cell_color_selector(1), count: 2) end it 'displays calendar activity square for yesterday' do yesterday = Date.yesterday.strftime(date_format) - expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1) + expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(1, yesterday), count: 1) end it 'displays calendar activity square for today' do today = Date.today.strftime(date_format) - expect(page).to have_selector(get_cell_date_selector(1, today), count: 1) + expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(1, today), count: 1) end end end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index d7234158fa1..0db8093411b 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -14,7 +14,7 @@ describe 'Tooltips on .timeago dates', :js do updated_at: created_date, created_at: created_date) sign_in user - visit user_path(user) + visit user_activity_path(user) wait_for_requests() page.find('.js-timeago').hover diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb index 59254ecc982..08fd9f8af2a 100644 --- a/spec/features/groups/group_settings_spec.rb +++ b/spec/features/groups/group_settings_spec.rb @@ -98,6 +98,22 @@ describe 'Edit group settings' do end end + describe 'edit group path' do + it 'has a root URL label for top-level group' do + visit edit_group_path(group) + + expect(find(:css, '.group-root-path').text).to eq(root_url) + end + + it 'has a parent group URL label for a subgroup group', :postgresql do + subgroup = create(:group, parent: group) + + visit edit_group_path(subgroup) + + expect(find(:css, '.group-root-path').text).to eq(group_url(subgroup.parent) + '/') + end + end + def update_path(new_group_path) visit edit_group_path(group) diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb index d9543bfa97f..22b51b297a6 100644 --- a/spec/features/groups/labels/subscription_spec.rb +++ b/spec/features/groups/labels/subscription_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe 'Labels subscription' do let(:user) { create(:user) } let(:group) { create(:group) } - let!(:feature) { create(:group_label, group: group, title: 'feature') } + let!(:label1) { create(:group_label, group: group, title: 'foo') } + let!(:label2) { create(:group_label, group: group, title: 'bar') } context 'when signed in' do before do @@ -14,9 +15,9 @@ describe 'Labels subscription' do it 'users can subscribe/unsubscribe to group labels', :js do visit group_labels_path(group) - expect(page).to have_content('feature') + expect(page).to have_content(label1.title) - within "#group_label_#{feature.id}" do + within "#group_label_#{label1.id}" do expect(page).not_to have_button 'Unsubscribe' click_button 'Subscribe' @@ -30,15 +31,48 @@ describe 'Labels subscription' do expect(page).not_to have_button 'Unsubscribe' end end + + context 'subscription filter' do + before do + visit group_labels_path(group) + end + + it 'shows only subscribed labels' do + label1.subscribe(user) + + click_subscribed_tab + + page.within('.labels-container') do + expect(page).to have_content label1.title + end + end + + it 'shows no subscribed labels message' do + click_subscribed_tab + + page.within('.labels-container') do + expect(page).not_to have_content label1.title + expect(page).to have_content('You do not have any subscriptions yet') + end + end + end end context 'when not signed in' do - it 'users can not subscribe/unsubscribe to labels' do + before do visit group_labels_path(group) + end - expect(page).to have_content 'feature' + it 'users can not subscribe/unsubscribe to labels' do + expect(page).to have_content label1.title expect(page).not_to have_button('Subscribe') end + + it 'does not show subscribed tab' do + page.within('.nav-tabs') do + expect(page).not_to have_link 'Subscribed' + end + end end def click_link_on_dropdown(text) @@ -48,4 +82,10 @@ describe 'Labels subscription' do find('a.js-subscribe-button', text: text).click end end + + def click_subscribed_tab + page.within('.nav-tabs') do + click_link 'Subscribed' + end + end end diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb new file mode 100644 index 00000000000..d422fd18346 --- /dev/null +++ b/spec/features/groups/settings/ci_cd_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Group CI/CD settings' do + include WaitForRequests + + let(:user) {create(:user)} + let(:group) {create(:group)} + + before do + group.add_owner(user) + sign_in(user) + end + + describe 'runners registration token' do + let!(:token) { group.runners_token } + + before do + visit group_settings_ci_cd_path(group) + end + + it 'has a registration token' do + expect(page.find('#registration_token')).to have_content(token) + end + + describe 'reload registration token' do + let(:page_token) { find('#registration_token').text } + + before do + click_button 'Reset runners registration token' + end + + it 'changes registration token' do + expect(page_token).not_to eq token + end + end + end +end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 98e37d8011a..08bf9bc7243 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -5,6 +5,7 @@ describe 'GFM autocomplete', :js do let(:project) { create(:project) } let(:label) { create(:label, project: project, title: 'special+') } let(:issue) { create(:issue, project: project) } + let!(:project_snippet) { create(:project_snippet, project: project, title: 'code snippet') } before do project.add_maintainer(user) @@ -301,6 +302,16 @@ describe 'GFM autocomplete', :js do end end + it 'shows project snippets' do + page.within '.timeline-content-form' do + find('#note-body').native.send_keys('$') + end + + page.within '.atwho-container' do + expect(page).to have_content(project_snippet.title) + end + end + private def expect_to_wrap(should_wrap, item, note, value) diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 206a3a4fe9a..5e0434c1c2c 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -61,83 +61,231 @@ describe 'User edit profile' do end context 'user status', :js do - def select_emoji(emoji_name) + def select_emoji(emoji_name, is_modal = false) + emoji_menu_class = is_modal ? '.js-modal-status-emoji-menu' : '.js-status-emoji-menu' toggle_button = find('.js-toggle-emoji-menu') toggle_button.click - emoji_button = find(%Q{.js-status-emoji-menu .js-emoji-btn gl-emoji[data-name="#{emoji_name}"]}) + emoji_button = find(%Q{#{emoji_menu_class} .js-emoji-btn gl-emoji[data-name="#{emoji_name}"]}) emoji_button.click end - it 'shows the user status form' do - visit(profile_path) + context 'profile edit form' do + it 'shows the user status form' do + visit(profile_path) - expect(page).to have_content('Current status') - end + expect(page).to have_content('Current status') + end - it 'adds emoji to user status' do - emoji = 'biohazard' - visit(profile_path) - select_emoji(emoji) - submit_settings + it 'adds emoji to user status' do + emoji = 'biohazard' + visit(profile_path) + select_emoji(emoji) + submit_settings - visit user_path(user) - within('.cover-status') do - expect(page).to have_emoji(emoji) + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(emoji) + end end - end - it 'adds message to user status' do - message = 'I have something to say' - visit(profile_path) - fill_in 'js-status-message-field', with: message - submit_settings + it 'adds message to user status' do + message = 'I have something to say' + visit(profile_path) + fill_in 'js-status-message-field', with: message + submit_settings - visit user_path(user) - within('.cover-status') do - expect(page).to have_emoji('speech_balloon') - expect(page).to have_content message + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji('speech_balloon') + expect(page).to have_content message + end end - end - it 'adds message and emoji to user status' do - emoji = 'tanabata_tree' - message = 'Playing outside' - visit(profile_path) - select_emoji(emoji) - fill_in 'js-status-message-field', with: message - submit_settings + it 'adds message and emoji to user status' do + emoji = 'tanabata_tree' + message = 'Playing outside' + visit(profile_path) + select_emoji(emoji) + fill_in 'js-status-message-field', with: message + submit_settings - visit user_path(user) - within('.cover-status') do - expect(page).to have_emoji(emoji) - expect(page).to have_content message + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(emoji) + expect(page).to have_content message + end end - end - it 'clears the user status' do - user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + it 'clears the user status' do + user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(user_status.emoji) + expect(page).to have_content user_status.message + end + + visit(profile_path) + click_button 'js-clear-user-status-button' + submit_settings + + wait_for_requests - visit user_path(user) - within('.cover-status') do - expect(page).to have_emoji(user_status.emoji) - expect(page).to have_content user_status.message + visit user_path(user) + expect(page).not_to have_selector '.cover-status' end - visit(profile_path) - click_button 'js-clear-user-status-button' - submit_settings + it 'displays a default emoji if only message is entered' do + message = 'a status without emoji' + visit(profile_path) + fill_in 'js-status-message-field', with: message - visit user_path(user) - expect(page).not_to have_selector '.cover-status' + within('.js-toggle-emoji-menu') do + expect(page).to have_emoji('speech_balloon') + end + end end - it 'displays a default emoji if only message is entered' do - message = 'a status without emoji' - visit(profile_path) - fill_in 'js-status-message-field', with: message + context 'user menu' do + def open_user_status_modal + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_button 'Set status' + end + end + + def set_user_status_in_modal + page.within "#set-user-status-modal" do + click_button 'Set status' + end + end + + before do + visit root_path(user) + end + + it 'shows the "Set status" menu item in the user menu' do + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + expect(page).to have_content('Set status') + end + end + + it 'shows the "Edit status" menu item in the user menu' do + user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + visit root_path(user) + + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + expect(page).to have_emoji(user_status.emoji) + expect(page).to have_content user_status.message + expect(page).to have_content('Edit status') + end + end + + it 'shows user status modal' do + open_user_status_modal + + expect(page.find('#set-user-status-modal')).to be_visible + expect(page).to have_content('Set a status') + end + + it 'adds emoji to user status' do + emoji = 'biohazard' + open_user_status_modal + select_emoji(emoji, true) + set_user_status_in_modal + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(emoji) + end + end + + it 'adds message to user status' do + message = 'I have something to say' + open_user_status_modal + find('.js-status-message-field').native.send_keys(message) + set_user_status_in_modal + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji('speech_balloon') + expect(page).to have_content message + end + end + + it 'adds message and emoji to user status' do + emoji = 'tanabata_tree' + message = 'Playing outside' + open_user_status_modal + select_emoji(emoji, true) + find('.js-status-message-field').native.send_keys(message) + set_user_status_in_modal + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(emoji) + expect(page).to have_content message + end + end + + it 'clears the user status with the "X" button' do + user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(user_status.emoji) + expect(page).to have_content user_status.message + end + + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_button 'Edit status' + end + + find('.js-clear-user-status-button').click + set_user_status_in_modal + + visit user_path(user) + expect(page).not_to have_selector '.cover-status' + end + + it 'clears the user status with the "Remove status" button' do + user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(user_status.emoji) + expect(page).to have_content user_status.message + end + + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_button 'Edit status' + end + + page.within "#set-user-status-modal" do + click_button 'Remove status' + end + + visit user_path(user) + expect(page).not_to have_selector '.cover-status' + end + + it 'displays a default emoji if only message is entered' do + message = 'a status without emoji' + open_user_status_modal + find('.js-status-message-field').native.send_keys(message) - within('.js-toggle-emoji-menu') do - expect(page).to have_emoji('speech_balloon') + within('.js-toggle-emoji-menu') do + expect(page).to have_emoji('speech_balloon') + end end end end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index edc763ad0ad..8b92b9fc869 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -84,10 +84,8 @@ describe 'Gcp Cluster', :js do it_behaves_like 'valid cluster gcp form' - context 'rbac_clusters feature flag is enabled' do + context 'RBAC is enabled for the cluster' do before do - stub_feature_flags(rbac_clusters: true) - check 'cluster_provider_gcp_attributes_legacy_abac' end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 2b4998ed5ac..9ae1dba60b5 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -44,10 +44,8 @@ describe 'User Cluster', :js do it_behaves_like 'valid cluster user form' - context 'rbac_clusters feature flag is enabled' do + context 'RBAC is enabled for the cluster' do before do - stub_feature_flags(rbac_clusters: true) - check 'cluster_platform_kubernetes_attributes_authorization_type' end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 45fc492f23f..2076ce7b4f7 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -542,7 +542,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) end - it 'shows manual action empty state' do + it 'shows manual action empty state', :js do expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This job requires a manual action') expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments') @@ -567,14 +567,13 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) end - it 'shows delayed job' do - expect(page).to have_content(job.detailed_status(user).illustration[:title]) + it 'shows delayed job', :js do expect(page).to have_content('This is a scheduled to run in') expect(page).to have_content("This job will automatically run after it's timer finishes.") expect(page).to have_link('Unschedule job') end - it 'unschedule delayed job and shows manual action', :js do + it 'unschedules delayed job and shows manual action', :js do click_link 'Unschedule job' wait_for_requests @@ -591,14 +590,14 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) end - it 'shows empty state' do + it 'shows empty state', :js do expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This job has not been triggered yet') expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered') end end - context 'Pending job' do + context 'Pending job', :js do let(:job) { create(:ci_build, :pending, pipeline: pipeline) } before do @@ -625,7 +624,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end - context 'without log' do + context 'without log', :js do let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } before do @@ -640,7 +639,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end - context 'Skipped job' do + context 'Skipped job', :js do let(:job) { create(:ci_build, :skipped, pipeline: pipeline) } before do @@ -654,7 +653,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end - context 'when job is failed but has no trace' do + context 'when job is failed but has no trace', :js do let(:job) { create(:ci_build, :failed, pipeline: pipeline) } it 'renders empty state' do diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index e6cb137b023..491c64fc329 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -84,7 +84,7 @@ describe 'Pipeline', :js do end end - it 'should be possible to cancel the running build' do + it 'cancels the running build and shows retry button' do find('#ci-badge-deploy .ci-action-icon-container').click page.within('#ci-badge-deploy') do @@ -112,8 +112,8 @@ describe 'Pipeline', :js do end end - context 'when pipeline has scheduled builds' do - it 'shows the scheduled icon and a unschedule action for the scheduled build' do + context 'when pipeline has a delayed job' do + it 'shows the scheduled icon and an unschedule action for the delayed job' do page.within('#ci-badge-delayed-job') do expect(page).to have_selector('.js-ci-status-icon-scheduled') expect(page).to have_content('delayed-job') @@ -124,10 +124,12 @@ describe 'Pipeline', :js do end end - it 'should be possible to unschedule the scheduled job' do + it 'unschedules the delayed job and shows play button as a manual job' do find('#ci-badge-delayed-job .ci-action-icon-container').click - expect(page).not_to have_content('Unschedule job') + page.within('#ci-badge-delayed-job') do + expect(page).to have_css('.js-icon-play') + end end end @@ -341,14 +343,16 @@ describe 'Pipeline', :js do it { expect(build_manual.reload).to be_pending } end - context 'unscheduling scheduled job' do + context 'when user unschedules a delayed job' do before do within '.pipeline-holder' do click_link('Unschedule') end end - it { expect(build_scheduled.reload).to be_manual } + it 'unschedules the delayed job and shows play button as a manual job' do + expect(page).to have_content('Trigger this manual action') + end end context 'failed jobs' do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 43f9608e8e3..17772a35779 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -232,7 +232,7 @@ describe 'Pipelines', :js do end end - context 'with delayed job' do + context 'when there is a delayed job' do let!(:delayed_job) do create(:ci_build, :scheduled, pipeline: pipeline, @@ -245,26 +245,43 @@ describe 'Pipelines', :js do visit_project_pipelines end - it 'has a dropdown with play button' do + it 'has a dropdown for actionable jobs' do expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play') end - it 'has link to the scheduled action' do + it "has link to the delayed job's action" do find('.js-pipeline-dropdown-manual-actions').click + time_diff = [0, delayed_job.scheduled_at - Time.now].max expect(page).to have_button('delayed job') + expect(page).to have_content(Time.at(time_diff).utc.strftime("%H:%M:%S")) end - context 'when scheduled action was played' do + context 'when delayed job is expired already' do + let!(:delayed_job) do + create(:ci_build, :expired_scheduled, + pipeline: pipeline, + name: 'delayed job', + stage: 'test', + commands: 'test') + end + + it "shows 00:00:00 as the remaining time" do + find('.js-pipeline-dropdown-manual-actions').click + + expect(page).to have_content("00:00:00") + end + end + + context 'when user played a delayed job immediately' do before do - accept_confirm do - find('.js-pipeline-dropdown-manual-actions').click - click_button('delayed job') - end + find('.js-pipeline-dropdown-manual-actions').click + page.accept_confirm { click_button('delayed job') } + wait_for_requests end - it 'enqueues scheduled action job' do - expect(page).to have_selector('.js-pipeline-dropdown-manual-actions:disabled') + it 'enqueues the delayed job', :js do + expect(delayed_job.reload).to be_pending end end end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 30b0a5578ea..6f8ec0015ad 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -137,5 +137,29 @@ describe "Projects > Settings > Pipelines settings" do end end end + + describe 'runners registration token' do + let!(:token) { project.runners_token } + + before do + visit project_settings_ci_cd_path(project) + end + + it 'has a registration token' do + expect(page.find('#registration_token')).to have_content(token) + end + + describe 'reload registration token' do + let(:page_token) { find('#registration_token').text } + + before do + click_button 'Reset runners registration token' + end + + it 'changes registration token' do + expect(page_token).not_to eq token + end + end + end end end diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb index 66afe163447..0725ff178ac 100644 --- a/spec/features/search/user_uses_search_filters_spec.rb +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -14,7 +14,7 @@ describe 'User uses search filters', :js do visit(search_path) end - context' when filtering by group' do + context 'when filtering by group' do it 'shows group projects' do find('.js-search-group-dropdown').click @@ -36,7 +36,7 @@ describe 'User uses search filters', :js do end end - context' when filtering by project' do + context 'when filtering by project' do it 'shows a project' do page.within('.project-filter') do find('.js-search-project-dropdown').click diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index f1192f48b86..ae9b65d1a39 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -42,7 +42,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do it 'allows registering a new device with a name' do visit profile_account_path manage_two_factor_authentication - expect(page).to have_content("You've already enabled two-factor authentication using mobile") + expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators") u2f_device = register_u2f_device @@ -70,7 +70,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do it 'allows deleting a device' do visit profile_account_path manage_two_factor_authentication - expect(page).to have_content("You've already enabled two-factor authentication using mobile") + expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators") first_u2f_device = register_u2f_device second_u2f_device = register_u2f_device(name: 'My other device') diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb new file mode 100644 index 00000000000..11f357cbaa5 --- /dev/null +++ b/spec/features/users/overview_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +describe 'Overview tab on a user profile', :js do + let(:user) { create(:user) } + let(:contributed_project) { create(:project, :public, :repository) } + + def push_code_contribution + event = create(:push_event, project: contributed_project, author: user) + + create(:push_event_payload, + event: event, + commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce', + commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2', + commit_count: 3, + ref: 'master') + end + + before do + sign_in user + end + + describe 'activities section' do + shared_context 'visit overview tab' do + before do + visit user.username + page.find('.js-overview-tab a').click + wait_for_requests + end + end + + describe 'user has no activities' do + include_context 'visit overview tab' + + it 'does not show any entries in the list of activities' do + page.within('.activities-block') do + expect(page).not_to have_selector('.event-item') + end + end + + it 'does not show a link to the activity list' do + expect(find('#js-overview .activities-block')).to have_selector('.js-view-all', visible: false) + end + end + + describe 'user has 3 activities' do + before do + 3.times { push_code_contribution } + end + + include_context 'visit overview tab' + + it 'display 3 entries in the list of activities' do + expect(find('#js-overview')).to have_selector('.event-item', count: 3) + end + end + + describe 'user has 10 activities' do + before do + 10.times { push_code_contribution } + end + + include_context 'visit overview tab' + + it 'displays 5 entries in the list of activities' do + expect(find('#js-overview')).to have_selector('.event-item', count: 5) + end + + it 'shows a link to the activity list' do + expect(find('#js-overview .activities-block')).to have_selector('.js-view-all', visible: true) + end + + it 'links to the activity tab' do + page.within('.activities-block') do + find('.js-view-all').click + wait_for_requests + expect(URI.parse(current_url).path).to eq("/users/#{user.username}/activity") + end + end + end + end + + describe 'projects section' do + shared_context 'visit overview tab' do + before do + visit user.username + page.find('.js-overview-tab a').click + wait_for_requests + end + end + + describe 'user has no personal projects' do + include_context 'visit overview tab' + + it 'it shows an empty project list with an info message' do + page.within('.projects-block') do + expect(page).to have_content('No projects found') + expect(page).not_to have_selector('.project-row') + end + end + + it 'does not show a link to the project list' do + expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: false) + end + end + + describe 'user has a personal project' do + let(:private_project) { create(:project, :private, namespace: user.namespace, creator: user) { |p| p.add_maintainer(user) } } + let!(:private_event) { create(:event, project: private_project, author: user) } + + include_context 'visit overview tab' + + it 'it shows one entry in the list of projects' do + page.within('.projects-block') do + expect(page).to have_selector('.project-row', count: 1) + end + end + + it 'shows a link to the project list' do + expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: true) + end + end + end +end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index bc07ab48c39..86379164cf0 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -8,6 +8,7 @@ describe 'User page' do visit(user_path(user)) page.within '.nav-links' do + expect(page).to have_link('Overview') expect(page).to have_link('Activity') expect(page).to have_link('Groups') expect(page).to have_link('Contributed projects') @@ -44,6 +45,7 @@ describe 'User page' do visit(user_path(user)) page.within '.nav-links' do + expect(page).to have_link('Overview') expect(page).to have_link('Activity') expect(page).to have_link('Groups') expect(page).to have_link('Contributed projects') diff --git a/spec/finders/group_labels_finder_spec.rb b/spec/finders/group_labels_finder_spec.rb index ef68fc105e4..7bdd312eff0 100644 --- a/spec/finders/group_labels_finder_spec.rb +++ b/spec/finders/group_labels_finder_spec.rb @@ -4,29 +4,38 @@ require 'spec_helper' describe GroupLabelsFinder, '#execute' do let!(:group) { create(:group) } + let!(:user) { create(:user) } let!(:label1) { create(:group_label, title: 'Foo', description: 'Lorem ipsum', group: group) } let!(:label2) { create(:group_label, title: 'Bar', description: 'Fusce consequat', group: group) } it 'returns all group labels sorted by name if no params' do - result = described_class.new(group).execute + result = described_class.new(user, group).execute expect(result.to_a).to match_array([label2, label1]) end it 'returns all group labels sorted by name desc' do - result = described_class.new(group, sort: 'name_desc').execute + result = described_class.new(user, group, sort: 'name_desc').execute expect(result.to_a).to match_array([label2, label1]) end - it 'returns group labels that march search' do - result = described_class.new(group, search: 'Foo').execute + it 'returns group labels that match search' do + result = described_class.new(user, group, search: 'Foo').execute expect(result.to_a).to match_array([label1]) end + it 'returns group labels user subscribed to' do + label2.subscribe(user) + + result = described_class.new(user, group, subscribed: 'true').execute + + expect(result.to_a).to match_array([label2]) + end + it 'returns second page of labels' do - result = described_class.new(group, page: '2').execute + result = described_class.new(user, group, page: '2').execute expect(result.to_a).to match_array([]) end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index d78451112ec..0689c843104 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -125,6 +125,14 @@ describe IssuesFinder do end end + context 'filtering by any milestone' do + let(:params) { { milestone_title: Milestone::Any.title } } + + it 'returns issues with any assigned milestone' do + expect(issues).to contain_exactly(issue1) + end + end + context 'filtering by upcoming milestone' do let(:params) { { milestone_title: Milestone::Upcoming.name } } diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index f5cec8e349a..9abc52aa664 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -210,5 +210,15 @@ describe LabelsFinder do expect(finder.execute).to eq [project_label_1] end end + + context 'filter by subscription' do + it 'returns labels user subscribed to' do + project_label_1.subscribe(user) + + finder = described_class.new(user, subscribed: 'true') + + expect(finder.execute).to eq [project_label_1] + end + end end end diff --git a/spec/finders/license_template_finder_spec.rb b/spec/finders/license_template_finder_spec.rb index a97903103c9..f6f40bf33cc 100644 --- a/spec/finders/license_template_finder_spec.rb +++ b/spec/finders/license_template_finder_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe LicenseTemplateFinder do describe '#execute' do - subject(:result) { described_class.new(params).execute } + subject(:result) { described_class.new(nil, params).execute } let(:categories) { categorised_licenses.keys } let(:categorised_licenses) { result.group_by(&:category) } @@ -31,7 +31,7 @@ describe LicenseTemplateFinder do it 'returns all licenses known by the Licensee gem' do from_licensee = Licensee::License.all.map { |l| l.key } - expect(result.map(&:id)).to match_array(from_licensee) + expect(result.map(&:key)).to match_array(from_licensee) end it 'correctly copies all attributes' do diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 33d01697c75..ff4c6b8dd42 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -3,21 +3,37 @@ require 'spec_helper' describe MergeRequestsFinder do include ProjectForksHelper + # We need to explicitly permit Gitaly N+1s because of the specs that use + # :request_store. Gitaly N+1 detection is only enabled when :request_store is, + # but we don't care about potential N+1s when we're just creating several + # projects in the setup phase. + def create_project_without_n_plus_1(*args) + Gitlab::GitalyClient.allow_n_plus_1_calls do + create(:project, :public, *args) + end + end + let(:user) { create :user } let(:user2) { create :user } let(:group) { create(:group) } let(:subgroup) { create(:group, parent: group) } - let(:project1) { create(:project, :public, group: group) } - let(:project2) { fork_project(project1, user) } + let(:project1) { create_project_without_n_plus_1(group: group) } + let(:project2) do + Gitlab::GitalyClient.allow_n_plus_1_calls do + fork_project(project1, user) + end + end let(:project3) do - p = fork_project(project1, user) - p.update!(archived: true) - p + Gitlab::GitalyClient.allow_n_plus_1_calls do + p = fork_project(project1, user) + p.update!(archived: true) + p + end end - let(:project4) { create(:project, :public, group: subgroup) } - let(:project5) { create(:project, :public, group: subgroup) } - let(:project6) { create(:project, :public, group: subgroup) } + let(:project4) { create_project_without_n_plus_1(group: subgroup) } + let(:project5) { create_project_without_n_plus_1(group: subgroup) } + let(:project6) { create_project_without_n_plus_1(group: subgroup) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } diff --git a/spec/finders/pending_todos_finder_spec.rb b/spec/finders/pending_todos_finder_spec.rb new file mode 100644 index 00000000000..32fad5e225f --- /dev/null +++ b/spec/finders/pending_todos_finder_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PendingTodosFinder do + let(:user) { create(:user) } + + describe '#execute' do + it 'returns only pending todos' do + create(:todo, :done, user: user) + + todo = create(:todo, :pending, user: user) + todos = described_class.new(user).execute + + expect(todos).to eq([todo]) + end + + it 'supports retrieving of todos for a specific project' do + project1 = create(:project) + project2 = create(:project) + + create(:todo, :pending, user: user, project: project2) + + todo = create(:todo, :pending, user: user, project: project1) + todos = described_class.new(user, project_id: project1.id).execute + + expect(todos).to eq([todo]) + end + + it 'supports retrieving of todos for a specific todo target' do + issue = create(:issue) + note = create(:note) + todo = create(:todo, :pending, user: user, target: issue) + + create(:todo, :pending, user: user, target: note) + + todos = described_class.new(user, target_id: issue.id).execute + + expect(todos).to eq([todo]) + end + + it 'supports retrieving of todos for a specific target type' do + issue = create(:issue) + note = create(:note) + todo = create(:todo, :pending, user: user, target: issue) + + create(:todo, :pending, user: user, target: note) + + todos = described_class.new(user, target_type: issue.class).execute + + expect(todos).to eq([todo]) + end + + it 'supports retrieving of todos for a specific commit ID' do + create(:todo, :pending, user: user, commit_id: '456') + + todo = create(:todo, :pending, user: user, commit_id: '123') + todos = described_class.new(user, commit_id: '123').execute + + expect(todos).to eq([todo]) + end + end +end diff --git a/spec/finders/template_finder_spec.rb b/spec/finders/template_finder_spec.rb index 1d399e8194f..114af9461e0 100644 --- a/spec/finders/template_finder_spec.rb +++ b/spec/finders/template_finder_spec.rb @@ -4,6 +4,8 @@ describe TemplateFinder do using RSpec::Parameterized::TableSyntax describe '#build' do + let(:project) { build_stubbed(:project) } + where(:type, :expected_class) do :dockerfiles | described_class :gitignores | described_class @@ -12,9 +14,10 @@ describe TemplateFinder do end with_them do - subject { described_class.build(type) } + subject(:finder) { described_class.build(type, project) } it { is_expected.to be_a(expected_class) } + it { expect(finder.project).to eq(project) } end end @@ -27,19 +30,19 @@ describe TemplateFinder do with_them do it 'returns all vendored templates when no name is specified' do - result = described_class.new(type).execute + result = described_class.new(type, nil).execute expect(result).to include(have_attributes(name: vendored_name)) end it 'returns only the specified vendored template when a name is specified' do - result = described_class.new(type, name: vendored_name).execute + result = described_class.new(type, nil, name: vendored_name).execute expect(result).to have_attributes(name: vendored_name) end it 'returns nil when an unknown name is specified' do - result = described_class.new(type, name: 'unknown').execute + result = described_class.new(type, nil, name: 'unknown').execute expect(result).to be_nil end diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index 7f7cfb2cb98..d4ed41d54f0 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -105,9 +105,24 @@ describe TodosFinder do todos = finder.new(user, { sort: 'priority' }).execute - puts todos.to_sql expect(todos).to eq([todo_3, todo_5, todo_4, todo_2, todo_1]) end end end + + describe '#any_for_target?' do + it 'returns true if there are any todos for the given target' do + todo = create(:todo, :pending) + finder = described_class.new(todo.user) + + expect(finder.any_for_target?(todo.target)).to eq(true) + end + + it 'returns false if there are no todos for the given target' do + issue = create(:issue) + finder = described_class.new(issue.author) + + expect(finder.any_for_target?(issue)).to eq(false) + end + end end diff --git a/spec/finders/users_with_pending_todos_finder_spec.rb b/spec/finders/users_with_pending_todos_finder_spec.rb new file mode 100644 index 00000000000..fa15355531c --- /dev/null +++ b/spec/finders/users_with_pending_todos_finder_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe UsersWithPendingTodosFinder do + describe '#execute' do + it 'returns the users for all pending todos of a target' do + issue = create(:issue) + note = create(:note) + todo = create(:todo, :pending, target: issue) + + create(:todo, :pending, target: note) + + users = described_class.new(issue).execute + + expect(users).to eq([todo.user]) + end + end +end diff --git a/spec/fixtures/api/schemas/entities/diff_viewer.json b/spec/fixtures/api/schemas/entities/diff_viewer.json new file mode 100644 index 00000000000..19780f49a88 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/diff_viewer.json @@ -0,0 +1,8 @@ +{ + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": ["string"] } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/entities/note_user_entity.json b/spec/fixtures/api/schemas/entities/note_user_entity.json new file mode 100644 index 00000000000..9b838054563 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/note_user_entity.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "required": [ + "id", + "state", + "avatar_url", + "path", + "name", + "username" + ], + "properties": { + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "string" }, + "path": { "type": "string" }, + "name": { "type": "string" }, + "username": { "type": "string" }, + "status_tooltip_html": { "$ref": "../types/nullable_string.json" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/job/deployment_status.json b/spec/fixtures/api/schemas/job/deployment_status.json index a90b8b35654..83b1899fdf3 100644 --- a/spec/fixtures/api/schemas/job/deployment_status.json +++ b/spec/fixtures/api/schemas/job/deployment_status.json @@ -2,7 +2,6 @@ "type": "object", "required": [ "status", - "icon", "environment" ], "properties": { @@ -20,7 +19,6 @@ { "type": "null" } ] }, - "icon": { "type": "string" }, "environment": { "$ref": "../environment.json" } }, "additionalProperties": false diff --git a/spec/fixtures/api/schemas/job/runners.json b/spec/fixtures/api/schemas/job/runners.json index bebb0c88652..646bfd3a82d 100644 --- a/spec/fixtures/api/schemas/job/runners.json +++ b/spec/fixtures/api/schemas/job/runners.json @@ -8,6 +8,5 @@ "online": { "type": "boolean" }, "available": { "type": "boolean" }, "settings_path": { "type": "string" } - }, - "additionalProperties": false + } } diff --git a/spec/fixtures/api/schemas/public_api/v4/license.json b/spec/fixtures/api/schemas/public_api/v4/license.json new file mode 100644 index 00000000000..38c8c3e9192 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/license.json @@ -0,0 +1,30 @@ +{ + "type": "object", + "required": [ + "key", + "name", + "nickname", + "popular", + "html_url", + "source_url", + "description", + "conditions", + "permissions", + "limitations", + "content" + ], + "properties": { + "key": { "type": "string" }, + "name": { "type": "string" }, + "nickname": { "type": ["null", "string"] }, + "popular": { "type": "boolean" }, + "html_url": { "type": ["null", "string"] }, + "source_url": { "type": ["null", "string"] }, + "description": { "type": ["null", "string"] }, + "conditions": { "type": "array", "items": { "type": "string" } }, + "permissions": { "type": "array", "items": { "type": "string" } }, + "limitations": { "type": "array", "items": { "type": "string" } }, + "content": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/template.json b/spec/fixtures/api/schemas/public_api/v4/template.json new file mode 100644 index 00000000000..38601aa6b45 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/template.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required": [ + "name", + "content" + ], + "properties": { + "name": { "type": "string" }, + "content": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/public_api/v4/template_list.json b/spec/fixtures/api/schemas/public_api/v4/template_list.json new file mode 100644 index 00000000000..2336dafb17b --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/template_list.json @@ -0,0 +1,15 @@ +{ + "type": "array", + "items": { + "type": "object", + "required": [ + "key", + "name" + ], + "properties": { + "key": { "type": "string" }, + "name": { "type": "string" } + }, + "additionalProperties": false + } +} diff --git a/spec/fixtures/ssh_host_example_key.pub b/spec/fixtures/ssh_host_example_key.pub index 6bac42b3ad0..d43315ddae8 100644 --- a/spec/fixtures/ssh_host_example_key.pub +++ b/spec/fixtures/ssh_host_example_key.pub @@ -1 +1 @@ -random content +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuRkAgwaap/pXThwCpjX8Wd5tR36Tqx3sW2sVVHs3UKB7kd+xNknw7e4qpuEATv56xHrhKm2+ye/JidTuQ/1EwFhjaz7I5wTslfVawQpeH1ZqAGmvdO/xTw+l7fgEFVlGVx9y0HV3m52y2C9yw82qmg+BohbTVgPtjjutpFc+CwLQxLTnTrRhZf5udQgz+YlwLv+Y0kDx6+DWWOl8N9+TWuGyFKBln79CyBgFcK5NFmF48kYn8W+r7rmawfw9XbuF1aa+6JF+6cNR1mCEonyrRLdXP+vWcxpLKYfejB0NmA1y+W9M/K53AcIHA5zlRQ49tFh0P22eh/Gl8JQ6yyuin foo@bar.mynet diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb index 89eecef096e..927153adc5b 100644 --- a/spec/graphql/types/permission_types/project_spec.rb +++ b/spec/graphql/types/permission_types/project_spec.rb @@ -10,7 +10,7 @@ describe Types::PermissionTypes::Project do :read_commit_status, :request_access, :create_pipeline, :create_pipeline_schedule, :create_merge_request_from, :create_wiki, :push_code, :create_deployment, :push_to_delete_protected_branch, :admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label, - :update_wiki, :destroy_wiki, :create_pages, :destroy_pages + :update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content ] expect(described_class).to have_graphql_fields(expected_permissions) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 1238cfbd1e7..4135f31e051 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -174,9 +174,7 @@ describe ApplicationHelper do it 'returns paths for autocomplete_sources_controller' do sources = helper.autocomplete_data_sources(project, noteable_type) - - expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands]) - + expect(sources.keys).to match_array([:members, :issues, :mergeRequests, :labels, :milestones, :commands, :snippets]) sources.keys.each do |key| expect(sources[key]).not_to be_nil end diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 54cb6d84109..091edf13cfe 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -250,71 +250,45 @@ describe('Api', () => { }); }); - describe('licenseText', () => { - it('fetches a license text', done => { - const licenseKey = "driver's license"; - const data = { unused: 'option' }; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/licenses/${licenseKey}`; + describe('issueTemplate', () => { + it('fetches an issue template', done => { + const namespace = 'some namespace'; + const project = 'some project'; + const templateKey = ' template #%?.key '; + const templateType = 'template type'; + const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent( + templateKey, + )}`; mock.onGet(expectedUrl).reply(200, 'test'); - Api.licenseText(licenseKey, data, response => { + Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => { expect(response).toBe('test'); done(); }); }); }); - describe('gitignoreText', () => { - it('fetches a gitignore text', done => { - const gitignoreKey = 'ignore git'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitignores/${gitignoreKey}`; - mock.onGet(expectedUrl).reply(200, 'test'); - - Api.gitignoreText(gitignoreKey, response => { - expect(response).toBe('test'); - done(); - }); - }); - }); + describe('projectTemplates', () => { + it('fetches a list of templates', done => { + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses`; - describe('gitlabCiYml', () => { - it('fetches a .gitlab-ci.yml', done => { - const gitlabCiYmlKey = 'Y CI ML'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/gitlab_ci_ymls/${gitlabCiYmlKey}`; mock.onGet(expectedUrl).reply(200, 'test'); - Api.gitlabCiYml(gitlabCiYmlKey, response => { + Api.projectTemplates('gitlab-org/gitlab-ce', 'licenses', {}, response => { expect(response).toBe('test'); done(); }); }); }); - describe('dockerfileYml', () => { - it('fetches a Dockerfile', done => { - const dockerfileYmlKey = 'a giant whale'; - const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/templates/dockerfiles/${dockerfileYmlKey}`; - mock.onGet(expectedUrl).reply(200, 'test'); - - Api.dockerfileYml(dockerfileYmlKey, response => { - expect(response).toBe('test'); - done(); - }); - }); - }); + describe('projectTemplate', () => { + it('fetches a single template', done => { + const data = { unused: 'option' }; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/gitlab-org%2Fgitlab-ce/templates/licenses/test%20license`; - describe('issueTemplate', () => { - it('fetches an issue template', done => { - const namespace = 'some namespace'; - const project = 'some project'; - const templateKey = ' template #%?.key '; - const templateType = 'template type'; - const expectedUrl = `${dummyUrlRoot}/${namespace}/${project}/templates/${templateType}/${encodeURIComponent( - templateKey, - )}`; mock.onGet(expectedUrl).reply(200, 'test'); - Api.issueTemplate(namespace, project, templateKey, templateType, (error, response) => { + Api.projectTemplate('gitlab-org/gitlab-ce', 'licenses', 'test license', data, response => { expect(response).toBe('test'); done(); }); diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index ada26b37f4a..0c5d68990d5 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, one-var, one-var-declaration-per-line, no-unused-expressions, no-unused-vars, prefer-template, max-len */ +/* eslint-disable no-var, one-var, no-unused-expressions, no-unused-vars, prefer-template */ import $ from 'jquery'; import Cookies from 'js-cookie'; diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 1ee6f4cf680..ed43ce9029e 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, no-unused-vars */ +/* eslint-disable no-unused-vars */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index db68096e3bd..0beb5782283 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,4 +1,3 @@ -/* eslint-disable comma-dangle */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index ac8bbb8f2a8..4232e0fc221 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -1,4 +1,3 @@ -/* eslint-disable comma-dangle */ /* global List */ /* global ListIssue */ diff --git a/spec/javascripts/diff_comments_store_spec.js b/spec/javascripts/diff_comments_store_spec.js index d6fc6b56b82..c6f2e66cebd 100644 --- a/spec/javascripts/diff_comments_store_spec.js +++ b/spec/javascripts/diff_comments_store_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */ +/* eslint-disable jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown */ /* global CommentsStore */ import '~/diff_notes/models/discussion'; diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js index 627fb8c490a..8c3376c0eb3 100644 --- a/spec/javascripts/diffs/components/commit_item_spec.js +++ b/spec/javascripts/diffs/components/commit_item_spec.js @@ -9,6 +9,8 @@ import getDiffWithCommit from '../mock_data/diff_with_commit'; const TEST_AUTHOR_NAME = 'test'; const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com'; const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=36`; +const TEST_SIGNATURE_HTML = '<a>Legit commit</a>'; +const TEST_PIPELINE_STATUS_PATH = `${TEST_HOST}/pipeline/status`; const getTitleElement = vm => vm.$el.querySelector('.commit-row-message.item-title'); const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description'); @@ -16,6 +18,7 @@ const getDescExpandElement = vm => vm.$el.querySelector('.commit-content .text-e const getShaElement = vm => vm.$el.querySelector('.commit-sha-group'); const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link'); const getCommitterElement = vm => vm.$el.querySelector('.commiter'); +const getCommitActionsElement = vm => vm.$el.querySelector('.commit-actions'); describe('diffs/components/commit_widget', () => { const Component = Vue.extend(CommitItem); @@ -125,4 +128,36 @@ describe('diffs/components/commit_widget', () => { expect(nameElement).toHaveText(TEST_AUTHOR_NAME); }); }); + + describe('with signature', () => { + beforeEach(done => { + vm.commit.signatureHtml = TEST_SIGNATURE_HTML; + + vm.$nextTick() + .then(done) + .catch(done.fail); + }); + + it('renders signature html', () => { + const actionsElement = getCommitActionsElement(vm); + + expect(actionsElement).toContainHtml(TEST_SIGNATURE_HTML); + }); + }); + + describe('with pipeline status', () => { + beforeEach(done => { + vm.commit.pipelineStatusPath = TEST_PIPELINE_STATUS_PATH; + + vm.$nextTick() + .then(done) + .catch(done.fail); + }); + + it('renders pipeline status', () => { + const actionsElement = getCommitActionsElement(vm); + + expect(actionsElement).toContainElement('.ci-status-link'); + }); + }); }); diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js index dea600a783a..67f7b569f47 100644 --- a/spec/javascripts/diffs/components/diff_content_spec.js +++ b/spec/javascripts/diffs/components/diff_content_spec.js @@ -8,13 +8,12 @@ import diffFileMockData from '../mock_data/diff_file'; describe('DiffContent', () => { const Component = Vue.extend(DiffContentComponent); let vm; - const getDiffFileMock = () => Object.assign({}, diffFileMockData); beforeEach(() => { vm = mountComponentWithStore(Component, { store, props: { - diffFile: getDiffFileMock(), + diffFile: JSON.parse(JSON.stringify(diffFileMockData)), }, }); }); @@ -43,7 +42,7 @@ describe('DiffContent', () => { describe('Non-Text diffs', () => { beforeEach(() => { - vm.diffFile.text = false; + vm.diffFile.viewer.name = 'image'; }); describe('image diff', () => { diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js index 2a52cd2b179..13859f43e98 100644 --- a/spec/javascripts/diffs/components/diff_file_spec.js +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -6,11 +6,10 @@ import diffFileMockData from '../mock_data/diff_file'; describe('DiffFile', () => { let vm; - const getDiffFileMock = () => Object.assign({}, diffFileMockData); beforeEach(() => { vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, { - file: getDiffFileMock(), + file: JSON.parse(JSON.stringify(diffFileMockData)), canCurrentUserFork: false, }).$mount(); }); @@ -18,7 +17,7 @@ describe('DiffFile', () => { describe('template', () => { it('should render component with file header, file content components', () => { const el = vm.$el; - const { fileHash, filePath } = diffFileMockData; + const { fileHash, filePath } = vm.file; expect(el.id).toEqual(fileHash); expect(el.classList.contains('diff-file')).toEqual(true); diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index b29a22da7c2..0ad214ea4a4 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -2,15 +2,13 @@ export default { id: '6b232e05bea388c6b043ccc243ba505faac04ea8', reply_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', position: { - formatter: { - old_line: null, - new_line: 2, - old_path: 'CHANGELOG', - new_path: 'CHANGELOG', - base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', - start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', - head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', - }, + old_line: null, + new_line: 2, + old_path: 'CHANGELOG', + new_path: 'CHANGELOG', + base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', + start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', + head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', }, line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', expanded: true, diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js index 2aa2f8f3528..d7bc0dbe431 100644 --- a/spec/javascripts/diffs/mock_data/diff_file.js +++ b/spec/javascripts/diffs/mock_data/diff_file.js @@ -23,6 +23,9 @@ export default { aMode: '100644', bMode: '100644', text: true, + viewer: { + name: 'text', + }, addedLines: 2, removedLines: 0, diffRefs: { diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index aacad7a479b..85c1926fcb1 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -148,12 +148,8 @@ describe('DiffsStoreActions', () => { }, fileHash: 'ABC', resolvable: true, - position: { - formatter: diffPosition, - }, - original_position: { - formatter: diffPosition, - }, + position: diffPosition, + original_position: diffPosition, }; const discussions = reduceDiscussionsToLineCodes([singleDiscussion]); @@ -178,6 +174,7 @@ describe('DiffsStoreActions', () => { oldLine: 5, oldPath: 'file2', lineCode: 'ABC_1_1', + positionType: 'text', }, }, }, diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index cc8d5dc4bac..0b712055956 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -194,24 +194,16 @@ describe('DiffsStoreMutations', () => { line_code: 'ABC_1', diff_discussion: true, resolvable: true, - original_position: { - formatter: diffPosition, - }, - position: { - formatter: diffPosition, - }, + original_position: diffPosition, + position: diffPosition, }, { id: 2, line_code: 'ABC_1', diff_discussion: true, resolvable: true, - original_position: { - formatter: diffPosition, - }, - position: { - formatter: diffPosition, - }, + original_position: diffPosition, + position: diffPosition, }, ]; diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index e660f94c72e..257270a91ec 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -333,20 +333,12 @@ describe('DiffsStoreUtils', () => { const discussions = { upToDateDiscussion1: { - original_position: { - formatter: diffPosition, - }, - position: { - formatter: wrongDiffPosition, - }, + original_position: diffPosition, + position: wrongDiffPosition, }, outDatedDiscussion1: { - original_position: { - formatter: wrongDiffPosition, - }, - position: { - formatter: wrongDiffPosition, - }, + original_position: wrongDiffPosition, + position: wrongDiffPosition, }, }; diff --git a/spec/javascripts/droplab/plugins/input_setter_spec.js b/spec/javascripts/droplab/plugins/input_setter_spec.js index bd625f4ae80..1988811a305 100644 --- a/spec/javascripts/droplab/plugins/input_setter_spec.js +++ b/spec/javascripts/droplab/plugins/input_setter_spec.js @@ -1,5 +1,3 @@ -/* eslint-disable */ - import InputSetter from '~/droplab/plugins/input_setter'; describe('InputSetter', function () { diff --git a/spec/javascripts/gfm_auto_complete_spec.js b/spec/javascripts/gfm_auto_complete_spec.js index 4f9cacf2724..b57c4943c01 100644 --- a/spec/javascripts/gfm_auto_complete_spec.js +++ b/spec/javascripts/gfm_auto_complete_spec.js @@ -103,7 +103,7 @@ describe('GfmAutoComplete', function () { gfmAutoCompleteCallbacks.matcher.call(context, flag, subtext) ); - const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%']; + const flagsUseDefaultMatcher = ['@', '#', '!', '~', '%', '$']; const otherFlags = ['/', ':']; const flags = flagsUseDefaultMatcher.concat(otherFlags); diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index af58dff7da7..25b819543da 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, no-param-reassign */ +/* eslint-disable no-param-reassign */ import $ from 'jquery'; import GLDropdown from '~/gl_dropdown'; diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js index d8a8c8cc260..4a4d6969e86 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var */ +/* eslint-disable jasmine/no-suite-dupes, vars-on-top, no-var */ + import { scaleLinear, scaleTime } from 'd3-scale'; import { timeParse } from 'd3-time-format'; import { ContributorsGraph, ContributorsMasterGraph } from '~/pages/projects/graphs/show/stat_graph_contributors_graph'; diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js index 22a9afe1a9d..02d1ca1cc3b 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable quotes, no-var, camelcase, object-property-newline, comma-dangle, max-len, vars-on-top, quote-props */ +/* eslint-disable no-var, camelcase, vars-on-top */ import ContributorsStatGraphUtil from '~/pages/projects/graphs/show/stat_graph_contributors_util'; diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index e12419b835d..62c71e00334 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle */ +/* eslint-disable one-var, no-use-before-define */ import $ from 'jquery'; import MockAdapter from 'axios-mock-adapter'; diff --git a/spec/javascripts/jobs/components/commit_block_spec.js b/spec/javascripts/jobs/components/commit_block_spec.js index 61ee993f46a..0bcc4ff940f 100644 --- a/spec/javascripts/jobs/components/commit_block_spec.js +++ b/spec/javascripts/jobs/components/commit_block_spec.js @@ -56,7 +56,7 @@ describe('Commit block', () => { props.mergeRequest.path, ); expect(vm.$el.querySelector('.js-link-commit').textContent.trim()).toEqual( - props.mergeRequest.iid, + `!${props.mergeRequest.iid}`, ); }); }); diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js index 872cc1e3864..73488eaab9b 100644 --- a/spec/javascripts/jobs/components/empty_state_spec.js +++ b/spec/javascripts/jobs/components/empty_state_spec.js @@ -67,7 +67,7 @@ describe('Empty State', () => { content, action: { path: 'runner', - title: 'Check runner', + button_title: 'Check runner', method: 'post', }, }); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index c31fa6f9887..e02eb9723fe 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -37,6 +37,7 @@ describe('Job App ', () => { available: false, }, tags: ['docker'], + has_trace: true, }; const props = { @@ -182,4 +183,142 @@ describe('Job App ', () => { expect(vm.$el.querySelector('.js-job-stuck')).toBeNull(); }); }); + + describe('environments block', () => { + it('renders environment block when job has environment', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + deployment_status: { + environment: { + environment_path: '/path', + name: 'foo', + }, + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull(); + }); + + it('does not render environment block when job has environment', () => { + store.dispatch('receiveJobSuccess', job); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-environment')).toBeNull(); + }); + }); + + describe('erased block', () => { + it('renders erased block when `erased` is true', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + erased: true, + erased_by: { + username: 'root', + web_url: 'gitlab.com/root', + }, + erased_at: '2016-11-07T11:11:16.525Z', + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-erased')).not.toBeNull(); + }); + + it('does not render erased block when `erased` is false', () => { + store.dispatch('receiveJobSuccess', Object.assign({}, job, { erased: false })); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-erased')).toBeNull(); + }); + }); + + describe('empty states block', () => { + it('renders empty state when job does not have trace and is not running', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + has_trace: false, + status: { + group: 'pending', + icon: 'status_pending', + label: 'pending', + text: 'pending', + details_path: 'path', + illustration: { + image: 'path', + size: '340', + title: 'Empty State', + content: 'This is an empty state', + }, + action: { + button_title: 'Retry job', + method: 'post', + path: '/path', + }, + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); + }); + + it('does not render empty state when job does not have trace but it is running', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + has_trace: false, + status: { + group: 'running', + icon: 'status_running', + label: 'running', + text: 'running', + details_path: 'path', + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }); + + it('does not render empty state when job has trace but it is not running', () => { + store.dispatch('receiveJobSuccess', Object.assign({}, job, { has_trace: true })); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }); + }); }); diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js index 63ef4135d83..160b2f4b34a 100644 --- a/spec/javascripts/jobs/store/getters_spec.js +++ b/spec/javascripts/jobs/store/getters_spec.js @@ -99,12 +99,14 @@ describe('Job Store Getters', () => { expect(getters.hasEnvironment(localState)).toEqual(false); }); }); + describe('with an empty object for `deployment_status`', () => { it('returns false', () => { localState.job.deployment_status = {}; expect(getters.hasEnvironment(localState)).toEqual(false); }); }); + describe('when `deployment_status` is defined and not empty', () => { it('returns true', () => { localState.job.deployment_status = { @@ -118,4 +120,94 @@ describe('Job Store Getters', () => { }); }); }); + + describe('hasTrace', () => { + describe('when has_trace is true', () => { + it('returns true', () => { + localState.job.has_trace = true; + localState.job.status = {}; + + expect(getters.hasTrace(localState)).toEqual(true); + }); + }); + + describe('when job is running', () => { + it('returns true', () => { + localState.job.has_trace = false; + localState.job.status = { group: 'running' }; + + expect(getters.hasTrace(localState)).toEqual(true); + }); + }); + + describe('when has_trace is false and job is not running', () => { + it('returns false', () => { + localState.job.has_trace = false; + localState.job.status = { group: 'pending' }; + + expect(getters.hasTrace(localState)).toEqual(false); + }); + }); + }); + + describe('emptyStateIllustration', () => { + describe('with defined illustration', () => { + it('returns the state illustration object', () => { + localState.job.status = { + illustration: { + path: 'foo', + }, + }; + + expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' }); + }); + }); + + describe('when illustration is not defined', () => { + it('returns an empty object', () => { + expect(getters.emptyStateIllustration(localState)).toEqual({}); + }); + }); + }); + + describe('isJobStuck', () => { + describe('when job is pending and runners are not available', () => { + it('returns true', () => { + localState.job.status = { + group: 'pending', + }; + localState.job.runners = { + available: false, + }; + + expect(getters.isJobStuck(localState)).toEqual(true); + }); + }); + + describe('when job is not pending', () => { + it('returns false', () => { + localState.job.status = { + group: 'running', + }; + localState.job.runners = { + available: false, + }; + + expect(getters.isJobStuck(localState)).toEqual(false); + }); + }); + + describe('when runners are available', () => { + it('returns false', () => { + localState.job.status = { + group: 'pending', + }; + localState.job.runners = { + available: true, + }; + + expect(getters.isJobStuck(localState)).toEqual(false); + }); + }); + }); }); diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index 8cf0017f4d8..c32ecb17e89 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, quotes, prefer-template, no-else-return, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, no-underscore-dangle, max-len */ +/* eslint-disable no-var, prefer-template, no-else-return, dot-notation, no-return-assign, no-new, one-var, no-underscore-dangle */ import $ from 'jquery'; import LineHighlighter from '~/line_highlighter'; diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index f0d53b2d8d7..732c37a24bf 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -4,30 +4,32 @@ import Dashboard from '~/monitoring/components/dashboard.vue'; import axios from '~/lib/utils/axios_utils'; import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data'; +const propsData = { + hasMetrics: false, + documentationPath: '/path/to/docs', + settingsPath: '/path/to/settings', + clustersPath: '/path/to/clusters', + tagsPath: '/path/to/tags', + projectPath: '/path/to/project', + metricsEndpoint: mockApiEndpoint, + deploymentEndpoint: null, + emptyGettingStartedSvgPath: '/path/to/getting-started.svg', + emptyLoadingSvgPath: '/path/to/loading.svg', + emptyNoDataSvgPath: '/path/to/no-data.svg', + emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', + environmentsEndpoint: '/root/hello-prometheus/environments/35', + currentEnvironmentName: 'production', +}; + +export default propsData; + describe('Dashboard', () => { let DashboardComponent; - const propsData = { - hasMetrics: false, - documentationPath: '/path/to/docs', - settingsPath: '/path/to/settings', - clustersPath: '/path/to/clusters', - tagsPath: '/path/to/tags', - projectPath: '/path/to/project', - metricsEndpoint: mockApiEndpoint, - deploymentEndpoint: null, - emptyGettingStartedSvgPath: '/path/to/getting-started.svg', - emptyLoadingSvgPath: '/path/to/loading.svg', - emptyNoDataSvgPath: '/path/to/no-data.svg', - emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', - environmentsEndpoint: '/root/hello-prometheus/environments/35', - currentEnvironmentName: 'production', - }; - beforeEach(() => { setFixtures(` <div class="prometheus-graphs"></div> - <div class="nav-sidebar"></div> + <div class="nav-sidebar"></div> `); DashboardComponent = Vue.extend(Dashboard); }); diff --git a/spec/javascripts/monitoring/graph/legend_spec.js b/spec/javascripts/monitoring/graph/legend_spec.js index abcc51aa077..9209e77dcf4 100644 --- a/spec/javascripts/monitoring/graph/legend_spec.js +++ b/spec/javascripts/monitoring/graph/legend_spec.js @@ -8,7 +8,7 @@ const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeri const defaultValuesComponent = {}; -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); defaultValuesComponent.timeSeries = timeSeries; diff --git a/spec/javascripts/monitoring/graph/track_info_spec.js b/spec/javascripts/monitoring/graph/track_info_spec.js index d3121d553f9..ce93ae28842 100644 --- a/spec/javascripts/monitoring/graph/track_info_spec.js +++ b/spec/javascripts/monitoring/graph/track_info_spec.js @@ -5,7 +5,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data'; const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); describe('TrackInfo component', () => { let vm; diff --git a/spec/javascripts/monitoring/graph/track_line_spec.js b/spec/javascripts/monitoring/graph/track_line_spec.js index 27602a861eb..2a4f89ddf6e 100644 --- a/spec/javascripts/monitoring/graph/track_line_spec.js +++ b/spec/javascripts/monitoring/graph/track_line_spec.js @@ -5,7 +5,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data'; const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); describe('TrackLine component', () => { let vm; diff --git a/spec/javascripts/monitoring/graph_path_spec.js b/spec/javascripts/monitoring/graph_path_spec.js index 2515e2ad897..5f270c5cfe9 100644 --- a/spec/javascripts/monitoring/graph_path_spec.js +++ b/spec/javascripts/monitoring/graph_path_spec.js @@ -13,7 +13,7 @@ const createComponent = (propsData) => { const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); const firstTimeSeries = timeSeries[0]; describe('Monitoring Paths', () => { diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index e4c98a3bcb5..6c833b17f98 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -8,6 +8,7 @@ export const metricsGroupsAPIResponse = { priority: 1, metrics: [ { + id: 5, title: 'Memory usage', weight: 1, queries: [ diff --git a/spec/javascripts/monitoring/utils/multiple_time_series_spec.js b/spec/javascripts/monitoring/utils/multiple_time_series_spec.js index 99584c75287..8937b7d9680 100644 --- a/spec/javascripts/monitoring/utils/multiple_time_series_spec.js +++ b/spec/javascripts/monitoring/utils/multiple_time_series_spec.js @@ -2,7 +2,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data'; const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); const firstTimeSeries = timeSeries[0]; describe('Multiple time series', () => { diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 122e5bc58b2..e52ac686435 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */ +/* eslint-disable one-var, no-var, no-return-assign */ import $ from 'jquery'; import NewBranchForm from '~/new_branch_form'; diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 1f030e5af28..9a0e7f34a9c 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -1177,10 +1177,8 @@ export const discussion1 = { file_path: 'about.md', }, position: { - formatter: { - new_line: 50, - old_line: null, - }, + new_line: 50, + old_line: null, }, notes: [ { @@ -1197,10 +1195,8 @@ export const resolvedDiscussion1 = { file_path: 'about.md', }, position: { - formatter: { - new_line: 50, - old_line: null, - }, + new_line: 50, + old_line: null, }, notes: [ { @@ -1217,10 +1213,8 @@ export const discussion2 = { file_path: 'README.md', }, position: { - formatter: { - new_line: null, - old_line: 20, - }, + new_line: null, + old_line: 20, }, notes: [ { @@ -1237,10 +1231,8 @@ export const discussion3 = { file_path: 'README.md', }, position: { - formatter: { - new_line: 21, - old_line: null, - }, + new_line: 21, + old_line: null, }, notes: [ { diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js index 72fb0a8f9ef..0566bc55693 100644 --- a/spec/javascripts/pipelines/pipelines_actions_spec.js +++ b/spec/javascripts/pipelines/pipelines_actions_spec.js @@ -1,46 +1,98 @@ import Vue from 'vue'; -import pipelinesActionsComp from '~/pipelines/components/pipelines_actions.vue'; +import eventHub from '~/pipelines/event_hub'; +import PipelinesActions from '~/pipelines/components/pipelines_actions.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { TEST_HOST } from 'spec/test_constants'; describe('Pipelines Actions dropdown', () => { - let component; - let actions; - let ActionsComponent; + const Component = Vue.extend(PipelinesActions); + let vm; - beforeEach(() => { - ActionsComponent = Vue.extend(pipelinesActionsComp); + afterEach(() => { + vm.$destroy(); + }); - actions = [ + describe('manual actions', () => { + const actions = [ { name: 'stop_review', - path: '/root/review-app/builds/1893/play', + path: `${TEST_HOST}/root/review-app/builds/1893/play`, }, { name: 'foo', - path: '#', + path: `${TEST_HOST}/disabled/pipeline/action`, playable: false, }, ]; - component = new ActionsComponent({ - propsData: { - actions, - }, - }).$mount(); - }); + beforeEach(() => { + vm = mountComponent(Component, { actions }); + }); - it('should render a dropdown with the provided actions', () => { - expect( - component.$el.querySelectorAll('.dropdown-menu li').length, - ).toEqual(actions.length); + it('renders a dropdown with the provided actions', () => { + const dropdownItems = vm.$el.querySelectorAll('.dropdown-menu li'); + expect(dropdownItems.length).toEqual(actions.length); + }); + + it("renders a disabled action when it's not playable", () => { + const dropdownItem = vm.$el.querySelector('.dropdown-menu li:last-child button'); + expect(dropdownItem).toBeDisabled(); + }); }); - it('should render a disabled action when it\'s not playable', () => { - expect( - component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'), - ).toEqual('disabled'); + describe('scheduled jobs', () => { + const scheduledJobAction = { + name: 'scheduled action', + path: `${TEST_HOST}/scheduled/job/action`, + playable: true, + scheduled_at: '2063-04-05T00:42:00Z', + }; + const expiredJobAction = { + name: 'expired action', + path: `${TEST_HOST}/expired/job/action`, + playable: true, + scheduled_at: '2018-10-05T08:23:00Z', + }; + const findDropdownItem = action => { + const buttons = vm.$el.querySelectorAll('.dropdown-menu li button'); + return Array.prototype.find.call(buttons, element => + element.innerText.trim().startsWith(action.name), + ); + }; + + beforeEach(() => { + spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime()); + vm = mountComponent(Component, { actions: [scheduledJobAction, expiredJobAction] }); + }); + + it('emits postAction event after confirming', () => { + const emitSpy = jasmine.createSpy('emit'); + eventHub.$on('postAction', emitSpy); + spyOn(window, 'confirm').and.callFake(() => true); + + findDropdownItem(scheduledJobAction).click(); + + expect(window.confirm).toHaveBeenCalled(); + expect(emitSpy).toHaveBeenCalledWith(scheduledJobAction.path); + }); + + it('does not emit postAction event if confirmation is cancelled', () => { + const emitSpy = jasmine.createSpy('emit'); + eventHub.$on('postAction', emitSpy); + spyOn(window, 'confirm').and.callFake(() => false); + + findDropdownItem(scheduledJobAction).click(); + + expect(window.confirm).toHaveBeenCalled(); + expect(emitSpy).not.toHaveBeenCalled(); + }); + + it('displays the remaining time in the dropdown', () => { + expect(findDropdownItem(scheduledJobAction)).toContainText('24:00:00'); + }); - expect( - component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'), - ).toEqual(true); + it('displays 00:00:00 for expired jobs in the dropdown', () => { + expect(findDropdownItem(expiredJobAction)).toContainText('00:00:00'); + }); }); }); diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js index 03ffc122795..42795f5c134 100644 --- a/spec/javascripts/pipelines/pipelines_table_row_spec.js +++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js @@ -158,8 +158,13 @@ describe('Pipelines Table Row', () => { }); describe('actions column', () => { + const scheduledJobAction = { + name: 'some scheduled job', + }; + beforeEach(() => { const withActions = Object.assign({}, pipeline); + withActions.details.scheduled_actions = [scheduledJobAction]; withActions.flags.cancelable = true; withActions.flags.retryable = true; withActions.cancel_path = '/cancel'; @@ -171,6 +176,8 @@ describe('Pipelines Table Row', () => { it('should render the provided actions', () => { expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull(); expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull(); + const dropdownMenu = component.$el.querySelectorAll('.dropdown-menu'); + expect(dropdownMenu).toContainText(scheduledJobAction.name); }); it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => { diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index c7190ea9960..f9395eedfea 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, one-var, one-var-declaration-per-line, no-return-assign, vars-on-top, jasmine/no-unsafe-spy, max-len */ +/* eslint-disable no-var, one-var, no-return-assign, vars-on-top, jasmine/no-unsafe-spy */ import $ from 'jquery'; import MockAdapter from 'axios-mock-adapter'; diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 646d843162c..b96023a33c4 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, object-shorthand, prefer-template, vars-on-top, max-len */ +/* eslint-disable no-var, one-var, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, object-shorthand, prefer-template, vars-on-top */ import $ from 'jquery'; import '~/gl_dropdown'; diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js index 1c3dac3584e..af3a5d58ba7 100644 --- a/spec/javascripts/syntax_highlight_spec.js +++ b/spec/javascripts/syntax_highlight_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, no-return-assign, quotes */ +/* eslint-disable no-var, no-return-assign */ import $ from 'jquery'; import syntaxHighlight from '~/syntax_highlight'; diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 012a1cefbbf..a8692be3546 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,4 +1,4 @@ -/* eslint-disable wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign */ +/* eslint-disable no-unused-expressions, no-return-assign, no-param-reassign */ export default class MockU2FDevice { constructor() { diff --git a/spec/javascripts/vue_shared/components/markdown/header_spec.js b/spec/javascripts/vue_shared/components/markdown/header_spec.js index bc934afe7a4..a4681617e66 100644 --- a/spec/javascripts/vue_shared/components/markdown/header_spec.js +++ b/spec/javascripts/vue_shared/components/markdown/header_spec.js @@ -17,8 +17,13 @@ describe('Markdown field header component', () => { Vue.nextTick(done); }); - it('renders markdown buttons', () => { - expect(vm.$el.querySelectorAll('.js-md').length).toBe(8); + it('renders markdown header buttons', () => { + const buttons = ['Add bold text', 'Add italic text', 'Insert a quote', 'Insert code', 'Add a link', 'Add a bullet list', 'Add a numbered list', 'Add a task list', 'Add a table', 'Go full screen']; + const elements = vm.$el.querySelectorAll('.toolbar-btn'); + + elements.forEach((buttonEl, index) => { + expect(buttonEl.getAttribute('data-original-title')).toBe(buttons[index]); + }); }); it('renders `write` link as active when previewMarkdown is false', () => { @@ -69,4 +74,8 @@ describe('Markdown field header component', () => { done(); }); }); + + it('renders markdown table template', () => { + expect(vm.mdTable).toEqual('| header | header |\n| ------ | ------ |\n| cell | cell |\n| cell | cell |'); + }); }); diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb index aadfe7637dd..ba995e16be7 100644 --- a/spec/lib/banzai/cross_project_reference_spec.rb +++ b/spec/lib/banzai/cross_project_reference_spec.rb @@ -1,16 +1,21 @@ require 'spec_helper' describe Banzai::CrossProjectReference do - include described_class + let(:including_class) { Class.new.include(described_class).new } + + before do + allow(including_class).to receive(:context).and_return({}) + allow(including_class).to receive(:parent_from_ref).and_call_original + end describe '#parent_from_ref' do context 'when no project was referenced' do it 'returns the project from context' do project = double - allow(self).to receive(:context).and_return({ project: project }) + allow(including_class).to receive(:context).and_return({ project: project }) - expect(parent_from_ref(nil)).to eq project + expect(including_class.parent_from_ref(nil)).to eq project end end @@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do it 'returns the group from context' do group = double - allow(self).to receive(:context).and_return({ group: group }) + allow(including_class).to receive(:context).and_return({ group: group }) - expect(parent_from_ref(nil)).to eq group + expect(including_class.parent_from_ref(nil)).to eq group end end context 'when referenced project does not exist' do it 'returns nil' do - expect(parent_from_ref('invalid/reference')).to be_nil + expect(including_class.parent_from_ref('invalid/reference')).to be_nil end end @@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do expect(Project).to receive(:find_by_full_path) .with('cross/reference').and_return(project2) - expect(parent_from_ref('cross/reference')).to eq project2 + expect(including_class.parent_from_ref('cross/reference')).to eq project2 end end end diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index e1af5a15371..cbff2fdab14 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do exp = act = "See #{commit1.id.reverse}...#{commit2.id}" allow(project.repository).to receive(:commit).with(commit1.id.reverse) + allow(project.repository).to receive(:commit).with(commit2.id) expect(reference_filter(act).to_html).to eq exp end diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb index cca53a8b9b9..f558dea209f 100644 --- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb @@ -120,4 +120,22 @@ describe Banzai::ReferenceParser::CommitParser do expect(subject.find_commits(project, %w{123})).to eq([]) end end + + context 'when checking commits on another projects' do + let(:control_links) do + [commit_link] + end + + let(:actual_links) do + control_links + [commit_link, commit_link] + end + + def commit_link + project = create(:project, :repository, :public) + + Nokogiri::HTML.fragment(%Q{<a data-commit="#{project.commit.id}" data-project="#{project.id}"></a>}).children[0] + end + + it_behaves_like 'no project N+1 queries' + end end diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb new file mode 100644 index 00000000000..ab401108c84 --- /dev/null +++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb @@ -0,0 +1,107 @@ +require 'spec_helper' + +describe Gitlab::Ci::Build::Policy::Changes do + set(:project) { create(:project) } + + describe '#satisfied_by?' do + describe 'paths matching matching' do + let(:pipeline) do + build(:ci_empty_pipeline, project: project, + ref: 'master', + source: :push, + sha: '1234abcd', + before_sha: '0123aabb') + end + + let(:ci_build) do + build(:ci_build, pipeline: pipeline, project: project, ref: 'master') + end + + let(:seed) { double('build seed', to_resource: ci_build) } + + before do + allow(pipeline).to receive(:modified_paths) do + %w[some/modified/ruby/file.rb some/other_file.txt some/.dir/file] + end + end + + it 'is satisfied by matching literal path' do + policy = described_class.new(%w[some/other_file.txt]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is satisfied by matching simple pattern' do + policy = described_class.new(%w[some/*.txt]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is satisfied by matching recusive pattern' do + policy = described_class.new(%w[some/**/*.rb]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is satisfied by matching a pattern with a dot' do + policy = described_class.new(%w[some/*/file]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied when pattern does not match path' do + policy = described_class.new(%w[some/*.rb]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied when pattern does not match' do + policy = described_class.new(%w[invalid/*.md]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + context 'when pipelines does not run for a branch update' do + before do + pipeline.before_sha = Gitlab::Git::BLANK_SHA + end + + it 'is always satisfied' do + policy = described_class.new(%w[invalid/*]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + end + end + + describe 'gitaly integration' do + set(:project) { create(:project, :repository) } + + let(:pipeline) do + create(:ci_empty_pipeline, project: project, + ref: 'master', + source: :push, + sha: '498214d', + before_sha: '281d3a7') + end + + let(:build) do + create(:ci_build, pipeline: pipeline, project: project, ref: 'master') + end + + let(:seed) { double('build seed', to_resource: build) } + + it 'is satisfied by changes introduced by a push' do + policy = described_class.new(['with space/*.md']) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied by changes that are not in the push' do + policy = described_class.new(%w[files/js/commit.js]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index d745c4ca2ad..1169938b80c 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -44,9 +44,7 @@ describe Gitlab::Ci::Config::Entry::Job do context 'when start_in is specified' do let(:config) { { script: 'echo', when: 'delayed', start_in: '1 day' } } - it 'returns error about invalid type' do - expect(entry).to be_valid - end + it { expect(entry).to be_valid } end end end @@ -158,7 +156,7 @@ describe Gitlab::Ci::Config::Entry::Job do end end - context 'when start_in is not formateed ad a duration' do + context 'when start_in is not formatted as a duration' do let(:config) { { when: 'delayed', start_in: 'test' } } it 'returns error about invalid type' do @@ -166,6 +164,15 @@ describe Gitlab::Ci::Config::Entry::Job do expect(entry.errors).to include 'job start in should be a duration' end end + + context 'when start_in is longer than one day' do + let(:config) { { when: 'delayed', start_in: '2 days' } } + + it 'returns error about exceeding the limit' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'job start in should not exceed the limit' + end + end end context 'when start_in specified without delayed specification' do diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 83d39b82068..bef93fe7af7 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,4 +1,5 @@ -require 'spec_helper' +require 'fast_spec_helper' +require_dependency 'active_model' describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } @@ -124,6 +125,23 @@ describe Gitlab::Ci::Config::Entry::Policy do end end + context 'when specifying a valid changes policy' do + let(:config) { { changes: %w[some/* paths/**/*.rb] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when changes policy is invalid' do + let(:config) { { changes: [1, 2] } } + + it 'returns errors' do + expect(entry.errors).to include /changes should be an array of strings/ + end + end + context 'when specifying unknown policy' do let(:config) { { refs: ['master'], invalid: :something } } diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb index 3098a17c50d..f98183d6d18 100644 --- a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb +++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb @@ -17,7 +17,7 @@ describe Gitlab::Ci::Status::Build::Scheduled do let(:build) { create(:ci_build, scheduled_at: 1.minute.since, project: project) } it 'shows execute_in of the scheduled job' do - Timecop.freeze do + Timecop.freeze(Time.now.change(usec: 0)) do expect(subject.status_tooltip).to include('00:01:00') end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index d75c473eb66..85b23edce9f 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1369,7 +1369,7 @@ module Gitlab end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings") end - it 'returns errors if pipeline variables expression is invalid' do + it 'returns errors if pipeline variables expression policy is invalid' do config = YAML.dump({ rspec: { script: 'test', only: { variables: ['== null'] } } }) expect { Gitlab::Ci::YamlProcessor.new(config) } @@ -1377,6 +1377,14 @@ module Gitlab 'jobs:rspec:only variables invalid expression syntax') end + it 'returns errors if pipeline changes policy is invalid' do + config = YAML.dump({ rspec: { script: 'test', only: { changes: [1] } } }) + + expect { Gitlab::Ci::YamlProcessor.new(config) } + .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:only changes should be an array of strings') + end + it 'returns errors if extended hash configuration is invalid' do config = YAML.dump({ rspec: { extends: 'something', script: 'test' } }) diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 677eb373d22..2d94356f386 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -5,6 +5,34 @@ describe Gitlab::Diff::Position do let(:project) { create(:project, :repository) } + let(:args_for_img) do + { + old_path: "files/any.img", + new_path: "files/any.img", + base_sha: nil, + head_sha: nil, + start_sha: nil, + width: 100, + height: 100, + x: 1, + y: 100, + position_type: "image" + } + end + + let(:args_for_text) do + { + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + base_sha: nil, + head_sha: nil, + start_sha: nil, + position_type: "text" + } + end + describe "position for an added text file" do let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } @@ -529,53 +557,49 @@ describe Gitlab::Diff::Position do end end + describe "#as_json" do + shared_examples "diff position json" do + let(:diff_position) { described_class.new(args) } + + it "returns the position as JSON" do + expect(diff_position.as_json).to eq(args.stringify_keys) + end + end + + context "for text positon" do + let(:args) { args_for_text } + + it_behaves_like "diff position json" + end + + context "for image positon" do + let(:args) { args_for_img } + + it_behaves_like "diff position json" + end + end + describe "#to_json" do shared_examples "diff position json" do + let(:diff_position) { described_class.new(args) } + it "returns the position as JSON" do - expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys) + expect(JSON.parse(diff_position.to_json)).to eq(args.stringify_keys) end it "works when nested under another hash" do - expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys) + expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => args.stringify_keys) end end context "for text positon" do - let(:hash) do - { - old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 14, - base_sha: nil, - head_sha: nil, - start_sha: nil, - position_type: "text" - } - end - - let(:diff_position) { described_class.new(hash) } + let(:args) { args_for_text } it_behaves_like "diff position json" end context "for image positon" do - let(:hash) do - { - old_path: "files/any.img", - new_path: "files/any.img", - base_sha: nil, - head_sha: nil, - start_sha: nil, - width: 100, - height: 100, - x: 1, - y: 100, - position_type: "image" - } - end - - let(:diff_position) { described_class.new(hash) } + let(:args) { args_for_img } it_behaves_like "diff position json" end diff --git a/spec/lib/gitlab/git/diff_stats_collection_spec.rb b/spec/lib/gitlab/git/diff_stats_collection_spec.rb index 89927cbb3a6..b07690ef39c 100644 --- a/spec/lib/gitlab/git/diff_stats_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_stats_collection_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Git::DiffStatsCollection do let(:diff_stats) { [stats_a, stats_b] } let(:collection) { described_class.new(diff_stats) } - describe '.find_by_path' do + describe '#find_by_path' do it 'returns stats by path when found' do expect(collection.find_by_path('foo')).to eq(stats_a) end @@ -23,4 +23,10 @@ describe Gitlab::Git::DiffStatsCollection do expect(collection.find_by_path('no-file')).to be_nil end end + + describe '#paths' do + it 'returns only modified paths' do + expect(collection.paths).to eq %w[foo bar] + end + end end diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb new file mode 100644 index 00000000000..566c8209504 --- /dev/null +++ b/spec/lib/gitlab/git/push_spec.rb @@ -0,0 +1,166 @@ +require 'spec_helper' + +describe Gitlab::Git::Push do + set(:project) { create(:project, :repository) } + + let(:oldrev) { project.commit('HEAD~2').id } + let(:newrev) { project.commit.id } + let(:ref) { 'refs/heads/some-branch' } + + subject { described_class.new(project, oldrev, newrev, ref) } + + describe '#branch_name' do + context 'when it is a branch push' do + let(:ref) { 'refs/heads/my-branch' } + + it 'returns branch name' do + expect(subject.branch_name).to eq 'my-branch' + end + end + + context 'when it is a tag push' do + let(:ref) { 'refs/tags/my-branch' } + + it 'returns nil' do + expect(subject.branch_name).to be_nil + end + end + end + + describe '#branch_push?' do + context 'when pushing a branch ref' do + let(:ref) { 'refs/heads/my-branch' } + + it { is_expected.to be_branch_push } + end + + context 'when it is a tag push' do + let(:ref) { 'refs/tags/my-tag' } + + it { is_expected.not_to be_branch_push } + end + end + + describe '#branch_updated?' do + context 'when it is a branch push with correct old and new revisions' do + it { is_expected.to be_branch_updated } + end + + context 'when it is not a branch push' do + let(:ref) { 'refs/tags/my-tag' } + + it { is_expected.not_to be_branch_updated } + end + + context 'when old revision is blank' do + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it { is_expected.not_to be_branch_updated } + end + + context 'when it is not a branch push' do + let(:newrev) { Gitlab::Git::BLANK_SHA } + + it { is_expected.not_to be_branch_updated } + end + + context 'when oldrev is nil' do + let(:oldrev) { nil } + + it { is_expected.not_to be_branch_updated } + end + end + + describe '#force_push?' do + context 'when old revision is an ancestor of the new revision' do + let(:oldrev) { 'HEAD~3' } + let(:newrev) { 'HEAD~1' } + + it { is_expected.not_to be_force_push } + end + + context 'when old revision is not an ancestor of the new revision' do + let(:oldrev) { 'HEAD~3' } + let(:newrev) { '123456' } + + it { is_expected.to be_force_push } + end + end + + describe '#branch_added?' do + context 'when old revision is defined' do + it { is_expected.not_to be_branch_added } + end + + context 'when old revision is not defined' do + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it { is_expected.to be_branch_added } + end + end + + describe '#branch_removed?' do + context 'when new revision is defined' do + it { is_expected.not_to be_branch_removed } + end + + context 'when new revision is not defined' do + let(:newrev) { Gitlab::Git::BLANK_SHA } + + it { is_expected.to be_branch_removed } + end + end + + describe '#modified_paths' do + context 'when a push is a branch update' do + let(:newrev) { '498214d' } + let(:oldrev) { '281d3a7' } + + it 'returns modified paths' do + expect(subject.modified_paths).to eq ['bar/branch-test.txt', + 'files/js/commit.coffee', + 'with space/README.md'] + end + end + + context 'when a push is not a branch update' do + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it 'raises an error' do + expect { subject.modified_paths }.to raise_error(ArgumentError) + end + end + end + + describe '#oldrev' do + context 'when a valid oldrev is provided' do + it 'returns oldrev' do + expect(subject.oldrev).to eq oldrev + end + end + + context 'when a nil valud is provided' do + let(:oldrev) { nil } + + it 'returns blank SHA' do + expect(subject.oldrev).to eq Gitlab::Git::BLANK_SHA + end + end + end + + describe '#newrev' do + context 'when valid newrev is provided' do + it 'returns newrev' do + expect(subject.newrev).to eq newrev + end + end + + context 'when a nil valud is provided' do + let(:newrev) { nil } + + it 'returns blank SHA' do + expect(subject.newrev).to eq Gitlab::Git::BLANK_SHA + end + end + end +end diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index cf9e0f71910..a31f77484d8 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -191,9 +191,7 @@ describe Gitlab::ImportExport::RelationFactory do "author" => { "name" => "Administrator" }, - "events" => [ - - ] + "events" => [] } end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 1d59cff7ba8..f7935149b23 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -493,6 +493,7 @@ ProjectFeature: - snippets_access_level - builds_access_level - repository_access_level +- pages_access_level - created_at - updated_at ProtectedBranch::MergeAccessLevel: diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb index c8a1e433d59..30035c79e58 100644 --- a/spec/lib/gitlab/version_info_spec.rb +++ b/spec/lib/gitlab/version_info_spec.rb @@ -57,6 +57,9 @@ describe 'Gitlab::VersionInfo' do context 'parse' do it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) } it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) } + it { expect(Gitlab::VersionInfo.parse("1.0.0-ee")).to eq(@v1_0_0) } + it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1")).to eq(@v1_0_0) } + it { expect(Gitlab::VersionInfo.parse("1.0.0-rc1-ee")).to eq(@v1_0_0) } it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) } it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid } end diff --git a/spec/migrations/add_pages_access_level_to_project_feature_spec.rb b/spec/migrations/add_pages_access_level_to_project_feature_spec.rb new file mode 100644 index 00000000000..3946602c5be --- /dev/null +++ b/spec/migrations/add_pages_access_level_to_project_feature_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20180423204600_add_pages_access_level_to_project_feature.rb') + +describe AddPagesAccessLevelToProjectFeature, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:features) { table(:project_features) } + let!(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab') } + let!(:first_project) { projects.create(name: 'gitlab1', path: 'gitlab1', namespace_id: namespace.id) } + let!(:first_project_features) { features.create(project_id: first_project.id) } + let!(:second_project) { projects.create(name: 'gitlab2', path: 'gitlab2', namespace_id: namespace.id) } + let!(:second_project_features) { features.create(project_id: second_project.id) } + + it 'correctly migrate pages for old projects to be public' do + migrate! + + # For old projects pages should be public + expect(first_project_features.reload.pages_access_level).to eq ProjectFeature::PUBLIC + expect(second_project_features.reload.pages_access_level).to eq ProjectFeature::PUBLIC + end + + it 'after migration pages are enabled as default' do + migrate! + + # For new project default is enabled + third_project = projects.create(name: 'gitlab3', path: 'gitlab3', namespace_id: namespace.id) + third_project_features = features.create(project_id: third_project.id) + expect(third_project_features.reload.pages_access_level).to eq ProjectFeature::ENABLED + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 31bcfe1c6b1..a046541031e 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -261,7 +261,7 @@ describe Ci::Build do it 'schedules BuildScheduleWorker at the right time' do Timecop.freeze do expect(Ci::BuildScheduleWorker) - .to receive(:perform_at).with(1.minute.since, build.id) + .to receive(:perform_at).with(be_like_time(1.minute.since), build.id) subject end @@ -1852,6 +1852,7 @@ describe Ci::Build do describe '#variables' do let(:container_registry_enabled) { false } + let(:gitlab_version_info) { Gitlab::VersionInfo.parse(Gitlab::VERSION) } let(:predefined_variables) do [ { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, @@ -1869,6 +1870,9 @@ describe Ci::Build do { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true }, { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, + { key: 'CI_SERVER_VERSION_MAJOR', value: gitlab_version_info.major.to_s, public: true }, + { key: 'CI_SERVER_VERSION_MINOR', value: gitlab_version_info.minor.to_s, public: true }, + { key: 'CI_SERVER_VERSION_PATCH', value: gitlab_version_info.patch.to_s, public: true }, { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true }, { key: 'CI_JOB_NAME', value: 'test', public: true }, { key: 'CI_JOB_STAGE', value: 'test', public: true }, diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index b19e75a956d..3b01b39ecab 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -837,6 +837,57 @@ describe Ci::Pipeline, :mailer do end end + describe '#branch_updated?' do + context 'when pipeline has before SHA' do + before do + pipeline.update_column(:before_sha, 'a1b2c3d4') + end + + it 'runs on a branch update push' do + expect(pipeline.before_sha).not_to be Gitlab::Git::BLANK_SHA + expect(pipeline.branch_updated?).to be true + end + end + + context 'when pipeline does not have before SHA' do + before do + pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA) + end + + it 'does not run on a branch updating push' do + expect(pipeline.branch_updated?).to be false + end + end + end + + describe '#modified_paths' do + context 'when old and new revisions are set' do + let(:project) { create(:project, :repository) } + + before do + pipeline.update(before_sha: '1234abcd', sha: '2345bcde') + end + + it 'fetches stats for changes between commits' do + expect(project.repository) + .to receive(:diff_stats).with('1234abcd', '2345bcde') + .and_call_original + + pipeline.modified_paths + end + end + + context 'when either old or new revision is missing' do + before do + pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA) + end + + it 'raises an error' do + expect { pipeline.modified_paths }.to raise_error(ArgumentError) + end + end + end + describe '#has_kubernetes_active?' do context 'when kubernetes is active' do shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do @@ -1993,4 +2044,34 @@ describe Ci::Pipeline, :mailer do end end end + + describe '#default_branch?' do + let(:default_branch) { 'master'} + + subject { pipeline.default_branch? } + + before do + allow(project).to receive(:default_branch).and_return(default_branch) + end + + context 'when pipeline ref is the default branch of the project' do + let(:pipeline) do + build(:ci_empty_pipeline, status: :created, project: project, ref: default_branch) + end + + it "returns true" do + expect(subject).to be_truthy + end + end + + context 'when pipeline ref is not the default branch of the project' do + let(:pipeline) do + build(:ci_empty_pipeline, status: :created, project: project, ref: 'another_branch') + end + + it "returns false" do + expect(subject).to be_falsey + end + end + end end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 060a1d95293..5076f7faeac 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -200,8 +200,8 @@ describe Ci::Stage, :models do end end - describe '#schedule' do - subject { stage.schedule } + describe '#delay' do + subject { stage.delay } let(:stage) { create(:ci_stage_entity, status: :created) } diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index da26d802688..f8d50e89d40 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -331,11 +331,12 @@ describe CacheMarkdownField do end context 'with a project' do - let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: :project_value) } + let(:project) { create(:project, group: create(:group)) } + let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: project) } it 'sets the project in the context' do is_expected.to have_key(:project) - expect(context[:project]).to eq(:project_value) + expect(context[:project]).to eq(project) end it 'invalidates the cache when project changes' do diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb index 34db94920f3..cb3d6c7cda2 100644 --- a/spec/models/instance_configuration_spec.rb +++ b/spec/models/instance_configuration_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' -RSpec.describe InstanceConfiguration do +describe InstanceConfiguration do context 'without cache' do describe '#settings' do describe '#ssh_algorithms_hashes' do - let(:md5) { '54:e0:f8:70:d6:4f:4c:b1:b3:02:44:77:cf:cd:0d:fc' } - let(:sha256) { '9327f0d15a48c4d9f6a3aee65a1825baf9a3412001c98169c5fd022ac27762fc' } + let(:md5) { '5a:65:6c:4d:d4:4c:6d:e6:59:25:b8:cf:ba:34:e7:64' } + let(:sha256) { 'SHA256:2KJDT7xf2i68mBgJ3TVsjISntg4droLbXYLfQj0VvSY' } it 'does not return anything if file does not exist' do stub_pub_file(exist: false) diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index f2aad455d5f..52c00a74b4b 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -65,7 +65,8 @@ describe InternalId do context 'with an insufficient schema version' do before do described_class.reset_column_information - expect(ActiveRecord::Migrator).to receive(:current_version).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) + # Project factory will also call the current_version + expect(ActiveRecord::Migrator).to receive(:current_version).twice.and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) end let(:init) { double('block') } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 19bc2713ef5..6f900a60213 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -268,45 +268,6 @@ describe Issue do end end - describe '#related_branches' do - let(:user) { create(:admin) } - - before do - allow(subject.project.repository).to receive(:branch_names) - .and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name, "#{subject.iid}-branch"]) - - # Without this stub, the `create(:merge_request)` above fails because it can't find - # the source branch. This seems like a reasonable compromise, in comparison with - # setting up a full repo here. - allow_any_instance_of(MergeRequest).to receive(:create_merge_request_diff) - end - - it "selects the right branches when there are no referenced merge requests" do - expect(subject.related_branches(user)).to eq([subject.to_branch_name, "#{subject.iid}-branch"]) - end - - it "selects the right branches when there is a referenced merge request" do - merge_request = create(:merge_request, { description: "Closes ##{subject.iid}", - source_project: subject.project, - source_branch: "#{subject.iid}-branch" }) - merge_request.create_cross_references!(user) - - referenced_merge_requests = Issues::ReferencedMergeRequestsService - .new(subject.project, user) - .referenced_merge_requests(subject) - - expect(referenced_merge_requests).not_to be_empty - expect(subject.related_branches(user)).to eq([subject.to_branch_name]) - end - - it 'excludes stable branches from the related branches' do - allow(subject.project.repository).to receive(:branch_names) - .and_return(["#{subject.iid}-0-stable"]) - - expect(subject.related_branches(user)).to eq [] - end - end - describe '#suggested_branch_name' do let(:repository) { double } diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 99670af786a..3fc6c06b7fa 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -155,4 +155,40 @@ describe Label do expect(described_class.search('feature')).to be_empty end end + + describe '.subscribed_by' do + let!(:user) { create(:user) } + let!(:label) { create(:label) } + let!(:label2) { create(:label) } + + before do + label.subscribe(user) + end + + it 'returns subscribed labels' do + expect(described_class.subscribed_by(user.id)).to eq([label]) + end + + it 'returns nothing' do + expect(described_class.subscribed_by(0)).to be_empty + end + end + + describe '.optionally_subscribed_by' do + let!(:user) { create(:user) } + let!(:label) { create(:label) } + let!(:label2) { create(:label) } + + before do + label.subscribe(user) + end + + it 'returns subscribed labels' do + expect(described_class.optionally_subscribed_by(user.id)).to eq([label]) + end + + it 'returns all labels if user_id is nil' do + expect(described_class.optionally_subscribed_by(nil)).to match_array([label, label2]) + end + end end diff --git a/spec/models/license_template_spec.rb b/spec/models/license_template_spec.rb index c633e1908d4..dd912eefac1 100644 --- a/spec/models/license_template_spec.rb +++ b/spec/models/license_template_spec.rb @@ -54,6 +54,6 @@ describe LicenseTemplate do end def build_template(content) - described_class.new(id: 'foo', name: 'foo', category: :Other, content: content) + described_class.new(key: 'foo', name: 'foo', category: :Other, content: content) end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 947be44c903..1783dd3206b 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -231,33 +231,60 @@ describe Note do let(:ext_proj) { create(:project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } - let(:note) do - create :note, - noteable: ext_issue, project: ext_proj, - note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", - system: true - end + shared_examples "checks references" do + it "returns true" do + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + end - it "returns true" do - expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy - end + it "returns false" do + expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy + end - it "returns false" do - expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy + it "returns false if user visible reference count set" do + note.user_visible_reference_count = 1 + note.total_reference_count = 1 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_falsy + end + + it "returns true if ref count is 0" do + note.user_visible_reference_count = 0 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + end end - it "returns false if user visible reference count set" do - note.user_visible_reference_count = 1 + context "when there is one reference in note" do + let(:note) do + create :note, + noteable: ext_issue, project: ext_proj, + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", + system: true + end - expect(note).not_to receive(:reference_mentionables) - expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_falsy + it_behaves_like "checks references" end - it "returns true if ref count is 0" do - note.user_visible_reference_count = 0 + context "when there are two references in note" do + let(:note) do + create :note, + noteable: ext_issue, project: ext_proj, + note: "mentioned in issue #{private_issue.to_reference(ext_proj)} and " \ + "public issue #{ext_issue.to_reference(ext_proj)}", + system: true + end + + it_behaves_like "checks references" - expect(note).not_to receive(:reference_mentionables) - expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + it "returns true if user visible reference count set and there is a private reference" do + note.user_visible_reference_count = 1 + note.total_reference_count = 2 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + end end end @@ -269,7 +296,7 @@ describe Note do end context 'when the note might contain cross references' do - SystemNoteMetadata::TYPES_WITH_CROSS_REFERENCES.each do |type| + SystemNoteMetadata.new.cross_reference_types.each do |type| let(:note) { create(:note, :system) } let!(:metadata) { create(:system_note_metadata, note: note, action: type) } diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 797d767465a..342798f730b 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -70,24 +70,31 @@ describe ProjectAutoDevops do end context 'when deploy_strategy is manual' do - let(:domain) { 'example.com' } - - before do - auto_devops.deploy_strategy = 'manual' + let(:auto_devops) { build_stubbed(:project_auto_devops, :manual_deployment, project: project) } + let(:expected_variables) do + [ + { key: 'INCREMENTAL_ROLLOUT_MODE', value: 'manual' }, + { key: 'STAGING_ENABLED', value: '1' }, + { key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1' } + ] end + it { expect(auto_devops.predefined_variables).to include(*expected_variables) } + end + + context 'when deploy_strategy is continuous' do + let(:auto_devops) { build_stubbed(:project_auto_devops, :continuous_deployment, project: project) } + it do expect(auto_devops.predefined_variables.map { |var| var[:key] }) - .to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED") + .not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED") end end - context 'when deploy_strategy is continuous' do - let(:domain) { 'example.com' } + context 'when deploy_strategy is timed_incremental' do + let(:auto_devops) { build_stubbed(:project_auto_devops, :timed_incremental_deployment, project: project) } - before do - auto_devops.deploy_strategy = 'continuous' - end + it { expect(auto_devops.predefined_variables).to include(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'timed') } it do expect(auto_devops.predefined_variables.map { |var| var[:key] }) diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index cd7f77024da..fee7d65c217 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -17,7 +17,7 @@ describe ProjectFeature do end describe '#feature_available?' do - let(:features) { %w(issues wiki builds merge_requests snippets repository) } + let(:features) { %w(issues wiki builds merge_requests snippets repository pages) } context 'when features are disabled' do it "returns false" do @@ -73,6 +73,22 @@ describe ProjectFeature do end end end + + context 'when feature is disabled by a feature flag' do + it 'returns false' do + stub_feature_flags(issues: false) + + expect(project.feature_available?(:issues, user)).to eq(false) + end + end + + context 'when feature is enabled by a feature flag' do + it 'returns true' do + stub_feature_flags(issues: true) + + expect(project.feature_available?(:issues, user)).to eq(true) + end + end end context 'repository related features' do @@ -96,6 +112,19 @@ describe ProjectFeature do end end + context 'public features' do + it "does not allow public for other than pages" do + features = %w(issues wiki builds merge_requests snippets repository) + project_feature = project.project_feature + + features.each do |feature| + field = "#{feature}_access_level".to_sym + project_feature.update_attribute(field, ProjectFeature::PUBLIC) + expect(project_feature.valid?).to be_falsy + end + end + end + describe '#*_enabled?' do let(:features) { %w(wiki builds merge_requests) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8b71919544e..3fecddefff2 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4039,6 +4039,63 @@ describe Project do end end + describe "#find_or_initialize_services" do + subject { build(:project) } + + it 'returns only enabled services' do + allow(Service).to receive(:available_services_names).and_return(%w(prometheus pushover)) + allow(subject).to receive(:disabled_services).and_return(%w(prometheus)) + + services = subject.find_or_initialize_services + + expect(services.count).to eq 1 + expect(services).to include(PushoverService) + end + end + + describe "#find_or_initialize_service" do + subject { build(:project) } + + it 'avoids N+1 database queries' do + allow(Service).to receive(:available_services_names).and_return(%w(prometheus pushover)) + + control_count = ActiveRecord::QueryRecorder.new { subject.find_or_initialize_service('prometheus') }.count + + allow(Service).to receive(:available_services_names).and_call_original + + expect { subject.find_or_initialize_service('prometheus') }.not_to exceed_query_limit(control_count) + end + + it 'returns nil if service is disabled' do + allow(subject).to receive(:disabled_services).and_return(%w(prometheus)) + + expect(subject.find_or_initialize_service('prometheus')).to be_nil + end + end + + describe '.find_without_deleted' do + it 'returns nil if the project is about to be removed' do + project = create(:project, pending_delete: true) + + expect(described_class.find_without_deleted(project.id)).to be_nil + end + + it 'returns a project when it is not about to be removed' do + project = create(:project) + + expect(described_class.find_without_deleted(project.id)).to eq(project) + end + end + + describe '.for_group' do + it 'returns the projects for a given group' do + group = create(:group) + project = create(:project, namespace: group) + + expect(described_class.for_group(group)).to eq([project]) + end + end + def rugged_config rugged_repo(project.repository).config end diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index f29abcf536e..2c01578aaca 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -173,4 +173,129 @@ describe Todo do expect(subject).not_to be_self_assigned end end + + describe '.for_action' do + it 'returns the todos for a given action' do + create(:todo, action: Todo::MENTIONED) + + todo = create(:todo, action: Todo::ASSIGNED) + + expect(described_class.for_action(Todo::ASSIGNED)).to eq([todo]) + end + end + + describe '.for_author' do + it 'returns the todos for a given author' do + user1 = create(:user) + user2 = create(:user) + todo = create(:todo, author: user1) + + create(:todo, author: user2) + + expect(described_class.for_author(user1)).to eq([todo]) + end + end + + describe '.for_project' do + it 'returns the todos for a given project' do + project1 = create(:project) + project2 = create(:project) + todo = create(:todo, project: project1) + + create(:todo, project: project2) + + expect(described_class.for_project(project1)).to eq([todo]) + end + end + + describe '.for_group' do + it 'returns the todos for a given group' do + group1 = create(:group) + group2 = create(:group) + todo = create(:todo, group: group1) + + create(:todo, group: group2) + + expect(described_class.for_group(group1)).to eq([todo]) + end + end + + describe '.for_type' do + it 'returns the todos for a given target type' do + todo = create(:todo, target: create(:issue)) + + create(:todo, target: create(:merge_request)) + + expect(described_class.for_type(Issue)).to eq([todo]) + end + end + + describe '.for_target' do + it 'returns the todos for a given target' do + todo = create(:todo, target: create(:issue)) + + create(:todo, target: create(:merge_request)) + + expect(described_class.for_target(todo.target)).to eq([todo]) + end + end + + describe '.for_commit' do + it 'returns the todos for a commit ID' do + todo = create(:todo, commit_id: '123') + + create(:todo, commit_id: '456') + + expect(described_class.for_commit('123')).to eq([todo]) + end + end + + describe '.for_group_and_descendants' do + it 'returns the todos for a group and its descendants' do + parent_group = create(:group) + child_group = create(:group, parent: parent_group) + + todo1 = create(:todo, group: parent_group) + todo2 = create(:todo, group: child_group) + todos = described_class.for_group_and_descendants(parent_group) + + expect(todos).to include(todo1) + + # Nested groups only work on PostgreSQL, so on MySQL todo2 won't be + # present. + expect(todos).to include(todo2) if Gitlab::Database.postgresql? + end + end + + describe '.any_for_target?' do + it 'returns true if there are todos for a given target' do + todo = create(:todo) + + expect(described_class.any_for_target?(todo.target)).to eq(true) + end + + it 'returns false if there are no todos for a given target' do + issue = create(:issue) + + expect(described_class.any_for_target?(issue)).to eq(false) + end + end + + describe '.update_state' do + it 'updates the state of todos' do + todo = create(:todo, :pending) + ids = described_class.update_state(:done) + + todo.reload + + expect(ids).to eq([todo.id]) + expect(todo.state).to eq('done') + end + + it 'does not update todos that already have the given state' do + create(:todo, :pending) + + expect(described_class.update_state(:pending)).to be_empty + end + end end diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index b2fe10bb0b0..d7992f0a4a9 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -227,7 +227,7 @@ describe Ci::BuildPresenter do it 'returns execution time' do Timecop.freeze do - is_expected.to eq(60.0) + is_expected.to be_like_time(60.0) end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 06ccf383362..98399471f9a 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -572,6 +572,20 @@ describe API::Commits do expect(json_response['title']).to eq(message) end + it 'includes the commit stats' do + post api(url, user), valid_mo_params + + expect(response).to have_gitlab_http_status(201) + expect(json_response).to include 'stats' + end + + it "doesn't include the commit stats when stats is false" do + post api(url, user), valid_mo_params.merge(stats: false) + + expect(response).to have_gitlab_http_status(201) + expect(json_response).not_to include 'stats' + end + it 'return a 400 bad request if there are any issues' do post api(url, user), invalid_mo_params diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 1e2e13a723c..9f6cf12f9a7 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -56,6 +56,7 @@ describe API::Issues do let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } let(:no_milestone_title) { URI.escape(Milestone::None.title) } + let(:any_milestone_title) { URI.escape(Milestone::Any.title) } before(:all) do project.add_reporter(user) @@ -811,6 +812,15 @@ describe API::Issues do expect(json_response.first['id']).to eq(confidential_issue.id) end + it 'returns an array of issues with any milestone' do + get api("#{base_url}/issues?milestone=#{any_milestone_title}", user) + + response_ids = json_response.map { |issue| issue['id'] } + + expect_paginated_array_response(size: 2) + expect(response_ids).to contain_exactly(closed_issue.id, issue.id) + end + it 'sorts by created_at descending by default' do get api("#{base_url}/issues", user) diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb index a55796cf343..e369c1435f0 100644 --- a/spec/requests/api/markdown_spec.rb +++ b/spec/requests/api/markdown_spec.rb @@ -106,6 +106,52 @@ describe API::Markdown do .and include("#1</a>") end end + + context 'with a public project and confidential issue' do + let(:public_project) { create(:project, :public) } + let(:confidential_issue) { create(:issue, :confidential, project: public_project, title: 'Confidential title') } + + let(:text) { ":tada: Hello world! :100: #{confidential_issue.to_reference}" } + let(:params) { { text: text, gfm: true, project: public_project.full_path } } + + shared_examples 'user without proper access' do + it 'does not render the title or link' do + expect(response).to have_http_status(201) + expect(json_response["html"]).not_to include('Confidential title') + expect(json_response["html"]).not_to include('<a href=') + expect(json_response["html"]).to include('Hello world!') + .and include('data-name="tada"') + .and include('data-name="100"') + .and include('#1</p>') + end + end + + context 'when not logged in' do + let(:user) { } + + it_behaves_like 'user without proper access' + end + + context 'when logged in as user without access' do + let(:user) { create(:user) } + + it_behaves_like 'user without proper access' + end + + context 'when logged in as author' do + let(:user) { confidential_issue.author } + + it 'renders the title or link' do + expect(response).to have_http_status(201) + expect(json_response["html"]).to include('Confidential title') + expect(json_response["html"]).to include('Hello world!') + .and include('data-name="tada"') + .and include('data-name="100"') + .and include("<a href=\"#{IssuesHelper.url_for_issue(confidential_issue.iid, public_project)}\"") + .and include("#1</a>") + end + end + end end end end diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb new file mode 100644 index 00000000000..c41eabe0a48 --- /dev/null +++ b/spec/requests/api/pages/internal_access_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe "Internal Project Pages Access" do + using RSpec::Parameterized::TableSyntax + include AccessMatchers + + set(:group) { create(:group) } + set(:project) { create(:project, :internal, pages_access_level: ProjectFeature::ENABLED, namespace: group) } + + set(:admin) { create(:admin) } + set(:owner) { create(:user) } + set(:master) { create(:user) } + set(:developer) { create(:user) } + set(:reporter) { create(:user) } + set(:guest) { create(:user) } + set(:user) { create(:user) } + + before do + allow(Gitlab.config.pages).to receive(:access_control).and_return(true) + group.add_owner(owner) + project.add_master(master) + project.add_developer(developer) + project.add_reporter(reporter) + project.add_guest(guest) + end + + describe "Project should be internal" do + describe '#internal?' do + subject { project.internal? } + it { is_expected.to be_truthy } + end + end + + describe "GET /projects/:id/pages_access" do + context 'access depends on the level' do + where(:pages_access_level, :with_user, :expected_result) do + ProjectFeature::DISABLED | "admin" | 403 + ProjectFeature::DISABLED | "owner" | 403 + ProjectFeature::DISABLED | "master" | 403 + ProjectFeature::DISABLED | "developer" | 403 + ProjectFeature::DISABLED | "reporter" | 403 + ProjectFeature::DISABLED | "guest" | 403 + ProjectFeature::DISABLED | "user" | 403 + ProjectFeature::DISABLED | nil | 404 + ProjectFeature::PUBLIC | "admin" | 200 + ProjectFeature::PUBLIC | "owner" | 200 + ProjectFeature::PUBLIC | "master" | 200 + ProjectFeature::PUBLIC | "developer" | 200 + ProjectFeature::PUBLIC | "reporter" | 200 + ProjectFeature::PUBLIC | "guest" | 200 + ProjectFeature::PUBLIC | "user" | 200 + ProjectFeature::PUBLIC | nil | 404 + ProjectFeature::ENABLED | "admin" | 200 + ProjectFeature::ENABLED | "owner" | 200 + ProjectFeature::ENABLED | "master" | 200 + ProjectFeature::ENABLED | "developer" | 200 + ProjectFeature::ENABLED | "reporter" | 200 + ProjectFeature::ENABLED | "guest" | 200 + ProjectFeature::ENABLED | "user" | 200 + ProjectFeature::ENABLED | nil | 404 + ProjectFeature::PRIVATE | "admin" | 200 + ProjectFeature::PRIVATE | "owner" | 200 + ProjectFeature::PRIVATE | "master" | 200 + ProjectFeature::PRIVATE | "developer" | 200 + ProjectFeature::PRIVATE | "reporter" | 200 + ProjectFeature::PRIVATE | "guest" | 200 + ProjectFeature::PRIVATE | "user" | 403 + ProjectFeature::PRIVATE | nil | 404 + end + + with_them do + before do + project.project_feature.update(pages_access_level: pages_access_level) + end + it "correct return value" do + if !with_user.nil? + user = public_send(with_user) + get api("/projects/#{project.id}/pages_access", user) + else + get api("/projects/#{project.id}/pages_access") + end + + expect(response).to have_gitlab_http_status(expected_result) + end + end + end + end +end diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb new file mode 100644 index 00000000000..d69c15b0477 --- /dev/null +++ b/spec/requests/api/pages/private_access_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe "Private Project Pages Access" do + using RSpec::Parameterized::TableSyntax + include AccessMatchers + + set(:group) { create(:group) } + set(:project) { create(:project, :private, pages_access_level: ProjectFeature::ENABLED, namespace: group) } + + set(:admin) { create(:admin) } + set(:owner) { create(:user) } + set(:master) { create(:user) } + set(:developer) { create(:user) } + set(:reporter) { create(:user) } + set(:guest) { create(:user) } + set(:user) { create(:user) } + + before do + allow(Gitlab.config.pages).to receive(:access_control).and_return(true) + group.add_owner(owner) + project.add_master(master) + project.add_developer(developer) + project.add_reporter(reporter) + project.add_guest(guest) + end + + describe "Project should be private" do + describe '#private?' do + subject { project.private? } + it { is_expected.to be_truthy } + end + end + + describe "GET /projects/:id/pages_access" do + context 'access depends on the level' do + where(:pages_access_level, :with_user, :expected_result) do + ProjectFeature::DISABLED | "admin" | 403 + ProjectFeature::DISABLED | "owner" | 403 + ProjectFeature::DISABLED | "master" | 403 + ProjectFeature::DISABLED | "developer" | 403 + ProjectFeature::DISABLED | "reporter" | 403 + ProjectFeature::DISABLED | "guest" | 403 + ProjectFeature::DISABLED | "user" | 404 + ProjectFeature::DISABLED | nil | 404 + ProjectFeature::PUBLIC | "admin" | 200 + ProjectFeature::PUBLIC | "owner" | 200 + ProjectFeature::PUBLIC | "master" | 200 + ProjectFeature::PUBLIC | "developer" | 200 + ProjectFeature::PUBLIC | "reporter" | 200 + ProjectFeature::PUBLIC | "guest" | 200 + ProjectFeature::PUBLIC | "user" | 404 + ProjectFeature::PUBLIC | nil | 404 + ProjectFeature::ENABLED | "admin" | 200 + ProjectFeature::ENABLED | "owner" | 200 + ProjectFeature::ENABLED | "master" | 200 + ProjectFeature::ENABLED | "developer" | 200 + ProjectFeature::ENABLED | "reporter" | 200 + ProjectFeature::ENABLED | "guest" | 200 + ProjectFeature::ENABLED | "user" | 404 + ProjectFeature::ENABLED | nil | 404 + ProjectFeature::PRIVATE | "admin" | 200 + ProjectFeature::PRIVATE | "owner" | 200 + ProjectFeature::PRIVATE | "master" | 200 + ProjectFeature::PRIVATE | "developer" | 200 + ProjectFeature::PRIVATE | "reporter" | 200 + ProjectFeature::PRIVATE | "guest" | 200 + ProjectFeature::PRIVATE | "user" | 404 + ProjectFeature::PRIVATE | nil | 404 + end + + with_them do + before do + project.project_feature.update(pages_access_level: pages_access_level) + end + it "correct return value" do + if !with_user.nil? + user = public_send(with_user) + get api("/projects/#{project.id}/pages_access", user) + else + get api("/projects/#{project.id}/pages_access") + end + + expect(response).to have_gitlab_http_status(expected_result) + end + end + end + end +end diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb new file mode 100644 index 00000000000..882ca26ac51 --- /dev/null +++ b/spec/requests/api/pages/public_access_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe "Public Project Pages Access" do + using RSpec::Parameterized::TableSyntax + include AccessMatchers + + set(:group) { create(:group) } + set(:project) { create(:project, :public, pages_access_level: ProjectFeature::ENABLED, namespace: group) } + + set(:admin) { create(:admin) } + set(:owner) { create(:user) } + set(:master) { create(:user) } + set(:developer) { create(:user) } + set(:reporter) { create(:user) } + set(:guest) { create(:user) } + set(:user) { create(:user) } + + before do + allow(Gitlab.config.pages).to receive(:access_control).and_return(true) + group.add_owner(owner) + project.add_master(master) + project.add_developer(developer) + project.add_reporter(reporter) + project.add_guest(guest) + end + + describe "Project should be public" do + describe '#public?' do + subject { project.public? } + it { is_expected.to be_truthy } + end + end + + describe "GET /projects/:id/pages_access" do + context 'access depends on the level' do + where(:pages_access_level, :with_user, :expected_result) do + ProjectFeature::DISABLED | "admin" | 403 + ProjectFeature::DISABLED | "owner" | 403 + ProjectFeature::DISABLED | "master" | 403 + ProjectFeature::DISABLED | "developer" | 403 + ProjectFeature::DISABLED | "reporter" | 403 + ProjectFeature::DISABLED | "guest" | 403 + ProjectFeature::DISABLED | "user" | 403 + ProjectFeature::DISABLED | nil | 403 + ProjectFeature::PUBLIC | "admin" | 200 + ProjectFeature::PUBLIC | "owner" | 200 + ProjectFeature::PUBLIC | "master" | 200 + ProjectFeature::PUBLIC | "developer" | 200 + ProjectFeature::PUBLIC | "reporter" | 200 + ProjectFeature::PUBLIC | "guest" | 200 + ProjectFeature::PUBLIC | "user" | 200 + ProjectFeature::PUBLIC | nil | 200 + ProjectFeature::ENABLED | "admin" | 200 + ProjectFeature::ENABLED | "owner" | 200 + ProjectFeature::ENABLED | "master" | 200 + ProjectFeature::ENABLED | "developer" | 200 + ProjectFeature::ENABLED | "reporter" | 200 + ProjectFeature::ENABLED | "guest" | 200 + ProjectFeature::ENABLED | "user" | 200 + ProjectFeature::ENABLED | nil | 200 + ProjectFeature::PRIVATE | "admin" | 200 + ProjectFeature::PRIVATE | "owner" | 200 + ProjectFeature::PRIVATE | "master" | 200 + ProjectFeature::PRIVATE | "developer" | 200 + ProjectFeature::PRIVATE | "reporter" | 200 + ProjectFeature::PRIVATE | "guest" | 200 + ProjectFeature::PRIVATE | "user" | 403 + ProjectFeature::PRIVATE | nil | 403 + end + + with_them do + before do + project.project_feature.update(pages_access_level: pages_access_level) + end + it "correct return value" do + if !with_user.nil? + user = public_send(with_user) + get api("/projects/#{project.id}/pages_access", user) + else + get api("/projects/#{project.id}/pages_access") + end + + expect(response).to have_gitlab_http_status(expected_result) + end + end + end + end +end diff --git a/spec/requests/api/project_templates_spec.rb b/spec/requests/api/project_templates_spec.rb new file mode 100644 index 00000000000..86e33f23951 --- /dev/null +++ b/spec/requests/api/project_templates_spec.rb @@ -0,0 +1,145 @@ +require 'spec_helper' + +describe API::ProjectTemplates do + let(:public_project) { create(:project, :public) } + let(:private_project) { create(:project, :private) } + let(:developer) { create(:user) } + + before do + private_project.add_developer(developer) + end + + describe 'GET /projects/:id/templates/:type' do + it 'returns dockerfiles' do + get api("/projects/#{public_project.id}/templates/dockerfiles") + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(response).to match_response_schema('public_api/v4/template_list') + expect(json_response).to satisfy_one { |template| template['key'] == 'Binary' } + end + + it 'returns gitignores' do + get api("/projects/#{public_project.id}/templates/gitignores") + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(response).to match_response_schema('public_api/v4/template_list') + expect(json_response).to satisfy_one { |template| template['key'] == 'Actionscript' } + end + + it 'returns gitlab_ci_ymls' do + get api("/projects/#{public_project.id}/templates/gitlab_ci_ymls") + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(response).to match_response_schema('public_api/v4/template_list') + expect(json_response).to satisfy_one { |template| template['key'] == 'Android' } + end + + it 'returns licenses' do + get api("/projects/#{public_project.id}/templates/licenses") + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(response).to match_response_schema('public_api/v4/template_list') + expect(json_response).to satisfy_one { |template| template['key'] == 'mit' } + end + + it 'returns 400 for an unknown template type' do + get api("/projects/#{public_project.id}/templates/unknown") + + expect(response).to have_gitlab_http_status(400) + end + + it 'denies access to an anonymous user on a private project' do + get api("/projects/#{private_project.id}/templates/licenses") + + expect(response).to have_gitlab_http_status(404) + end + + it 'permits access to a developer on a private project' do + get api("/projects/#{private_project.id}/templates/licenses", developer) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template_list') + end + end + + describe 'GET /projects/:id/templates/licenses' do + it 'returns key and name for the listed licenses' do + get api("/projects/#{public_project.id}/templates/licenses") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template_list') + end + end + + describe 'GET /projects/:id/templates/:type/:key' do + it 'returns a specific dockerfile' do + get api("/projects/#{public_project.id}/templates/dockerfiles/Binary") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('Binary') + end + + it 'returns a specific gitignore' do + get api("/projects/#{public_project.id}/templates/gitignores/Actionscript") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('Actionscript') + end + + it 'returns a specific gitlab_ci_yml' do + get api("/projects/#{public_project.id}/templates/gitlab_ci_ymls/Android") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/template') + expect(json_response['name']).to eq('Android') + end + + it 'returns a specific license' do + get api("/projects/#{public_project.id}/templates/licenses/mit") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/license') + end + + it 'returns 404 for an unknown specific template' do + get api("/projects/#{public_project.id}/templates/licenses/unknown") + + expect(response).to have_gitlab_http_status(404) + end + + it 'denies access to an anonymous user on a private project' do + get api("/projects/#{private_project.id}/templates/licenses/mit") + + expect(response).to have_gitlab_http_status(404) + end + + it 'permits access to a developer on a private project' do + get api("/projects/#{private_project.id}/templates/licenses/mit", developer) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/license') + end + end + + describe 'GET /projects/:id/templates/licenses/:key' do + it 'fills placeholders in the license' do + get api("/projects/#{public_project.id}/templates/licenses/agpl-3.0"), + project: 'Project Placeholder', + fullname: 'Fullname Placeholder' + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/license') + + content = json_response['content'] + + expect(content).to include('Project Placeholder') + expect(content).to include("Copyright (C) #{Time.now.year} Fullname Placeholder") + end + end +end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index b7d62df0663..09c1d016081 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -785,35 +785,25 @@ describe API::Users do end describe 'GET /user/:id/keys' do - before do - admin - end + it 'returns 404 for non-existing user' do + user_id = not_existing_user_id - context 'when unauthenticated' do - it 'returns authentication error' do - get api("/users/#{user.id}/keys") - expect(response).to have_gitlab_http_status(401) - end - end + get api("/users/#{user_id}/keys") - context 'when authenticated' do - it 'returns 404 for non-existing user' do - get api('/users/999999/keys', admin) - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 User Not Found') - end + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end - it 'returns array of ssh keys' do - user.keys << key - user.save + it 'returns array of ssh keys' do + user.keys << key + user.save - get api("/users/#{user.id}/keys", admin) + get api("/users/#{user.id}/keys") - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(key.title) - end + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(key.title) end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 5abc6d81958..bdfb12dc5df 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -133,8 +133,9 @@ describe 'project routing' do # labels_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/labels(.:format) projects/autocomplete_sources#labels # milestones_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/milestones(.:format) projects/autocomplete_sources#milestones # commands_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/commands(.:format) projects/autocomplete_sources#commands + # snippets_namespace_project_autocomplete_sources_path GET /:project_id/autocomplete_sources/snippets(.:format) projects/autocomplete_sources#snippets describe Projects::AutocompleteSourcesController, 'routing' do - [:members, :issues, :merge_requests, :labels, :milestones, :commands].each do |action| + [:members, :issues, :merge_requests, :labels, :milestones, :commands, :snippets].each do |action| it "to ##{action}" do expect(get("/gitlab/gitlabhq/autocomplete_sources/#{action}")).to route_to("projects/autocomplete_sources##{action}", namespace_id: 'gitlab', project_id: 'gitlabhq') end @@ -258,10 +259,10 @@ describe 'project routing' do end it 'to #logs_tree' do - expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable') - expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') - expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') - expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable') + expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz') expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz') diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb index 35215e06f5f..b9995818e98 100644 --- a/spec/serializers/commit_entity_spec.rb +++ b/spec/serializers/commit_entity_spec.rb @@ -1,10 +1,11 @@ require 'spec_helper' describe CommitEntity do + SIGNATURE_HTML = 'TEST'.freeze + let(:entity) do described_class.new(commit, request: request) end - let(:request) { double('request') } let(:project) { create(:project, :repository) } let(:commit) { project.commit } @@ -12,7 +13,11 @@ describe CommitEntity do subject { entity.as_json } before do + render = double('render') + allow(render).to receive(:call).and_return(SIGNATURE_HTML) + allow(request).to receive(:project).and_return(project) + allow(request).to receive(:render).and_return(render) end context 'when commit author is a user' do @@ -61,7 +66,7 @@ describe CommitEntity do context 'when type is "full"' do let(:entity) do - described_class.new(commit, request: request, type: :full) + described_class.new(commit, request: request, type: :full, pipeline_ref: project.default_branch, pipeline_project: project) end it 'exposes extra properties' do @@ -70,6 +75,25 @@ describe CommitEntity do expect(subject.fetch(:description_html)).not_to be_nil expect(subject.fetch(:title_html)).not_to be_nil end + + context 'when commit has signature' do + let(:commit) { project.commit(TestEnv::BRANCH_SHA['signed-commits']) } + + it 'exposes "signature_html"' do + expect(request.render).to receive(:call) + expect(subject.fetch(:signature_html)).to be SIGNATURE_HTML + end + end + + context 'when commit has pipeline' do + before do + create(:ci_pipeline, project: project, sha: commit.id) + end + + it 'exposes "pipeline_status_path"' do + expect(subject.fetch(:pipeline_status_path)).not_to be_nil + end + end end context 'when commit_url_params is set' do diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb index 3d90ce44dfb..7497b8f27bd 100644 --- a/spec/serializers/diff_file_entity_spec.rb +++ b/spec/serializers/diff_file_entity_spec.rb @@ -26,6 +26,11 @@ describe DiffFileEntity do ) end + it 'includes viewer' do + expect(subject[:viewer].with_indifferent_access) + .to match_schema('entities/diff_viewer') + end + # Converted diff files from GitHub import does not contain blob file # and content sha. context 'when diff file does not have a blob and content sha' do diff --git a/spec/serializers/diff_viewer_entity_spec.rb b/spec/serializers/diff_viewer_entity_spec.rb new file mode 100644 index 00000000000..66ac6ef2adc --- /dev/null +++ b/spec/serializers/diff_viewer_entity_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe DiffViewerEntity do + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:commit) { project.commit(sample_commit.id) } + let(:diff_refs) { commit.diff_refs } + let(:diff) { commit.raw_diffs.first } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } + let(:viewer) { diff_file.simple_viewer } + + subject { described_class.new(viewer).as_json } + + it 'serializes diff file viewer' do + expect(subject.with_indifferent_access).to match_schema('entities/diff_viewer') + end +end diff --git a/spec/serializers/discussion_entity_spec.rb b/spec/serializers/discussion_entity_spec.rb index 378540a35b6..0590304e832 100644 --- a/spec/serializers/discussion_entity_spec.rb +++ b/spec/serializers/discussion_entity_spec.rb @@ -36,6 +36,13 @@ describe DiscussionEntity do ) end + it 'resolved_by matches note_user_entity schema' do + Notes::ResolveService.new(note.project, user).execute(note) + + expect(subject[:resolved_by].with_indifferent_access) + .to match_schema('entities/note_user_entity') + end + context 'when is LegacyDiffDiscussion' do let(:project) { create(:project) } let(:merge_request) { create(:merge_request, source_project: project) } diff --git a/spec/services/issues/related_branches_service_spec.rb b/spec/services/issues/related_branches_service_spec.rb new file mode 100644 index 00000000000..c2e1eba6a63 --- /dev/null +++ b/spec/services/issues/related_branches_service_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Issues::RelatedBranchesService do + let(:user) { create(:admin) } + let(:issue) { create(:issue) } + + subject { described_class.new(issue.project, user) } + + describe '#execute' do + before do + allow(issue.project.repository).to receive(:branch_names).and_return(["mpempe", "#{issue.iid}mepmep", issue.to_branch_name, "#{issue.iid}-branch"]) + end + + it "selects the right branches when there are no referenced merge requests" do + expect(subject.execute(issue)).to eq([issue.to_branch_name, "#{issue.iid}-branch"]) + end + + it "selects the right branches when there is a referenced merge request" do + merge_request = create(:merge_request, { description: "Closes ##{issue.iid}", + source_project: issue.project, + source_branch: "#{issue.iid}-branch" }) + merge_request.create_cross_references!(user) + + referenced_merge_requests = Issues::ReferencedMergeRequestsService + .new(issue.project, user) + .referenced_merge_requests(issue) + + expect(referenced_merge_requests).not_to be_empty + expect(subject.execute(issue)).to eq([issue.to_branch_name]) + end + + it 'excludes stable branches from the related branches' do + allow(issue.project.repository).to receive(:branch_names) + .and_return(["#{issue.iid}-0-stable"]) + + expect(subject.execute(issue)).to eq [] + end + end +end diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb index 14ba6b7bed2..cea5ea125b9 100644 --- a/spec/services/notification_recipient_service_spec.rb +++ b/spec/services/notification_recipient_service_spec.rb @@ -10,27 +10,50 @@ describe NotificationRecipientService do let(:issue) { create(:issue, project: project, assignees: [assignee]) } let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) } - def create_watcher - watcher = create(:user) - create(:notification_setting, source: project, user: watcher, level: :watch) + shared_examples 'no N+1 queries' do + it 'avoids N+1 queries', :request_store do + create_user - other_projects.each do |other_project| - create(:notification_setting, source: other_project, user: watcher, level: :watch) + service.build_new_note_recipients(note) + + control_count = ActiveRecord::QueryRecorder.new do + service.build_new_note_recipients(note) + end + + create_user + + expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count) end end - it 'avoids N+1 queries', :request_store do - create_watcher + context 'when there are multiple watchers' do + def create_user + watcher = create(:user) + create(:notification_setting, source: project, user: watcher, level: :watch) + + other_projects.each do |other_project| + create(:notification_setting, source: other_project, user: watcher, level: :watch) + end + end - service.build_new_note_recipients(note) + include_examples 'no N+1 queries' + end - control_count = ActiveRecord::QueryRecorder.new do - service.build_new_note_recipients(note) + context 'when there are multiple subscribers' do + def create_user + subscriber = create(:user) + issue.subscriptions.create(user: subscriber, project: project, subscribed: true) end - create_watcher + include_examples 'no N+1 queries' - expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count) + context 'when the project is private' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + include_examples 'no N+1 queries' + end end end end diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index e98df375d48..373fe7cb7dd 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -148,7 +148,7 @@ describe Projects::AutocompleteService do let!(:label1) { create(:label, project: project) } let!(:label2) { create(:label, project: project) } let!(:sub_group_label) { create(:group_label, group: sub_group) } - let!(:parent_group_label) { create(:group_label, group: group.parent) } + let!(:parent_group_label) { create(:group_label, group: group.parent, group_id: group.id) } before do create(:group_member, group: group, user: user) @@ -156,7 +156,7 @@ describe Projects::AutocompleteService do it 'returns labels from project and ancestor groups' do service = described_class.new(project, user) - results = service.labels_as_hash + results = service.labels_as_hash(nil) expected_labels = [label1, label2, parent_group_label] expect_labels_to_equal(results, expected_labels) diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 695b9980548..d58ff2cedc0 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -340,6 +340,27 @@ describe Projects::UpdateService do call_service end end + + context 'when updating #pages_access_level' do + subject(:call_service) do + update_project(project, admin, project_feature_attributes: { pages_access_level: ProjectFeature::PRIVATE }) + end + + it 'updates the attribute' do + expect { call_service } + .to change { project.project_feature.pages_access_level } + .to(ProjectFeature::PRIVATE) + end + + it 'calls Projects::UpdatePagesConfigurationService' do + expect(Projects::UpdatePagesConfigurationService) + .to receive(:new) + .with(project) + .and_call_original + + call_service + end + end end describe '#run_auto_devops_pipeline?' do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index f4b7cb8c90a..a18126ee339 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -324,7 +324,7 @@ describe SystemNoteService do end it "posts the 'merge when pipeline succeeds' system note" do - expect(subject.note).to eq "canceled the automatic merge" + expect(subject.note).to eq "canceled the automatic merge" end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d1337325973..cd69160be10 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -34,6 +34,11 @@ Rainbow.enabled = false # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. # Requires helpers, and shared contexts/examples first since they're used in other support files + +# Load these first since they may be required by other helpers +require Rails.root.join("spec/support/helpers/git_helpers.rb") + +# Then the rest Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb index c01897ed1a1..9f27502aa52 100644 --- a/spec/support/helpers/reference_parser_helpers.rb +++ b/spec/support/helpers/reference_parser_helpers.rb @@ -3,7 +3,7 @@ module ReferenceParserHelpers Nokogiri::HTML.fragment('<a></a>').children[0] end - shared_examples 'no N+1 queries' do + shared_examples 'no project N+1 queries' do it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do context = Banzai::RenderContext.new(project, user) @@ -19,6 +19,10 @@ module ReferenceParserHelpers expect(actual.count).to be <= control.count expect(actual.cached_count).to be <= control.cached_count end + end + + shared_examples 'no N+1 queries' do + it_behaves_like 'no project N+1 queries' it 'avoids N+1 queries in #records_for_nodes', :request_store do context = Banzai::RenderContext.new(project, user) diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb index 22f712f3fcf..b0bf942aa09 100644 --- a/spec/support/services/clusters/create_service_shared.rb +++ b/spec/support/services/clusters/create_service_shared.rb @@ -30,10 +30,6 @@ shared_context 'invalid cluster create params' do end shared_examples 'create cluster service success' do - before do - stub_feature_flags(rbac_clusters: false) - end - it 'creates a cluster object and performs a worker' do expect(ClusterProvisionWorker).to receive(:perform_async) diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb index ea974355050..b999a6fd5b6 100644 --- a/spec/workers/prune_old_events_worker_spec.rb +++ b/spec/workers/prune_old_events_worker_spec.rb @@ -4,23 +4,29 @@ describe PruneOldEventsWorker do describe '#perform' do let(:user) { create(:user) } - let!(:expired_event) { create(:event, :closed, author: user, created_at: 13.months.ago) } - let!(:not_expired_event) { create(:event, :closed, author: user, created_at: 1.day.ago) } - let!(:exactly_12_months_event) { create(:event, :closed, author: user, created_at: 12.months.ago) } + let!(:expired_event) { create(:event, :closed, author: user, created_at: 25.months.ago) } + let!(:not_expired_1_day_event) { create(:event, :closed, author: user, created_at: 1.day.ago) } + let!(:not_expired_13_month_event) { create(:event, :closed, author: user, created_at: 13.months.ago) } + let!(:not_expired_2_years_event) { create(:event, :closed, author: user, created_at: 2.years.ago) } - it 'prunes events older than 12 months' do + it 'prunes events older than 2 years' do expect { subject.perform }.to change { Event.count }.by(-1) expect(Event.find_by(id: expired_event.id)).to be_nil end it 'leaves fresh events' do subject.perform - expect(not_expired_event.reload).to be_present + expect(not_expired_1_day_event.reload).to be_present end - it 'leaves events from exactly 12 months ago' do + it 'leaves events from 13 months ago' do subject.perform - expect(exactly_12_months_event).to be_present + expect(not_expired_13_month_event.reload).to be_present + end + + it 'leaves events from 2 years ago' do + subject.perform + expect(not_expired_2_years_event).to be_present end end end diff --git a/vendor/gitignore/Global/JetBrains.gitignore b/vendor/gitignore/Global/JetBrains.gitignore index 343be1a3b8e..72f4d988a19 100644 --- a/vendor/gitignore/Global/JetBrains.gitignore +++ b/vendor/gitignore/Global/JetBrains.gitignore @@ -61,3 +61,6 @@ fabric.properties # Editor-based Rest Client .idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index 3a4c8581b3a..c221276ebae 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -20,7 +20,7 @@ coverage # nyc test coverage .nyc_output -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore index ff87d483645..753f2b954ff 100644 --- a/vendor/gitignore/TeX.gitignore +++ b/vendor/gitignore/TeX.gitignore @@ -64,6 +64,9 @@ acs-*.bib # changes *.soc +# comment +*.cut + # cprotect *.cpt @@ -205,6 +208,9 @@ pythontex-files-*/ # easy-todo *.lod +# xcolor +*.xcp + # xmpincl *.xmpi diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index 94b41b913fb..4d13c54854e 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -4,6 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files +*.rsuser *.suo *.user *.userosscache @@ -76,6 +77,7 @@ StyleCopReport.xml *.tlh *.tmp *.tmp_proj +*_wpftmp.csproj *.log *.vspscc *.vssscc @@ -290,8 +292,8 @@ paket-files/ .idea/ *.sln.iml -# CodeRush -.cr/ +# CodeRush personal settings +.cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml index 4ea5b44c59c..049ffcc3407 100644 --- a/vendor/jupyter/values.yaml +++ b/vendor/jupyter/values.yaml @@ -13,6 +13,10 @@ auth: singleuser: defaultUrl: "/lab" + lifecycleHooks: + postStart: + exec: + command: ["git", "clone", "https://gitlab.com/gitlab-org/nurtch-demo.git", "DevOps-Runbook-Demo"] ingress: enabled: true diff --git a/vendor/licenses.csv b/vendor/licenses.csv index 6adcb28f736..9083fe076c3 100644 --- a/vendor/licenses.csv +++ b/vendor/licenses.csv @@ -1,31 +1,102 @@ -@gitlab-org/gitlab-svgs,1.29.0,MIT -@gitlab-org/gitlab-ui,1.1.0,MIT +@babel/code-frame,7.0.0,MIT +@babel/core,7.1.2,MIT +@babel/generator,7.1.2,MIT +@babel/helper-annotate-as-pure,7.0.0,MIT +@babel/helper-builder-binary-assignment-operator-visitor,7.1.0,MIT +@babel/helper-call-delegate,7.1.0,MIT +@babel/helper-define-map,7.1.0,MIT +@babel/helper-explode-assignable-expression,7.1.0,MIT +@babel/helper-function-name,7.1.0,MIT +@babel/helper-get-function-arity,7.0.0,MIT +@babel/helper-hoist-variables,7.0.0,MIT +@babel/helper-member-expression-to-functions,7.0.0,MIT +@babel/helper-module-imports,7.0.0,MIT +@babel/helper-module-transforms,7.1.0,MIT +@babel/helper-optimise-call-expression,7.0.0,MIT +@babel/helper-plugin-utils,7.0.0,MIT +@babel/helper-regex,7.0.0,MIT +@babel/helper-remap-async-to-generator,7.1.0,MIT +@babel/helper-replace-supers,7.1.0,MIT +@babel/helper-simple-access,7.1.0,MIT +@babel/helper-split-export-declaration,7.0.0,MIT +@babel/helper-wrap-function,7.1.0,MIT +@babel/helpers,7.1.2,MIT +@babel/highlight,7.0.0,MIT +@babel/parser,7.1.2,MIT +@babel/plugin-proposal-async-generator-functions,7.1.0,MIT +@babel/plugin-proposal-class-properties,7.1.0,MIT +@babel/plugin-proposal-json-strings,7.0.0,MIT +@babel/plugin-proposal-object-rest-spread,7.0.0,MIT +@babel/plugin-proposal-optional-catch-binding,7.0.0,MIT +@babel/plugin-proposal-unicode-property-regex,7.0.0,MIT +@babel/plugin-syntax-async-generators,7.0.0,MIT +@babel/plugin-syntax-class-properties,7.0.0,MIT +@babel/plugin-syntax-dynamic-import,7.0.0,MIT +@babel/plugin-syntax-import-meta,7.0.0,MIT +@babel/plugin-syntax-json-strings,7.0.0,MIT +@babel/plugin-syntax-object-rest-spread,7.0.0,MIT +@babel/plugin-syntax-optional-catch-binding,7.0.0,MIT +@babel/plugin-transform-arrow-functions,7.0.0,MIT +@babel/plugin-transform-async-to-generator,7.1.0,MIT +@babel/plugin-transform-block-scoped-functions,7.0.0,MIT +@babel/plugin-transform-block-scoping,7.0.0,MIT +@babel/plugin-transform-classes,7.1.0,MIT +@babel/plugin-transform-computed-properties,7.0.0,MIT +@babel/plugin-transform-destructuring,7.1.2,MIT +@babel/plugin-transform-dotall-regex,7.0.0,MIT +@babel/plugin-transform-duplicate-keys,7.0.0,MIT +@babel/plugin-transform-exponentiation-operator,7.1.0,MIT +@babel/plugin-transform-for-of,7.0.0,MIT +@babel/plugin-transform-function-name,7.1.0,MIT +@babel/plugin-transform-literals,7.0.0,MIT +@babel/plugin-transform-modules-amd,7.1.0,MIT +@babel/plugin-transform-modules-commonjs,7.1.0,MIT +@babel/plugin-transform-modules-systemjs,7.0.0,MIT +@babel/plugin-transform-modules-umd,7.1.0,MIT +@babel/plugin-transform-new-target,7.0.0,MIT +@babel/plugin-transform-object-super,7.1.0,MIT +@babel/plugin-transform-parameters,7.1.0,MIT +@babel/plugin-transform-regenerator,7.0.0,MIT +@babel/plugin-transform-shorthand-properties,7.0.0,MIT +@babel/plugin-transform-spread,7.0.0,MIT +@babel/plugin-transform-sticky-regex,7.0.0,MIT +@babel/plugin-transform-template-literals,7.0.0,MIT +@babel/plugin-transform-typeof-symbol,7.0.0,MIT +@babel/plugin-transform-unicode-regex,7.0.0,MIT +@babel/preset-env,7.1.0,MIT +@babel/template,7.1.2,MIT +@babel/traverse,7.1.0,MIT +@babel/types,7.1.2,MIT +@gitlab-org/gitlab-svgs,1.31.0,MIT +@gitlab-org/gitlab-ui,1.8.0,MIT @sindresorhus/is,0.7.0,MIT @types/jquery,2.0.48,MIT -@vue/component-compiler-utils,1.2.1,MIT -@webassemblyjs/ast,1.5.13,MIT -@webassemblyjs/floating-point-hex-parser,1.5.13,MIT -@webassemblyjs/helper-api-error,1.5.13,MIT -@webassemblyjs/helper-buffer,1.5.13,MIT -@webassemblyjs/helper-code-frame,1.5.13,MIT -@webassemblyjs/helper-fsm,1.5.13,ISC -@webassemblyjs/helper-module-context,1.5.13,MIT -@webassemblyjs/helper-wasm-bytecode,1.5.13,MIT -@webassemblyjs/helper-wasm-section,1.5.13,MIT -@webassemblyjs/ieee754,1.5.13,MIT -@webassemblyjs/leb128,1.5.13,MIT -@webassemblyjs/utf8,1.5.13,MIT -@webassemblyjs/wasm-edit,1.5.13,MIT -@webassemblyjs/wasm-gen,1.5.13,MIT -@webassemblyjs/wasm-opt,1.5.13,MIT -@webassemblyjs/wasm-parser,1.5.13,MIT -@webassemblyjs/wast-parser,1.5.13,MIT -@webassemblyjs/wast-printer,1.5.13,MIT +@vue/component-compiler-utils,2.2.0,MIT +@webassemblyjs/ast,1.7.6,MIT +@webassemblyjs/floating-point-hex-parser,1.7.6,MIT +@webassemblyjs/helper-api-error,1.7.6,MIT +@webassemblyjs/helper-buffer,1.7.6,MIT +@webassemblyjs/helper-code-frame,1.7.6,MIT +@webassemblyjs/helper-fsm,1.7.6,ISC +@webassemblyjs/helper-module-context,1.7.6,MIT +@webassemblyjs/helper-wasm-bytecode,1.7.6,MIT +@webassemblyjs/helper-wasm-section,1.7.6,MIT +@webassemblyjs/ieee754,1.7.6,MIT +@webassemblyjs/leb128,1.7.6,MIT +@webassemblyjs/utf8,1.7.6,MIT +@webassemblyjs/wasm-edit,1.7.6,MIT +@webassemblyjs/wasm-gen,1.7.6,MIT +@webassemblyjs/wasm-opt,1.7.6,MIT +@webassemblyjs/wasm-parser,1.7.6,MIT +@webassemblyjs/wast-parser,1.7.6,MIT +@webassemblyjs/wast-printer,1.7.6,MIT +@xtuc/ieee754,1.2.0,New BSD +@xtuc/long,4.2.1,Apache 2.0 RedCloth,4.3.2,MIT -abbrev,1.1.1,ISC -accepts,1.3.4,MIT +abbrev,1.0.9,ISC +accepts,1.3.5,MIT ace-rails-ap,4.1.2,MIT -acorn,5.7.1,MIT +acorn,5.7.3,MIT acorn-dynamic-import,3.0.0,MIT actionmailer,4.2.10,MIT actionpack,4.2.10,MIT @@ -37,8 +108,9 @@ activesupport,4.2.10,MIT acts-as-taggable-on,5.0.0,MIT addressable,2.5.2,Apache 2.0 aes_key_wrap,1.0.1,MIT -ajv,6.1.1,MIT -ajv-keywords,3.1.0,MIT +ajv,6.5.3,MIT +ajv-errors,1.0.0,MIT +ajv-keywords,3.2.0,MIT akismet,2.0.0,MIT ansi-escapes,1.4.0,MIT ansi-escapes,3.0.0,MIT @@ -73,78 +145,10 @@ autosize,4.0.0,MIT axiom-types,0.1.1,MIT axios,0.17.1,MIT babel-code-frame,6.26.0,MIT -babel-core,6.26.3,MIT -babel-generator,6.26.0,MIT -babel-helper-bindify-decorators,6.24.1,MIT -babel-helper-builder-binary-assignment-operator-visitor,6.24.1,MIT -babel-helper-call-delegate,6.24.1,MIT -babel-helper-define-map,6.26.0,MIT -babel-helper-explode-assignable-expression,6.24.1,MIT -babel-helper-explode-class,6.24.1,MIT -babel-helper-function-name,6.24.1,MIT -babel-helper-get-function-arity,6.24.1,MIT -babel-helper-hoist-variables,6.24.1,MIT -babel-helper-optimise-call-expression,6.24.1,MIT -babel-helper-regex,6.26.0,MIT -babel-helper-remap-async-to-generator,6.24.1,MIT -babel-helper-replace-supers,6.24.1,MIT -babel-helpers,6.24.1,MIT -babel-loader,7.1.5,MIT -babel-messages,6.23.0,MIT -babel-plugin-check-es2015-constants,6.22.0,MIT -babel-plugin-syntax-async-functions,6.13.0,MIT -babel-plugin-syntax-async-generators,6.13.0,MIT -babel-plugin-syntax-class-properties,6.13.0,MIT -babel-plugin-syntax-decorators,6.13.0,MIT -babel-plugin-syntax-dynamic-import,6.18.0,MIT -babel-plugin-syntax-exponentiation-operator,6.13.0,MIT -babel-plugin-syntax-object-rest-spread,6.13.0,MIT -babel-plugin-syntax-trailing-function-commas,6.22.0,MIT -babel-plugin-transform-async-generator-functions,6.24.1,MIT -babel-plugin-transform-async-to-generator,6.24.1,MIT -babel-plugin-transform-class-properties,6.24.1,MIT -babel-plugin-transform-decorators,6.24.1,MIT -babel-plugin-transform-define,1.3.0,MIT -babel-plugin-transform-es2015-arrow-functions,6.22.0,MIT -babel-plugin-transform-es2015-block-scoped-functions,6.22.0,MIT -babel-plugin-transform-es2015-block-scoping,6.26.0,MIT -babel-plugin-transform-es2015-classes,6.24.1,MIT -babel-plugin-transform-es2015-computed-properties,6.24.1,MIT -babel-plugin-transform-es2015-destructuring,6.23.0,MIT -babel-plugin-transform-es2015-duplicate-keys,6.24.1,MIT -babel-plugin-transform-es2015-for-of,6.23.0,MIT -babel-plugin-transform-es2015-function-name,6.24.1,MIT -babel-plugin-transform-es2015-literals,6.22.0,MIT -babel-plugin-transform-es2015-modules-amd,6.24.1,MIT -babel-plugin-transform-es2015-modules-commonjs,6.26.0,MIT -babel-plugin-transform-es2015-modules-systemjs,6.24.1,MIT -babel-plugin-transform-es2015-modules-umd,6.24.1,MIT -babel-plugin-transform-es2015-object-super,6.24.1,MIT -babel-plugin-transform-es2015-parameters,6.24.1,MIT -babel-plugin-transform-es2015-shorthand-properties,6.24.1,MIT -babel-plugin-transform-es2015-spread,6.22.0,MIT -babel-plugin-transform-es2015-sticky-regex,6.24.1,MIT -babel-plugin-transform-es2015-template-literals,6.22.0,MIT -babel-plugin-transform-es2015-typeof-symbol,6.23.0,MIT -babel-plugin-transform-es2015-unicode-regex,6.24.1,MIT -babel-plugin-transform-exponentiation-operator,6.24.1,MIT -babel-plugin-transform-object-rest-spread,6.23.0,MIT -babel-plugin-transform-regenerator,6.26.0,MIT -babel-plugin-transform-strict-mode,6.24.1,MIT +babel-loader,8.0.4,MIT babel-polyfill,6.23.0,MIT -babel-preset-es2015,6.24.1,MIT -babel-preset-es2016,6.24.1,MIT -babel-preset-es2017,6.24.1,MIT -babel-preset-latest,6.24.1,MIT -babel-preset-stage-2,6.24.1,MIT -babel-preset-stage-3,6.24.1,MIT -babel-register,6.26.0,MIT babel-runtime,6.26.0,MIT -babel-template,6.26.0,MIT -babel-traverse,6.26.0,MIT -babel-types,6.26.0,MIT babosa,1.0.2,MIT -babylon,6.18.0,MIT balanced-match,1.0.0,MIT base,0.11.2,MIT base32,0.3.2,MIT @@ -152,8 +156,8 @@ base64-js,1.2.3,MIT batch-loader,1.2.1,MIT bcrypt,3.1.12,MIT bcrypt_pbkdf,1.0.0,MIT -bfj-node4,5.2.1,MIT -big.js,3.1.3,MIT +bfj,6.1.1,MIT +big.js,3.2.0,MIT binary-extensions,1.11.0,MIT binaryextensions,2.1.1,MIT bindata,2.4.3,ruby @@ -161,7 +165,7 @@ blackst0ne-mermaid,7.1.0-fixed,MIT bluebird,3.5.1,MIT bn.js,4.11.8,MIT body-parser,1.18.2,MIT -bootstrap,4.1.2,MIT +bootstrap,4.1.1,MIT bootstrap-vue,2.0.0-rc.11,MIT bootstrap_form,2.7.0,MIT brace-expansion,1.1.11,MIT @@ -174,6 +178,7 @@ browserify-des,1.0.0,MIT browserify-rsa,4.0.1,MIT browserify-sign,4.0.4,ISC browserify-zlib,0.2.0,MIT +browserslist,4.1.1,MIT buffer,4.9.1,MIT buffer-from,1.0.0,MIT buffer-xor,1.0.3,MIT @@ -181,10 +186,12 @@ builder,3.2.3,MIT builtin-status-codes,3.0.0,MIT bytes,3.0.0,MIT cacache,10.0.4,ISC +cacache,11.2.0,ISC cache-base,1.0.1,MIT cache-loader,1.2.2,MIT cacheable-request,2.1.4,MIT camelcase,4.1.0,MIT +caniuse-lite,1.0.30000888,CC-BY-4.0 carrierwave,1.2.3,MIT cause,0.1,MIT chalk,1.1.3,MIT @@ -216,31 +223,31 @@ codesandbox-import-util-types,1.2.11,LGPL codesandbox-import-utils,1.2.11,LGPL coercible,1.0.0,MIT collection-visit,1.0.0,MIT -color-convert,1.9.1,MIT -color-name,1.1.2,MIT +color-convert,1.9.3,MIT +color-name,1.1.3,MIT commander,2.13.0,MIT -commander,2.15.1,MIT +commander,2.18.0,MIT commondir,1.0.1,MIT -commonmarker,0.17.8,MIT +commonmarker,0.17.13,MIT component-emitter,1.2.1,MIT -compression-webpack-plugin,1.1.11,MIT +compression-webpack-plugin,2.0.0,MIT concat-map,0.0.1,MIT concat-stream,1.6.2,MIT concurrent-ruby-ext,1.0.5,MIT -connection_pool,2.2.1,MIT +connection_pool,2.2.2,MIT console-browserify,1.1.0,MIT console-control-strings,1.1.0,ISC consolidate,0.15.1,MIT constants-browserify,1.0.0,MIT content-disposition,0.5.2,MIT content-type,1.0.4,MIT -convert-source-map,1.5.1,MIT +convert-source-map,1.6.0,MIT cookie,0.3.1,MIT cookie-signature,1.0.6,MIT copy-concurrently,1.0.5,ISC copy-descriptor,0.1.1,MIT core-js,2.3.0,MIT -core-js,2.5.3,MIT +core-js,2.5.7,MIT core-util-is,1.0.2,MIT crack,0.4.3,MIT crass,1.0.4,MIT @@ -249,7 +256,6 @@ create-hash,1.1.3,MIT create-hmac,1.1.6,MIT creole,0.5.0,ruby cropper,2.3.0,MIT -cross-spawn,5.1.0,MIT cross-spawn,6.0.5,MIT crypt,0.0.2,New BSD crypto-browserify,3.12.0,MIT @@ -296,9 +302,9 @@ date-now,0.1.4,MIT dateformat,3.0.3,MIT de-indent,1.0.2,MIT debug,2.6.9,MIT -debug,3.1.0,MIT +debug,3.2.5,MIT debugger-ruby_core_source,1.3.8,MIT -decamelize,1.2.0,MIT +decamelize,2.0.0,MIT deckar01-task_list,2.0.0,MIT declarative,0.0.10,MIT declarative-option,0.1.0,MIT @@ -306,16 +312,17 @@ decode-uri-component,0.2.0,MIT decompress-response,3.3.0,MIT deep-extend,0.4.2,MIT default_value_for,3.0.2,MIT +define-properties,1.1.3,MIT define-property,0.2.5,MIT define-property,1.0.0,MIT define-property,2.0.2,MIT delegate,3.1.2,MIT delegates,1.0.0,MIT depd,1.1.1,MIT +depd,1.1.2,MIT des.js,1.0.0,MIT descendants_tracker,0.0.4,MIT destroy,1.0.4,MIT -detect-indent,4.0.0,MIT detect-libc,1.0.3,Apache 2.0 device_detector,1.0.0,LGPL devise,4.4.3,MIT @@ -342,7 +349,8 @@ duplexify,3.5.3,MIT ed25519,1.2.4,MIT editions,1.3.4,MIT ee-first,1.1.1,MIT -ejs,2.5.9,Apache 2.0 +ejs,2.6.1,Apache 2.0 +electron-to-chromium,1.3.73,ISC elliptic,6.4.0,MIT email_reply_trimmer,0.1.6,MIT emoji-unicode-version,0.2.1,MIT @@ -356,11 +364,13 @@ entities,1.1.1,Simplified BSD equalizer,0.0.11,MIT errno,0.1.7,MIT erubis,2.7.0,MIT +es-abstract,1.12.0,MIT +es-to-primitive,1.1.1,MIT es6-promise,3.0.2,MIT escape-html,1.0.3,MIT escape-string-regexp,1.0.5,MIT escape_utils,1.1.1,MIT -eslint-scope,3.7.1,Simplified BSD +eslint-scope,4.0.0,Simplified BSD esrecurse,4.2.1,Simplified BSD estraverse,4.2.0,Simplified BSD esutils,2.0.2,Simplified BSD @@ -370,11 +380,11 @@ eve-raphael,0.5.0,Apache 2.0 events,1.1.1,MIT evp_bytestokey,1.0.3,MIT excon,0.62.0,MIT -execa,0.7.0,MIT +execa,0.10.0,MIT execjs,2.6.0,MIT expand-brackets,2.1.4,MIT exports-loader,0.7.0,MIT -express,4.16.2,MIT +express,4.16.3,MIT expression_parser,0.9.0,MIT extend-shallow,2.0.1,MIT extend-shallow,3.0.2,MIT @@ -384,19 +394,22 @@ extglob,2.0.4,MIT faraday,0.12.2,MIT faraday_middleware,0.12.2,MIT faraday_middleware-multi_json,0.0.6,MIT -fast-deep-equal,1.0.0,MIT +fast-deep-equal,2.0.1,MIT fast-json-stable-stringify,2.0.0,MIT fast_blank,1.0.0,MIT fast_gettext,1.6.0,"MIT,ruby" fastparse,1.1.1,MIT ffi,1.9.25,New BSD +figgy-pudding,3.5.1,ISC figures,2.0.0,MIT -file-loader,1.1.11,MIT -filesize,3.6.0,New BSD +file-loader,2.0.0,MIT +filesize,3.6.1,New BSD fill-range,4.0.0,MIT -finalhandler,1.1.0,MIT +finalhandler,1.1.1,MIT find-cache-dir,1.0.0,MIT +find-cache-dir,2.0.0,MIT find-up,2.1.0,MIT +find-up,3.0.0,MIT flipper,0.13.0,MIT flipper-active_record,0.13.0,MIT flipper-active_support_cache_store,0.13.0,MIT @@ -424,6 +437,7 @@ fs-minipass,1.2.5,ISC fs-write-stream-atomic,1.0.10,ISC fs.realpath,1.0.0,ISC fsevents,1.2.4,MIT +function-bind,1.1.1,MIT fuzzaldrin-plus,0.5.0,MIT gauge,2.7.4,ISC gemojione,3.3.0,MIT @@ -433,19 +447,20 @@ get-value,2.0.6,MIT get_process_mem,0.2.0,MIT gettext_i18n_rails,1.8.0,MIT gettext_i18n_rails_js,1.3.0,MIT -gitaly-proto,0.117.0,MIT +gitaly-proto,0.118.1,MIT github-linguist,5.3.3,MIT github-markup,1.7.0,MIT gitlab-flowdock-git-hook,1.0.1,MIT gitlab-gollum-lib,4.2.7.5,MIT gitlab-grit,2.8.2,MIT gitlab-markup,1.6.4,MIT +gitlab-sidekiq-fetcher,0.3.0,LGPL gitlab_omniauth-ldap,2.0.4,MIT -glob,7.1.2,ISC +glob,7.1.3,ISC glob-parent,3.1.0,ISC global-modules-path,2.1.0,Apache 2.0 globalid,0.4.1,MIT -globals,9.18.0,MIT +globals,11.7.0,MIT gollum-grit_adapter,1.0.1,MIT gon,6.2.0,MIT good-listener,1.2.2,MIT @@ -456,7 +471,7 @@ googleauth,0.6.2,Apache 2.0 got,8.3.0,MIT gpgme,2.0.13,LGPL-2.1+ graceful-fs,4.1.11,ISC -grape,1.0.3,MIT +grape,1.1.0,MIT grape-entity,0.7.1,MIT grape-path-helpers,1.0.6,MIT grape_logging,1.7.0,MIT @@ -464,12 +479,14 @@ graphiql-rails,1.4.10,MIT graphlib,2.1.1,MIT graphql,1.8.1,MIT grpc,1.11.0,Apache 2.0 -gzip-size,4.1.0,MIT +gzip-size,5.0.0,MIT hamlit,2.8.8,MIT hangouts-chat,0.0.5,MIT +has,1.0.1,MIT has-ansi,2.0.0,MIT has-flag,3.0.0,MIT has-symbol-support-x,1.3.0,MIT +has-symbols,1.0.0,MIT has-to-string-tag-x,1.3.0,MIT has-unicode,2.0.1,ISC has-value,0.3.1,MIT @@ -486,7 +503,7 @@ he,1.1.1,MIT health_check,2.6.0,MIT hipchat,1.5.2,MIT hmac-drbg,1.0.1,MIT -home-or-tmp,2.0.0,MIT +hoopy,0.1.4,MIT html-pipeline,2.8.4,MIT html2text,0.2.0,MIT htmlentities,4.3.4,MIT @@ -522,25 +539,26 @@ inherits,2.0.1,ISC inherits,2.0.3,ISC ini,1.3.5,ISC inquirer,3.0.6,MIT -inquirer,6.0.0,MIT +inquirer,6.2.0,MIT interpret,1.1.0,MIT into-stream,3.1.0,MIT -invariant,2.2.2,New BSD -invert-kv,1.0.0,MIT -ipaddr.js,1.6.0,MIT +invariant,2.2.4,MIT +invert-kv,2.0.0,MIT +ipaddr.js,1.8.0,MIT ipaddress,0.8.3,MIT is-accessor-descriptor,0.1.6,MIT is-accessor-descriptor,1.0.0,MIT is-binary-path,1.0.1,MIT is-buffer,1.1.6,MIT +is-callable,1.1.4,MIT is-data-descriptor,0.1.4,MIT is-data-descriptor,1.0.0,MIT +is-date-object,1.0.1,MIT is-descriptor,0.1.6,MIT is-descriptor,1.0.2,MIT is-extendable,0.1.1,MIT is-extendable,1.0.1,MIT is-extglob,2.1.1,MIT -is-finite,1.0.2,MIT is-fullwidth-code-point,1.0.0,MIT is-fullwidth-code-point,2.0.0,MIT is-glob,3.1.0,MIT @@ -553,8 +571,10 @@ is-odd,2.0.0,MIT is-plain-obj,1.1.0,MIT is-plain-object,2.0.4,MIT is-promise,2.1.0,MIT +is-regex,1.0.4,MIT is-retry-allowed,1.1.0,MIT is-stream,1.1.0,MIT +is-symbol,1.0.2,MIT is-windows,1.0.2,MIT isarray,1.0.0,MIT isexe,2.0.0,ISC @@ -569,15 +589,17 @@ jquery-atwho-rails,1.3.2,MIT jquery-ujs,1.2.2,MIT jquery.waitforimages,2.2.0,MIT js-cookie,2.1.3,MIT +js-levenshtein,1.1.4,MIT js-tokens,3.0.2,MIT +js-tokens,4.0.0,MIT js_regex,2.2.1,MIT jsesc,0.5.0,MIT -jsesc,1.3.0,MIT +jsesc,2.5.1,MIT json,1.8.6,ruby json-buffer,3.0.0,MIT json-jwt,1.9.4,MIT json-parse-better-errors,1.0.2,MIT -json-schema-traverse,0.3.1,MIT +json-schema-traverse,0.4.1,MIT json5,0.5.1,MIT jszip,3.1.3,(MIT OR GPL-3.0) jszip-utils,0.0.2,MIT or GPLv3 @@ -586,7 +608,7 @@ kaminari,1.0.1,MIT kaminari-actionview,1.0.1,MIT kaminari-activerecord,1.0.1,MIT kaminari-core,1.0.1,MIT -katex,0.8.3,MIT +katex,0.9.0,MIT keyv,3.0.0,MIT kgio,2.10.0,LGPL-2.1+ kind-of,3.2.2,MIT @@ -595,7 +617,7 @@ kind-of,5.1.0,MIT kind-of,6.0.2,MIT kubeclient,3.1.0,MIT lazy-cache,2.0.2,MIT -lcid,1.0.0,MIT +lcid,2.0.0,MIT licensee,8.9.2,MIT lie,3.1.1,MIT little-plugger,1.1.4,MIT @@ -603,8 +625,8 @@ loader-runner,2.3.0,MIT loader-utils,1.1.0,MIT locale,2.1.2,"ruby,LGPLv3+" locate-path,2.0.0,MIT -lodash,4.17.10,MIT -lodash,4.17.4,MIT +locate-path,3.0.0,MIT +lodash,4.17.11,MIT lodash.camelcase,4.3.0,MIT lodash.clonedeep,4.5.0,MIT lodash.debounce,4.0.8,MIT @@ -615,24 +637,23 @@ lodash.mergewith,4.6.0,MIT lodash.startcase,4.4.0,MIT logging,2.2.2,MIT lograge,0.10.0,MIT -long,3.2.0,Apache 2.0 -long,4.0.0,Apache 2.0 loofah,2.2.2,MIT -loose-envify,1.3.1,MIT +loose-envify,1.4.0,MIT lowercase-keys,1.0.0,MIT lru-cache,4.1.3,ISC lz-string,1.4.4,WTFPL mail,2.7.0,MIT mail_room,0.9.1,MIT -make-dir,1.2.0,MIT +make-dir,1.3.0,MIT mamacro,0.0.3,MIT +map-age-cleaner,0.1.2,MIT map-cache,0.2.2,MIT map-visit,1.0.0,MIT marked,0.3.12,MIT match-at,0.1.1,MIT md5.js,1.3.4,MIT media-typer,0.3.0,MIT -mem,1.1.0,MIT +mem,4.0.0,MIT memoist,0.16.0,MIT memory-fs,0.4.1,MIT merge-descriptors,1.0.1,MIT @@ -651,7 +672,7 @@ mimemagic,0.3.0,MIT mimic-fn,1.1.0,MIT mimic-response,1.0.0,MIT mini_magick,4.8.0,MIT -mini_mime,1.0.0,MIT +mini_mime,1.0.1,MIT mini_portile2,2.3.0,MIT minimalistic-assert,1.0.0,ISC minimalistic-crypto-utils,1.0.1,MIT @@ -661,20 +682,22 @@ minimist,1.2.0,MIT minipass,2.3.3,ISC minizlib,1.1.0,MIT mississippi,2.0.0,Simplified BSD +mississippi,3.0.0,Simplified BSD mixin-deep,1.3.1,MIT mkdirp,0.5.1,MIT moment,2.19.2,MIT monaco-editor,0.14.3,MIT -monaco-editor-webpack-plugin,1.5.2,MIT +monaco-editor-webpack-plugin,1.5.4,MIT mousetrap,1.4.6,Apache 2.0 mousetrap-rails,1.4.6,"MIT,Apache" move-concurrently,1.0.1,ISC ms,2.0.0,MIT +ms,2.1.1,MIT msgpack,1.2.4,Apache 2.0 multi_json,1.13.1,MIT multi_xml,0.6.0,MIT multipart-post,2.0.0,MIT -mustermann,1.0.2,MIT +mustermann,1.0.3,MIT mustermann-grape,1.0.0,MIT mute-stream,0.0.7,ISC mysql2,0.4.10,MIT @@ -690,6 +713,7 @@ nice-try,1.0.4,MIT node-fetch,1.6.3,MIT node-libs-browser,2.1.0,MIT node-pre-gyp,0.10.0,New BSD +node-releases,1.0.0-alpha.12,CC-BY-4.0 nokogiri,1.8.4,MIT nokogumbo,1.5.0,Apache 2.0 nopt,4.0.1,ISC @@ -705,7 +729,9 @@ oauth,0.5.4,MIT oauth2,1.4.0,MIT object-assign,4.1.1,MIT object-copy,0.1.0,MIT +object-keys,1.0.12,MIT object-visit,1.0.1,MIT +object.getownpropertydescriptors,2.0.3,MIT object.pick,1.3.0,MIT octokit,4.9.0,MIT omniauth,1.8.1,MIT @@ -730,23 +756,27 @@ on-finished,2.3.0,MIT once,1.4.0,ISC onetime,2.0.1,MIT opencollective,1.0.3,MIT -opener,1.4.3,(WTFPL OR MIT) +opener,1.5.1,(WTFPL OR MIT) opn,4.0.2,MIT org-ruby,0.9.12,MIT orm_adapter,0.5.0,MIT os,0.9.6,MIT os-browserify,0.3.0,MIT os-homedir,1.0.2,MIT -os-locale,2.1.0,MIT +os-locale,3.0.1,MIT os-tmpdir,1.0.2,MIT osenv,0.1.5,ISC p-cancelable,0.4.1,MIT +p-defer,1.0.0,MIT p-finally,1.0.0,MIT p-is-promise,1.1.0,MIT p-limit,1.2.0,MIT +p-limit,2.0.0,MIT p-locate,2.0.0,MIT +p-locate,3.0.0,MIT p-timeout,2.0.1,MIT p-try,1.0.0,MIT +p-try,2.0.0,MIT pako,1.0.6,(MIT AND Zlib) parallel-transform,1.1.0,MIT parse-asn1,5.1.0,ISC @@ -757,6 +787,7 @@ path-dirname,1.0.2,MIT path-exists,3.0.0,MIT path-is-absolute,1.0.1,MIT path-key,2.0.1,MIT +path-parse,1.0.6,MIT path-to-regexp,0.1.7,MIT pbkdf2,3.0.14,MIT peek,1.0.1,MIT @@ -765,13 +796,13 @@ peek-mysql2,1.1.0,MIT peek-pg,1.3.0,MIT peek-rblineprof,0.2.0,MIT peek-redis,1.2.0,MIT -peek-sidekiq,1.0.3,MIT pg,0.18.4,"BSD,ruby,GPL" pify,3.0.0,MIT pikaday,1.6.1,MIT pinkie,2.0.4,MIT pinkie-promise,2.0.1,MIT pkg-dir,2.0.0,MIT +pkg-dir,3.0.0,MIT po_to_json,1.0.1,MIT popper.js,1.14.3,MIT posix-character-classes,0.1.1,MIT @@ -786,7 +817,7 @@ postcss-value-parser,3.3.0,MIT premailer,1.10.4,New BSD premailer-rails,1.9.7,MIT prepend-http,2.0.0,MIT -prettier,1.12.1,MIT +prettier,1.13.7,MIT prismjs,1.6.0,MIT private,0.1.8,MIT process,0.11.10,MIT @@ -794,15 +825,16 @@ process-nextick-args,1.0.7,MIT process-nextick-args,2.0.0,MIT prometheus-client-mmap,0.9.4,Apache 2.0 promise-inflight,1.0.1,ISC -proxy-addr,2.0.3,MIT +proxy-addr,2.0.4,MIT prr,1.0.1,MIT pseudomap,1.0.2,ISC public-encrypt,4.0.0,MIT -public_suffix,3.0.2,MIT +public_suffix,3.0.3,MIT pump,2.0.1,MIT +pump,3.0.0,MIT pumpify,1.4.0,MIT punycode,1.3.2,MIT -punycode,1.4.1,MIT +punycode,2.1.1,MIT pyu-ruby-sasl,0.0.3.3,MIT qs,6.5.1,New BSD query-string,5.1.1,MIT @@ -813,7 +845,7 @@ rack-accept,0.4.5,MIT rack-attack,4.4.1,MIT rack-cors,1.0.2,MIT rack-oauth2,1.2.3,MIT -rack-protection,2.0.1,MIT +rack-protection,2.0.3,MIT rack-proxy,0.6.0,MIT rack-test,0.6.3,MIT rails,4.2.10,MIT @@ -851,24 +883,27 @@ redis-namespace,1.6.0,MIT redis-rack,2.0.4,MIT redis-rails,5.0.2,MIT redis-store,1.4.1,MIT -regenerate,1.3.2,MIT +regenerate,1.4.0,MIT +regenerate-unicode-properties,7.0.0,MIT regenerator-runtime,0.10.5,MIT -regenerator-runtime,0.11.0,MIT -regenerator-transform,0.10.1,BSD +regenerator-runtime,0.11.1,MIT +regenerator-transform,0.13.3,MIT regex-not,1.0.2,MIT regexp_parser,0.5.0,MIT regexpu-core,1.0.0,MIT -regexpu-core,2.0.0,MIT +regexpu-core,4.2.0,MIT regjsgen,0.2.0,MIT +regjsgen,0.4.0,MIT regjsparser,0.1.5,Simplified BSD +regjsparser,0.3.0,Simplified BSD remove-trailing-separator,1.1.0,ISC repeat-element,1.1.2,MIT repeat-string,1.6.1,MIT -repeating,2.0.1,MIT representable,3.0.4,MIT request_store,1.3.1,MIT require-directory,2.1.1,MIT require-main-filename,1.0.1,ISC +resolve,1.8.1,MIT resolve-cwd,2.0.0,MIT resolve-from,3.0.0,MIT resolve-url,0.2.1,MIT @@ -882,7 +917,7 @@ rimraf,2.6.2,ISC rinku,2.0.0,ISC ripemd160,2.0.1,MIT rotp,2.1.2,MIT -rouge,3.2.1,MIT +rouge,3.3.0,MIT rqrcode,0.7.0,MIT rqrcode-rails3,0.1.7,MIT ruby-enum,0.7.2,MIT @@ -913,15 +948,16 @@ sass-rails,5.0.6,MIT sawyer,0.8.1,MIT sax,1.2.4,ISC schema-utils,0.4.5,MIT +schema-utils,1.0.0,MIT seed-fu,2.3.7,MIT select,1.1.2,MIT select2,3.5.2-browserify,Apache* select2-rails,3.5.9.3,MIT -semver,5.5.0,ISC -send,0.16.1,MIT +semver,5.5.1,ISC +send,0.16.2,MIT sentry-raven,2.7.2,Apache 2.0 serialize-javascript,1.4.0,New BSD -serve-static,1.13.1,MIT +serve-static,1.13.2,MIT set-blocking,2.0.0,ISC set-getter,0.1.0,MIT set-immediate-shim,1.0.1,MIT @@ -936,13 +972,11 @@ sha.js,2.4.10,MIT sha1,1.1.1,New BSD shebang-command,1.2.0,MIT shebang-regex,1.0.0,MIT -sidekiq,5.1.3,LGPL +sidekiq,5.2.1,LGPL sidekiq-cron,0.6.0,MIT -sidekiq-limit_fetch,3.4.0,MIT signal-exit,3.0.2,ISC signet,0.8.1,Apache 2.0 slack-notifier,1.5.1,MIT -slash,1.0.0,MIT smooshpack,0.0.48,LGPL snapdragon,0.8.1,MIT snapdragon-node,2.1.1,MIT @@ -954,7 +988,6 @@ source-map,0.5.0,New BSD source-map,0.5.7,New BSD source-map,0.6.1,New BSD source-map-resolve,0.5.1,MIT -source-map-support,0.4.18,MIT source-map-url,0.4.0,MIT split-string,3.1.0,MIT sprockets,3.7.2,MIT @@ -963,12 +996,12 @@ sql.js,0.4.0,MIT srcset,1.0.0,MIT sshkey,1.9.0,MIT ssri,5.2.4,ISC +ssri,6.0.1,ISC state_machines,0.5.0,MIT state_machines-activemodel,0.5.1,MIT state_machines-activerecord,0.5.1,MIT static-extend,0.1.2,MIT -statuses,1.3.1,MIT -statuses,1.5.0,MIT +statuses,1.4.0,MIT stickyfilljs,2.0.5,MIT stream-browserify,2.0.1,MIT stream-each,1.2.2,MIT @@ -984,12 +1017,12 @@ strip-ansi,3.0.1,MIT strip-ansi,4.0.0,MIT strip-eof,1.0.0,MIT strip-json-comments,2.0.1,MIT -style-loader,0.21.0,MIT +style-loader,0.23.0,MIT supports-color,2.0.0,MIT -supports-color,5.4.0,MIT +supports-color,5.5.0,MIT svg4everybody,2.1.9,CC0-1.0 sys-filesystem,1.1.6,Artistic 2.0 -tapable,1.0.0,MIT +tapable,1.1.0,MIT tar,4.4.4,ISC temple,0.8.0,MIT text,1.3.1,MIT @@ -1009,12 +1042,11 @@ timfel-krb5-auth,0.8.3,LGPL tiny-emitter,2.0.2,MIT tmp,0.0.33,MIT to-arraybuffer,1.0.1,MIT -to-fast-properties,1.0.3,MIT +to-fast-properties,2.0.0,MIT to-object-path,0.3.0,MIT to-regex,3.0.2,MIT to-regex-range,2.1.1,MIT toml-rb,1.0.0,MIT -traverse,0.6.6,MIT trim-right,1.0.1,MIT trollop,2.1.3,MIT truncato,0.7.10,MIT @@ -1029,10 +1061,13 @@ uber,0.1.0,MIT uglifier,2.7.2,MIT uglify-es,3.3.9,Simplified BSD uglifyjs-webpack-plugin,1.2.5,MIT -ultron,1.1.1,MIT underscore,1.9.0,MIT unf,0.1.4,BSD unf_ext,0.0.7.5,MIT +unicode-canonical-property-names-ecmascript,1.0.4,MIT +unicode-match-property-ecmascript,1.0.4,MIT +unicode-match-property-value-ecmascript,1.0.2,MIT +unicode-property-aliases-ecmascript,1.0.4,MIT unicorn,5.1.0,ruby unicorn-worker-killer,0.4.4,ruby union-value,1.0.0,MIT @@ -1042,14 +1077,16 @@ unique-slug,2.0.0,ISC unpipe,1.0.0,MIT unset-value,1.0.0,MIT upath,1.1.0,MIT +uri-js,4.2.2,Simplified BSD urix,0.1.0,MIT url,0.11.0,MIT -url-loader,1.0.1,MIT +url-loader,1.1.1,MIT url-parse-lax,3.0.0,MIT url-to-options,1.0.1,MIT use,2.0.2,MIT util,0.10.3,MIT util-deprecate,1.0.2,MIT +util.promisify,1.0.0,MIT utils-merge,1.0.1,MIT v8-compile-cache,2.0.0,MIT validates_hostname,1.0.6,MIT @@ -1059,24 +1096,24 @@ virtus,1.0.5,MIT visibilityjs,1.2.4,MIT vm-browserify,0.0.4,MIT vmstat,2.3.0,MIT -vue,2.5.16,MIT +vue,2.5.17,MIT vue-functional-data-merge,2.0.6,MIT vue-hot-reload-api,2.3.0,MIT -vue-loader,15.2.4,MIT +vue-loader,15.4.2,MIT vue-resource,1.5.0,MIT vue-router,3.0.1,MIT vue-style-loader,4.1.0,MIT -vue-template-compiler,2.5.16,MIT +vue-template-compiler,2.5.17,MIT vue-template-es2015-compiler,1.6.0,MIT vue-virtual-scroll-list,1.2.5,MIT vuex,3.0.1,MIT warden,1.2.7,MIT watchpack,1.5.0,MIT -webpack,4.16.0,MIT -webpack-bundle-analyzer,2.13.1,MIT -webpack-cli,3.0.8,MIT -webpack-rails,0.9.10,MIT -webpack-sources,1.1.0,MIT +webpack,4.19.1,MIT +webpack-bundle-analyzer,3.0.2,MIT +webpack-cli,3.1.0,MIT +webpack-rails,0.9.11,MIT +webpack-sources,1.3.0,MIT webpack-stats-plugin,0.2.1,MIT which,1.3.0,ISC which-module,2.0.0,ISC @@ -1086,14 +1123,14 @@ worker-farm,1.5.2,MIT worker-loader,2.0.0,MIT wrap-ansi,2.1.0,MIT wrappy,1.0.2,ISC -ws,4.0.0,MIT +ws,6.0.0,MIT xml-simple,1.1.5,ruby xmlhttprequest,1.8.0,MIT +xregexp,4.0.0,MIT xtend,4.0.1,MIT xterm,3.5.0,MIT -y18n,3.2.1,ISC y18n,4.0.0,ISC yallist,2.1.2,ISC yallist,3.0.2,ISC -yargs,11.1.0,MIT -yargs-parser,9.0.2,ISC +yargs,12.0.2,MIT +yargs-parser,10.1.0,ISC diff --git a/yarn.lock b/yarn.lock index 579b9408986..25ea8d7557c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,18 +29,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0.tgz#1efd58bffa951dc846449e58ce3a1d7f02d393aa" - integrity sha512-/BM2vupkpbZXq22l1ALO7MqXJZH2k8bKVv8Y+pABFnzWdztDB/ZLveP5At21vLz5c2YtSE6p7j2FZEsqafMz5Q== - dependencies: - "@babel/types" "^7.0.0" - jsesc "^2.5.1" - lodash "^4.17.10" - source-map "^0.5.0" - trim-right "^1.0.1" - -"@babel/generator@^7.1.2": +"@babel/generator@^7.0.0", "@babel/generator@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630" integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig== @@ -224,12 +213,7 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.0.tgz#a7cd42cb3c12aec52e24375189a47b39759b783e" - integrity sha512-SmjnXCuPAlai75AFtzv+KCBcJ3sDDWbIn+WytKw1k+wAtEy6phqI2RqKh/zAnw53i1NR8su3Ep/UoqaKcimuLg== - -"@babel/parser@^7.1.2": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409" integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ== @@ -599,7 +583,7 @@ js-levenshtein "^1.1.3" semver "^5.3.0" -"@babel/template@^7.0.0", "@babel/template@^7.1.2": +"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag== @@ -608,15 +592,6 @@ "@babel/parser" "^7.1.2" "@babel/types" "^7.1.2" -"@babel/template@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.0.tgz#58cc9572e1bfe24fe1537fdf99d839d53e517e22" - integrity sha512-yZ948B/pJrwWGY6VxG6XRFsVTee3IQ7bihq9zFpM00Vydu6z5Xwg0C3J644kxI9WOTzd+62xcIsQ+AT1MGhqhA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2" @@ -632,16 +607,7 @@ globals "^11.1.0" lodash "^4.17.10" -"@babel/types@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0.tgz#6e191793d3c854d19c6749989e3bc55f0e962118" - integrity sha512-5tPDap4bGKTLPtci2SUl/B7Gv8RnuJFuQoWx26RJobS0fFrz4reUA3JnwIM+HVHEmWE0C1mzKhDtTp8NsWY02Q== - dependencies: - esutils "^2.0.2" - lodash "^4.17.10" - to-fast-properties "^2.0.0" - -"@babel/types@^7.1.2": +"@babel/types@^7.0.0", "@babel/types@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0" integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg== @@ -650,14 +616,10 @@ lodash "^4.17.10" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.23.0": - version "1.29.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.29.0.tgz#03b65b513f9099bbda6ecf94d673a2952f8c6c70" - integrity sha512-sCl6nP3ph36+8P3nrw9VanAR648rgOUEBlEoLPHkhKm79xB1dUkXGBtI0uaSJVgbJx40M1/Ts8HSdMv+PF3EIg== - -"@gitlab-org/gitlab-svgs@^1.29.0": +"@gitlab-org/gitlab-svgs@^1.23.0", "@gitlab-org/gitlab-svgs@^1.29.0": version "1.31.0" resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.31.0.tgz#495b074669f93af40e34f9978ce887773dea470a" + integrity sha512-tJbf99XX/ddFkXCXxQr9a0GJD9rPVoW3qMbU14dkxwG4WBmPEoVg+e7sLvm9OWTD1uUqiVW3qWKp++SGhhcRlw== "@gitlab-org/gitlab-ui@^1.8.0": version "1.8.0" @@ -1198,10 +1160,9 @@ babel-loader@^8.0.4: babel-messages@^6.23.0: version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" babel-runtime "^6.22.0" babel-plugin-istanbul@^5.1.0: @@ -1218,11 +1179,6 @@ babel-plugin-rewire@^1.2.0: resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89" integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ== -babel-plugin-syntax-object-rest-spread@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" - integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= - babel-polyfill@6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d" @@ -2684,12 +2640,12 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -depd@1.1.1, depd@~1.1.1: +depd@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k= -depd@~1.1.2: +depd@~1.1.1, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -3056,6 +3012,13 @@ eslint-config-airbnb-base@^13.1.0: object.assign "^4.1.0" object.entries "^1.0.4" +eslint-config-prettier@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.1.0.tgz#2c26d2cdcfa3a05f0642cd7e6e4ef3316cdabfa2" + integrity sha512-QYGfmzuc4q4J6XIhlp8vRKdI/fI0tQfQPy1dME3UOLprE+v4ssH/3W9LM2Q7h5qBcy5m0ehCrBDU2YF8q6OY8w== + dependencies: + get-stdin "^6.0.0" + eslint-import-resolver-node@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" @@ -3752,6 +3715,11 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U= +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -5219,14 +5187,7 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= -make-dir@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b" - integrity sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw== - dependencies: - pify "^3.0.0" - -make-dir@^1.3.0: +make-dir@^1.0.0, make-dir@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== @@ -6322,16 +6283,16 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" - integrity sha1-wa0g6APndJ+vkFpAnSNn4Gu+cyU= - prettier@1.13.7: version "1.13.7" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281" integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w== +prettier@1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" + integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg== + prismjs@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.6.0.tgz#118d95fb7a66dba2272e343b345f5236659db365" @@ -6619,12 +6580,7 @@ regenerate-unicode-properties@^7.0.0: dependencies: regenerate "^1.4.0" -regenerate@^1.2.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" - integrity sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA= - -regenerate@^1.4.0: +regenerate@^1.2.1, regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== @@ -6789,20 +6745,13 @@ resolve@1.1.x: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.3.2: +resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== dependencies: path-parse "^1.0.5" -resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" - integrity sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw== - dependencies: - path-parse "^1.0.5" - responselike@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" |