diff options
author | Shinya Maeda <shinya@gitlab.com> | 2018-10-05 10:13:06 +0900 |
---|---|---|
committer | Shinya Maeda <shinya@gitlab.com> | 2018-10-05 10:13:06 +0900 |
commit | af2c51d7ca73e16cdb9a11fff6d097116c7c5324 (patch) | |
tree | cd7f07f36e09a357902a262cbb932f25d254a16c | |
parent | acfe7ec65c75f65f2f87bf3e8598c1725c9150f4 (diff) | |
parent | f71c497f5da791a35876206255e342a9bb5e49c5 (diff) | |
download | gitlab-ce-af2c51d7ca73e16cdb9a11fff6d097116c7c5324.tar.gz |
Merge branch 'master-ce' into scheduled-manual-jobs
231 files changed, 3137 insertions, 1224 deletions
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/.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..a02d1bc38b7 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) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 3ba7bd5ba83..ee476f3ae84 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.123.0 +0.124.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..9837a195d8c 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) @@ -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..9eab07d9659 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) @@ -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..ecc2440c7e6 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -22,6 +22,7 @@ const Api = { dockerfilePath: '/api/:version/templates/dockerfiles/:key', issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', 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', @@ -266,6 +267,15 @@ const Api = { }); }, + postUserStatus({ emoji, message }) { + const url = Api.buildUrl(this.userStatusPath); + + return axios.put(url, { + emoji, + message, + }); + }, + templates(key, params = {}) { const url = Api.buildUrl(this.templatesPath).replace(':key', key); 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/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/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 4ae588042e4..c39403f1021 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -244,6 +244,7 @@ export function getDiffPositionByLineCode(diffFiles) { oldLine, newLine, lineCode, + positionType: 'text', }; } }); @@ -259,8 +260,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/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 11e3b781e5a..a1d8e531940 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -466,7 +466,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 @@ -480,9 +482,7 @@ export default { v-if="!model.isFolder" class="environment-name table-mobile-content"> <a - v-tooltip :href="environmentPath" - :title="model.name" > {{ model.name }} </a> 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/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/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index d4babf1fab2..75832884711 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -126,8 +126,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/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/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/users_select.js b/app/assets/javascripts/users_select.js index e19bbbacf4d..b9dfa22dd49 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -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/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/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/notes.scss b/app/assets/stylesheets/pages/notes.scss index c9e0899425f..a2070087963 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -519,59 +519,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 { 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/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/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/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/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/labels_finder.rb b/app/finders/labels_finder.rb index 08fc2968e77..82e0b2ed9e1 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) 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/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/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/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/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/pipeline.rb b/app/models/ci/pipeline.rb index 765943104a0..d986e33280d 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -633,6 +633,10 @@ module Ci end end + def default_branch? + ref == project.default_branch + end + private def ci_yaml_from_repo 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/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/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/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/policies/project_policy.rb b/app/policies/project_policy.rb index d0e84b1aa38..f2c246cd969 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -390,7 +390,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/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/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/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 5286b92ab6b..7b747171d9c 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,14 @@ 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 labels_as_hash(target) + super(target, project_id: project.id, include_ancestor_groups: true) + end end end 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/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index ccba1c461fc..32f6eefc16a 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 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/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/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/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/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml index 0222bbf7338..eaf3a93bd15 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-experimental-support'), 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..56551ed4d65 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-experimental-support'), 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/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/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/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/_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/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/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/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/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/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/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/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/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/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/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/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..356a509da75 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -366,7 +366,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get :discussions, format: :json end collection do - post :bulk_update + post :bulk_update end end @@ -438,6 +438,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/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/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/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/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/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/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/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/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/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/declarative_policy.rb b/lib/declarative_policy.rb index dda6cd38dcd..10d34b0c6e7 100644 --- a/lib/declarative_policy.rb +++ b/lib/declarative_policy.rb @@ -10,8 +10,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/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_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/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/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..d16a72b76b8 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 "" @@ -2099,6 +2096,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 +2746,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 +3372,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 "" @@ -3733,6 +3739,9 @@ msgstr "" msgid "Median" msgstr "" +msgid "Member since %{date}" +msgstr "" + msgid "Members" msgstr "" @@ -5463,6 +5472,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 +5810,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 +6013,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 +6650,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 +6983,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 +6992,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/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb index 94d7df7128b..ed9d0329081 100644 --- a/qa/qa/factory/resource/kubernetes_cluster.rb +++ b/qa/qa/factory/resource/kubernetes_cluster.rb @@ -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/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/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb index abd9d53554f..d868515555c 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,7 @@ module QA shell <<~CMD.tr("\n", ' ') gcloud container clusters create #{cluster_name} - --enable-legacy-authorization + #{auth_options} --zone #{Runtime::Env.gcloud_zone} && gcloud container clusters get-credentials @@ -28,8 +33,21 @@ 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}'`) + 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 +62,42 @@ module QA private + def create_service_account + shell('kubectl create -f -', stdin_data: service_account) + shell('kubectl 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/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..4604936916b 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,63 @@ 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::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::Menu::Side.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) + 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 + end end end end 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/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/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_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/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/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/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 206a3a4fe9a..e168bb0fc89 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -61,83 +61,229 @@ 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 - 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 6a7bf0860e9..7bebd066b80 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') @@ -591,14 +591,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 +625,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 +640,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 +654,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/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/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/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/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/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/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/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/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/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/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/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/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/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index b19e75a956d..5ca140879c3 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1993,4 +1993,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/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/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/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/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 5abc6d81958..56df8dddbc1 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -258,10 +258,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/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/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/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/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/yarn.lock b/yarn.lock index 579b9408986..89f6391e48b 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== @@ -1198,10 +1164,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 +1183,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 +2644,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= @@ -5219,14 +5179,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== @@ -6619,12 +6572,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 +6737,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" |