diff options
author | Shinya Maeda <shinya@gitlab.com> | 2018-05-28 20:01:56 +0900 |
---|---|---|
committer | Shinya Maeda <shinya@gitlab.com> | 2018-05-28 20:01:56 +0900 |
commit | 1d20679e9c8b1ba16bebaf982255946e7207b4d4 (patch) | |
tree | 128685af66ca56c751b505a8314ca79dd3b54b34 | |
parent | 8e92e25b62ca108de775362e6d2981e54535f094 (diff) | |
parent | 014f5f6a69f63ee42bd94454108268f189b62b18 (diff) | |
download | gitlab-ce-1d20679e9c8b1ba16bebaf982255946e7207b4d4.tar.gz |
Merge branch 'master' into per-project-pipeline-iid
942 files changed, 7436 insertions, 4640 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ced51cf8225..ef263a3f106 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -307,6 +307,8 @@ cloud-native-image: before_script: [] stage: build allow_failure: true + variables: + GIT_DEPTH: "1" cache: {} before_script: - gem install gitlab --no-rdoc --no-ri @@ -855,3 +857,15 @@ gitlab_git_test: cache: {} script: - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes + +no_ee_check: + <<: *dedicated-runner + <<: *except-docs-and-qa + variables: + SETUP_DB: "false" + before_script: [] + cache: {} + script: + - scripts/no-ee-check + only: + - //@gitlab-org/gitlab-ce diff --git a/.scss-lint.yml b/.scss-lint.yml index 180d377d6f8..3df66033fa8 100644 --- a/.scss-lint.yml +++ b/.scss-lint.yml @@ -46,8 +46,9 @@ linters: # - properties # - @include declarations with inner @content # - nested rule sets. + # Disabled to minimize Bootstrap migration footprint DeclarationOrder: - enabled: true + enabled: false # `scss-lint:disable` control comments should be preceded by a comment # explaining why these linters are being disabled for this file. diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c90ab1e8bd..99cf96035d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.8.1 (2018-05-23) + +### Fixed (9 changes) + +- Allow CommitStatus class to use presentable methods. !18979 +- Fix corrupted environment pages with unathorized proxy url. !18989 +- Fixes deploy token variables on Ci::Build. !19047 +- Fix project mirror database inconsistencies when upgrading from EE to CE. !19109 +- Render 404 when prometheus adapter is disabled in Prometheus metrics controller. !19110 +- Fix error when deleting an empty list of refs. +- Fixed U2F login when used with LDAP. +- Bump prometheus-client-mmap to 0.9.3 to fix nil exception error. +- Fix system hook not firing for blocked users when LDAP sign-in is used. + + ## 10.8.0 (2018-05-22) ### Security (3 changes, 1 of them is from the community) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 6aba2b245a8..fae6e3d04b2 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -4.2.0 +4.2.1 @@ -50,7 +50,7 @@ gem 'omniauth-saml', '~> 1.10' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.4' gem 'omniauth_crowd', '~> 2.2.0' -gem 'omniauth-authentiq', '~> 0.3.1' +gem 'omniauth-authentiq', '~> 0.3.3' gem 'rack-oauth2', '~> 1.2.1' gem 'jwt', '~> 1.5.6' @@ -257,7 +257,6 @@ gem 'sass-rails', '~> 5.0.6' gem 'uglifier', '~> 2.7.2' gem 'addressable', '~> 2.5.2' -gem 'bootstrap-sass', '~> 3.3.0' gem 'font-awesome-rails', '~> 4.7' gem 'gemojione', '~> 3.3' gem 'gon', '~> 6.2' @@ -297,7 +296,7 @@ group :metrics do gem 'influxdb', '~> 0.2', require: false # Prometheus - gem 'prometheus-client-mmap', '~> 0.9.2' + gem 'prometheus-client-mmap', '~> 0.9.3' gem 'raindrops', '~> 0.18' end diff --git a/Gemfile.lock b/Gemfile.lock index 2a33c08512a..7332b55c175 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -69,9 +69,6 @@ GEM attr_encrypted (3.1.0) encryptor (~> 3.0.0) attr_required (1.0.0) - autoprefixer-rails (6.2.3) - execjs - json awesome_print (1.2.0) axiom-types (0.1.1) descendants_tracker (~> 0.0.4) @@ -91,9 +88,6 @@ GEM binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) blankslate (2.1.2.4) - bootstrap-sass (3.3.6) - autoprefixer-rails (>= 5.2.1) - sass (>= 3.3.4) bootstrap_form (2.7.0) brakeman (4.2.1) browser (2.2.0) @@ -529,8 +523,9 @@ GEM rack (>= 1.6.2, < 3) omniauth-auth0 (2.0.0) omniauth-oauth2 (~> 1.4) - omniauth-authentiq (0.3.1) - omniauth-oauth2 (~> 1.3, >= 1.3.1) + omniauth-authentiq (0.3.3) + jwt (>= 1.5) + omniauth-oauth2 (>= 1.5) omniauth-azure-oauth2 (0.0.9) jwt (~> 1.0) omniauth (~> 1.0) @@ -629,7 +624,7 @@ GEM parser unparser procto (0.0.3) - prometheus-client-mmap (0.9.2) + prometheus-client-mmap (0.9.3) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -991,7 +986,6 @@ DEPENDENCIES benchmark-ips (~> 2.3.0) better_errors (~> 2.1.0) binding_of_caller (~> 0.7.2) - bootstrap-sass (~> 3.3.0) bootstrap_form (~> 2.7.0) brakeman (~> 4.2) browser (~> 2.2) @@ -1092,7 +1086,7 @@ DEPENDENCIES octokit (~> 4.8) omniauth (~> 1.8) omniauth-auth0 (~> 2.0.0) - omniauth-authentiq (~> 0.3.1) + omniauth-authentiq (~> 0.3.3) omniauth-azure-oauth2 (~> 0.0.9) omniauth-cas3 (~> 1.1.4) omniauth-facebook (~> 4.0.0) @@ -1115,7 +1109,7 @@ DEPENDENCIES peek-sidekiq (~> 1.0.3) pg (~> 0.18.2) premailer-rails (~> 1.9.7) - prometheus-client-mmap (~> 0.9.2) + prometheus-client-mmap (~> 0.9.3) pry-byebug (~> 3.4.1) pry-rails (~> 0.3.4) rack-attack (~> 4.4.1) @@ -1197,4 +1191,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.16.1 + 1.16.2 diff --git a/README.md b/README.md index 013cac75c46..0266fe82c82 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) [![Overall test coverage](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg)](https://gitlab.com/gitlab-org/gitlab-ce/pipelines) -[![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.svg)](https://gemnasium.com/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) [![Gitter](https://badges.gitter.im/gitlabhq/gitlabhq.svg)](https://gitter.im/gitlabhq/gitlabhq?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 8ad3d18b302..ce1069276ab 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -21,8 +21,11 @@ const Api = { issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', usersPath: '/api/:version/users.json', commitPath: '/api/:version/projects/:id/repository/commits', + commitPipelinesPath: '/:project_id/commit/:sha/pipelines', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', createBranchPath: '/api/:version/projects/:id/repository/branches', + pipelinesPath: '/api/:version/projects/:id/pipelines', + pipelineJobsPath: '/api/:version/projects/:id/pipelines/:pipeline_id/jobs', group(groupId, callback) { const url = Api.buildUrl(Api.groupPath).replace(':id', groupId); @@ -164,6 +167,19 @@ const Api = { }); }, + commitPipelines(projectId, sha) { + const encodedProjectId = projectId + .split('/') + .map(fragment => encodeURIComponent(fragment)) + .join('/'); + + const url = Api.buildUrl(Api.commitPipelinesPath) + .replace(':project_id', encodedProjectId) + .replace(':sha', encodeURIComponent(sha)); + + return axios.get(url); + }, + branchSingle(id, branch) { const url = Api.buildUrl(Api.branchSinglePath) .replace(':id', encodeURIComponent(id)) @@ -222,6 +238,20 @@ const Api = { }); }, + pipelines(projectPath, params = {}) { + const url = Api.buildUrl(this.pipelinesPath).replace(':id', encodeURIComponent(projectPath)); + + return axios.get(url, { params }); + }, + + pipelineJobs(projectPath, pipelineId, params = {}) { + const url = Api.buildUrl(this.pipelineJobsPath) + .replace(':id', encodeURIComponent(projectPath)) + .replace(':pipeline_id', pipelineId); + + return axios.get(url, { params }); + }, + buildUrl(url) { let urlRoot = ''; if (gon.relative_url_root != null) { diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 976d32abe9b..eb0f06efab4 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -345,7 +345,7 @@ class AwardsHandler { counter.text(counterNumber - 1); this.removeYouFromUserList($emojiButton); } else if (emoji === 'thumbsup' || emoji === 'thumbsdown') { - $emojiButton.tooltip('destroy'); + $emojiButton.tooltip('dispose'); counter.text('0'); this.removeYouFromUserList($emojiButton); if ($emojiButton.parents('.note').length) { @@ -358,7 +358,7 @@ class AwardsHandler { } removeEmoji($emojiButton) { - $emojiButton.tooltip('destroy'); + $emojiButton.tooltip('dispose'); $emojiButton.remove(); const $votesBlock = this.getVotesBlock(); if ($votesBlock.find('.js-emoji-btn').length === 0) { @@ -392,7 +392,7 @@ class AwardsHandler { .removeAttr('data-title') .removeAttr('data-original-title') .attr('title', this.toSentence(authors)) - .tooltip('fixTitle'); + .tooltip('_fixTitle'); } addYouToUserList(votesBlock, emoji) { @@ -405,7 +405,7 @@ class AwardsHandler { users.unshift('You'); return awardBlock .attr('title', this.toSentence(users)) - .tooltip('fixTitle'); + .tooltip('_fixTitle'); } createAwardButtonForVotesBlock(votesBlock, emojiName) { diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue index 6e6cb31e3ac..d0f60e1d4cb 100644 --- a/app/assets/javascripts/badges/components/badge.vue +++ b/app/assets/javascripts/badges/components/badge.vue @@ -89,7 +89,7 @@ export default { v-show="hasError" class="btn-group" > - <div class="btn btn-default btn-xs disabled"> + <div class="btn btn-default btn-sm disabled"> <icon class="prepend-left-8 append-right-8" name="doc_image" @@ -98,7 +98,7 @@ export default { /> </div> <div - class="btn btn-default btn-xs disabled" + class="btn btn-default btn-sm disabled" > <span class="prepend-left-8 append-right-8">{{ s__('Badges|No badge image') }}</span> </div> @@ -106,7 +106,7 @@ export default { <button v-show="hasError" - class="btn btn-transparent btn-xs text-primary" + class="btn btn-transparent btn-sm text-primary" type="button" v-tooltip :title="s__('Badges|Reload badge image')" diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue index ca7197e1e0f..268968b63b3 100644 --- a/app/assets/javascripts/badges/components/badge_list.vue +++ b/app/assets/javascripts/badges/components/badge_list.vue @@ -23,8 +23,8 @@ export default { </script> <template> - <div class="panel panel-default"> - <div class="panel-heading"> + <div class="card"> + <div class="card-header"> {{ s__('Badges|Your badges') }} <span v-show="!isLoading" @@ -33,19 +33,19 @@ export default { </div> <loading-icon v-show="isLoading" - class="panel-body" + class="card-body" size="2" /> <div v-if="hasNoBadges" - class="panel-body" + class="card-body" > <span v-if="isGroupBadge">{{ s__('Badges|This group has no badges') }}</span> <span v-else>{{ s__('Badges|This project has no badges') }}</span> </div> <div v-else - class="panel-body" + class="card-body" > <badge-list-row v-for="badge in badges" diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js index e2a73a1797c..75834ba351d 100644 --- a/app/assets/javascripts/behaviors/copy_to_clipboard.js +++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js @@ -8,10 +8,10 @@ function showTooltip(target, title) { if (!$target.data('hideTooltip')) { $target .attr('title', title) - .tooltip('fixTitle') + .tooltip('_fixTitle') .tooltip('show') .attr('title', originalTitle) - .tooltip('fixTitle'); + .tooltip('_fixTitle'); } } diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index 3ec932bdb73..b6e2781773c 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -69,7 +69,7 @@ $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-q $this.tooltip({ container: 'body', html: 'true', - placement: 'auto top', + placement: 'top', title, trigger: 'manual', }); diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index ffff4ddb71a..a8b6dbf0948 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -42,9 +42,9 @@ $.fn.requiresInput = function requiresInput() { function hideOrShowHelpBlock(form) { const selected = $('.js-select-namespace option:selected'); if (selected.length && selected.data('optionsParent') === 'groups') { - form.find('.help-block').hide(); + form.find('.form-text.text-muted').hide(); } else if (selected.length) { - form.find('.help-block').show(); + form.find('.form-text.text-muted').show(); } } diff --git a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js index c17877a276d..766039404ce 100644 --- a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js +++ b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js @@ -2,9 +2,9 @@ import sqljs from 'sql.js'; import { template as _template } from 'underscore'; const PREVIEW_TEMPLATE = _template(` - <div class="panel panel-default"> - <div class="panel-heading"><%- name %></div> - <div class="panel-body"> + <div class="card"> + <div class="card-header"><%- name %></div> + <div class="card-body"> <img class="img-thumbnail" src="data:image/png;base64,<%- image %>"/> </div> </div> diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js index 0799991aa40..13318c58006 100644 --- a/app/assets/javascripts/blob/sketch/index.js +++ b/app/assets/javascripts/blob/sketch/index.js @@ -44,7 +44,7 @@ export default class SketchLoader { previewLink.href = previewUrl; previewLink.target = '_blank'; previewImage.src = previewUrl; - previewImage.className = 'img-responsive'; + previewImage.className = 'img-fluid'; previewLink.appendChild(previewImage); this.container.appendChild(previewLink); diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 137e1f5a099..f61c0be9230 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -116,7 +116,7 @@ export default class BlobViewer { this.copySourceBtn.classList.add('disabled'); } - $(this.copySourceBtn).tooltip('fixTitle'); + $(this.copySourceBtn).tooltip('_fixTitle'); } switchToViewer(name) { diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 84885ca9306..33e3369b971 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -77,7 +77,7 @@ export default { <template> <li - class="card" + class="board-card" :class="{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id, diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 8d84c1735b8..e8dfd95f7ae 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -92,7 +92,7 @@ export default { <template> <div class="board-new-issue-form"> - <div class="card"> + <div class="board-card"> <form @submit="submit($event)"> <div class="flash-container" @@ -122,7 +122,7 @@ export default { /> <div class="clearfix prepend-top-10"> <button - class="btn btn-success pull-left" + class="btn btn-success float-left" type="submit" :disabled="disabled" ref="submit-button" @@ -130,7 +130,7 @@ export default { Submit issue </button> <button - class="btn btn-default pull-right" + class="btn btn-default float-right" type="button" @click="cancel" > diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js b/app/assets/javascripts/boards/components/issue_card_inner.js index 84fe9b1288a..dcc07810d01 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.js +++ b/app/assets/javascripts/boards/components/issue_card_inner.js @@ -135,8 +135,8 @@ gl.issueBoards.IssueCardInner = Vue.extend({ }, template: ` <div> - <div class="card-header"> - <h4 class="card-title"> + <div class="board-card-header"> + <h4 class="board-card-title"> <i class="fa fa-eye-slash confidential-icon" v-if="issue.confidential" @@ -147,13 +147,13 @@ gl.issueBoards.IssueCardInner = Vue.extend({ :href="issue.path" :title="issue.title">{{ issue.title }}</a> <span - class="card-number" + class="board-card-number" v-if="issueId" > {{ issue.referencePath }} </span> </h4> - <div class="card-assignee"> + <div class="board-card-assignee"> <user-avatar-link v-for="(assignee, index) in issue.assignees" :key="assignee.id" @@ -175,11 +175,11 @@ gl.issueBoards.IssueCardInner = Vue.extend({ </div> </div> <div - class="card-footer" + class="board-card-footer" v-if="showLabelFooter" > <button - class="label color-label has-tooltip" + class="badge color-label has-tooltip" v-for="label in issue.labels" type="button" v-if="showLabel(label)" diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js b/app/assets/javascripts/boards/components/modal/empty_state.js index 9e37f95cdd6..eb8a66975ee 100644 --- a/app/assets/javascripts/boards/components/modal/empty_state.js +++ b/app/assets/javascripts/boards/components/modal/empty_state.js @@ -41,10 +41,10 @@ gl.issueBoards.ModalEmptyState = Vue.extend({ template: ` <section class="empty-state"> <div class="row"> - <div class="col-xs-12 col-sm-6 col-sm-push-6"> + <div class="col-xs-12 col-sm-6 order-sm-last"> <aside class="svg-content"><img :src="emptyStateSvg"/></aside> </div> - <div class="col-xs-12 col-sm-6 col-sm-pull-6"> + <div class="col-xs-12 col-sm-6 order-sm-first"> <div class="text-content"> <h4>{{ contents.title }}</h4> <p v-html="contents.content"></p> diff --git a/app/assets/javascripts/boards/components/modal/footer.js b/app/assets/javascripts/boards/components/modal/footer.js index 9735e0ddacc..11bb3e98334 100644 --- a/app/assets/javascripts/boards/components/modal/footer.js +++ b/app/assets/javascripts/boards/components/modal/footer.js @@ -58,7 +58,7 @@ gl.issueBoards.ModalFooter = Vue.extend({ template: ` <footer class="form-actions add-issues-footer"> - <div class="pull-left"> + <div class="float-left"> <button class="btn btn-success" type="button" @@ -72,7 +72,7 @@ gl.issueBoards.ModalFooter = Vue.extend({ <lists-dropdown></lists-dropdown> </div> <button - class="btn btn-default pull-right" + class="btn btn-default float-right" type="button" @click="toggleModal(false)"> Cancel diff --git a/app/assets/javascripts/boards/components/modal/list.js b/app/assets/javascripts/boards/components/modal/list.js index 6b04a6c7a6c..6c662432037 100644 --- a/app/assets/javascripts/boards/components/modal/list.js +++ b/app/assets/javascripts/boards/components/modal/list.js @@ -133,9 +133,9 @@ gl.issueBoards.ModalList = Vue.extend({ <div v-for="issue in group" v-if="showIssue(issue)" - class="card-parent"> + class="board-card-parent"> <div - class="card" + class="board-card" :class="{ 'is-active': issue.selected }" @click="toggleIssue($event, issue)"> <issue-card-inner diff --git a/app/assets/javascripts/boards/components/modal/tabs.js b/app/assets/javascripts/boards/components/modal/tabs.js index b6465a88e5e..9d331de8e22 100644 --- a/app/assets/javascripts/boards/components/modal/tabs.js +++ b/app/assets/javascripts/boards/components/modal/tabs.js @@ -24,7 +24,7 @@ gl.issueBoards.ModalTabs = Vue.extend({ role="button" @click.prevent="changeTab('all')"> Open issues - <span class="badge"> + <span class="badge badge-pill"> {{ issuesCount }} </span> </a> @@ -35,7 +35,7 @@ gl.issueBoards.ModalTabs = Vue.extend({ role="button" @click.prevent="changeTab('selected')"> Selected issues - <span class="badge"> + <span class="badge badge-pill"> {{ selectedCount }} </span> </a> diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index a6f8681cfac..29ab13b8e0b 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -214,7 +214,7 @@ export default () => { if (this.disabled) { $tooltip.tooltip(); } else { - $tooltip.tooltip('destroy'); + $tooltip.tooltip('dispose'); } }); }, diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js index e177a3bfdc7..47efb3a8cee 100644 --- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js +++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js @@ -141,6 +141,11 @@ export default class VariableList { $rowClone.find(entry.selector).val(entry.default); }); + // Close any dropdowns + $rowClone.find('.dropdown-menu.show').each((index, $dropdown) => { + $dropdown.classList.remove('show'); + }); + this.initRow($rowClone); $row.after($rowClone); diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 9c12b89240c..bb5fcea648d 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -191,11 +191,11 @@ export default { :value="ingressExternalIp" readonly /> - <span class="input-group-btn"> + <span class="input-group-append"> <clipboard-button :text="ingressExternalIp" :title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')" - class="js-clipboard-btn" + class="input-group-text js-clipboard-btn" /> </span> </div> diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js index db96da4ccba..50e2949ab55 100644 --- a/app/assets/javascripts/commons/bootstrap.js +++ b/app/assets/javascripts/commons/bootstrap.js @@ -1,15 +1,7 @@ import $ from 'jquery'; // bootstrap jQuery plugins -import 'bootstrap-sass/assets/javascripts/bootstrap/affix'; -import 'bootstrap-sass/assets/javascripts/bootstrap/alert'; -import 'bootstrap-sass/assets/javascripts/bootstrap/button'; -import 'bootstrap-sass/assets/javascripts/bootstrap/dropdown'; -import 'bootstrap-sass/assets/javascripts/bootstrap/modal'; -import 'bootstrap-sass/assets/javascripts/bootstrap/tab'; -import 'bootstrap-sass/assets/javascripts/bootstrap/transition'; -import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip'; -import 'bootstrap-sass/assets/javascripts/bootstrap/popover'; +import 'bootstrap'; // custom jQuery functions $.fn.extend({ diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js index 9c88466e576..ffe15f02f2e 100644 --- a/app/assets/javascripts/compare_autocomplete.js +++ b/app/assets/javascripts/compare_autocomplete.js @@ -54,7 +54,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = ( .attr('href', '#') .addClass(ref === selected ? 'is-active' : '') .text(ref) - .attr('data-ref', escape(ref)); + .attr('data-ref', ref); return $('<li />').append(link); } }, @@ -78,7 +78,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = ( $dropdownContainer.on('click', '.dropdown-content a', e => { $dropdown.prop('title', e.target.text.replace(/_+?/g, '-')); if ($dropdown.hasClass('has-tooltip')) { - $dropdown.tooltip('fixTitle'); + $dropdown.tooltip('_fixTitle'); } }); }); diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/create_merge_request_dropdown.js index a88b6971f90..09d490106df 100644 --- a/app/assets/javascripts/create_merge_request_dropdown.js +++ b/app/assets/javascripts/create_merge_request_dropdown.js @@ -61,8 +61,8 @@ export default class CreateMergeRequestDropdown { } available() { - this.availableButton.classList.remove('hide'); - this.unavailableButton.classList.add('hide'); + this.availableButton.classList.remove('hidden'); + this.unavailableButton.classList.add('hidden'); } bindEvents() { @@ -232,7 +232,7 @@ export default class CreateMergeRequestDropdown { } hide() { - this.wrapperEl.classList.add('hide'); + this.wrapperEl.classList.add('hidden'); } init() { @@ -406,8 +406,8 @@ export default class CreateMergeRequestDropdown { } unavailable() { - this.availableButton.classList.add('hide'); - this.unavailableButton.classList.remove('hide'); + this.availableButton.classList.add('hidden'); + this.unavailableButton.classList.remove('hidden'); } updateBranchName(suggestedBranchName) { diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue index 32ae0cc1476..5be17081b58 100644 --- a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue @@ -16,7 +16,7 @@ <template> <span v-if="count === 50" - class="events-info pull-right" + class="events-info float-right" > <i class="fa fa-warning" diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js index 180a6bd67e7..fe9b0795609 100644 --- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js +++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js @@ -79,7 +79,7 @@ const DiffNoteAvatars = Vue.extend({ storeState: { handler() { this.$nextTick(() => { - $('.has-tooltip', this.$el).tooltip('fixTitle'); + $('.has-tooltip', this.$el).tooltip('_fixTitle'); // We need to add/remove a class to an element that is outside the Vue instance this.addNoCommentClass(); @@ -138,7 +138,7 @@ const DiffNoteAvatars = Vue.extend({ this.$nextTick(() => { this.setDiscussionVisible(); - $('.has-tooltip', this.$el).tooltip('fixTitle'); + $('.has-tooltip', this.$el).tooltip('_fixTitle'); $('.has-tooltip', this.$el).tooltip('hide'); }); }, diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js b/app/assets/javascripts/diff_notes/components/resolve_btn.js index df4c72ba0ed..8d66417abac 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js @@ -61,7 +61,7 @@ const ResolveBtn = Vue.extend({ this.$nextTick(() => { $(this.$refs.button) .tooltip('hide') - .tooltip('fixTitle'); + .tooltip('_fixTitle'); }); }, resolve: function () { diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue index ab9e22037d0..0b3fef9fcca 100644 --- a/app/assets/javascripts/environments/components/environment_actions.vue +++ b/app/assets/javascripts/environments/components/environment_actions.vue @@ -74,7 +74,7 @@ </span> </button> - <ul class="dropdown-menu dropdown-menu-align-right"> + <ul class="dropdown-menu dropdown-menu-right"> <li v-for="(action, i) in actions" :key="i"> diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 79326ca3487..23aaab2c441 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -486,14 +486,14 @@ {{ model.folderName }} </span> - <span class="badge"> + <span class="badge badge-pill"> {{ model.size }} </span> </span> </div> <div - class="table-section section-10 deployment-column hidden-xs hidden-sm" + class="table-section section-10 deployment-column d-none d-sm-none d-md-block" role="gridcell" > <span v-if="shouldRenderDeploymentID"> @@ -513,7 +513,7 @@ </div> <div - class="table-section section-15 hidden-xs hidden-sm" + class="table-section section-15 d-none d-sm-none d-md-block" role="gridcell" > <a diff --git a/app/assets/javascripts/environments/components/environment_monitoring.vue b/app/assets/javascripts/environments/components/environment_monitoring.vue index deada134b27..8df1b6317e3 100644 --- a/app/assets/javascripts/environments/components/environment_monitoring.vue +++ b/app/assets/javascripts/environments/components/environment_monitoring.vue @@ -28,7 +28,7 @@ <template> <a v-tooltip - class="btn monitoring-url hidden-xs hidden-sm" + class="btn monitoring-url d-none d-sm-none d-md-block" data-container="body" rel="noopener noreferrer nofollow" :href="monitoringUrl" diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index c822fb1574c..7515d711c50 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -40,7 +40,7 @@ <template> <button type="button" - class="btn hidden-xs hidden-sm" + class="btn d-none d-sm-none d-md-block" @click="onClick" :disabled="isLoading" > diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue index dda7429a726..7055f208451 100644 --- a/app/assets/javascripts/environments/components/environment_stop.vue +++ b/app/assets/javascripts/environments/components/environment_stop.vue @@ -43,7 +43,7 @@ if (confirm('Are you sure you want to stop this environment?')) { this.isLoading = true; - $(this.$el).tooltip('destroy'); + $(this.$el).tooltip('dispose'); eventHub.$emit('postAction', this.stopUrl); } @@ -55,7 +55,7 @@ <button v-tooltip type="button" - class="btn stop-env-link hidden-xs hidden-sm" + class="btn stop-env-link d-none d-sm-none d-md-block" data-container="body" @click="onClick" :disabled="isLoading" diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.vue b/app/assets/javascripts/environments/components/environment_terminal_button.vue index e8469d088ef..0dbbbb75e07 100644 --- a/app/assets/javascripts/environments/components/environment_terminal_button.vue +++ b/app/assets/javascripts/environments/components/environment_terminal_button.vue @@ -30,7 +30,7 @@ <template> <a v-tooltip - class="btn terminal-button hidden-xs hidden-sm" + class="btn terminal-button d-none d-sm-none d-md-block" data-container="body" :title="title" :aria-label="title" diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js index 2d5bae9a9c4..2f27c9351bc 100644 --- a/app/assets/javascripts/feature_highlight/feature_highlight.js +++ b/app/assets/javascripts/feature_highlight/feature_highlight.js @@ -24,7 +24,7 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { template: ` <div class="popover feature-highlight-popover" role="tooltip"> <div class="arrow"></div> - <div class="popover-content"></div> + <div class="popover-body"></div> </div> `, }) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index fa48d7d1915..746a06b7c4f 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -374,7 +374,7 @@ GitLabDropdown = (function() { $relatedTarget = $(e.relatedTarget); $dropdownMenu = $relatedTarget.closest('.dropdown-menu'); if ($dropdownMenu.length === 0) { - return _this.dropdown.removeClass('open'); + return _this.dropdown.removeClass('show'); } } }; @@ -801,7 +801,7 @@ GitLabDropdown = (function() { if (this.options.filterable) { const initialScrollTop = $(window).scrollTop(); - if (this.dropdown.is('.open')) { + if (this.dropdown.is('.show') && !this.filterInput.is(':focus')) { this.filterInput.focus(); } diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js index 972b2252acb..87c6e37b9fb 100644 --- a/app/assets/javascripts/gl_field_error.js +++ b/app/assets/javascripts/gl_field_error.js @@ -62,7 +62,7 @@ export default class GlFieldError { this.inputDomElement = this.inputElement.get(0); this.form = formErrors; this.errorMessage = this.inputElement.attr('title') || 'This field is required.'; - this.fieldErrorElement = $(`<p class='${errorMessageClass} hide'>${this.errorMessage}</p>`); + this.fieldErrorElement = $(`<p class='${errorMessageClass} hidden'>${this.errorMessage}</p>`); this.state = { valid: false, @@ -146,8 +146,8 @@ export default class GlFieldError { renderInvalid() { this.inputElement.addClass(inputErrorClass); - this.scopedSiblings.hide(); - return this.fieldErrorElement.show(); + this.scopedSiblings.addClass('hidden'); + return this.fieldErrorElement.removeClass('hidden'); } renderClear() { @@ -157,7 +157,7 @@ export default class GlFieldError { this.accessCurrentValue(trimmedInput); } this.inputElement.removeClass(inputErrorClass); - this.scopedSiblings.hide(); - this.fieldErrorElement.hide(); + this.scopedSiblings.addClass('hidden'); + this.fieldErrorElement.addClass('hidden'); } } diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index 764b130fdb8..7f64a9bd741 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -99,7 +99,7 @@ export default { /> </div> <div - class="avatar-container prepend-top-8 prepend-left-5 s24 hidden-xs" + class="avatar-container prepend-top-8 prepend-left-5 s24 d-none d-sm-block" :class="{ 'content-loading': group.isChildrenLoading }" > <a diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue index ea2b13a8b21..cabb3f59b17 100644 --- a/app/assets/javascripts/ide/components/file_finder/index.vue +++ b/app/assets/javascripts/ide/components/file_finder/index.vue @@ -39,12 +39,10 @@ export default { return this.allBlobs.slice(0, MAX_FILE_FINDER_RESULTS); } - return fuzzaldrinPlus - .filter(this.allBlobs, searchText, { - key: 'path', - maxResults: MAX_FILE_FINDER_RESULTS, - }) - .sort((a, b) => b.lastOpenedAt - a.lastOpenedAt); + return fuzzaldrinPlus.filter(this.allBlobs, searchText, { + key: 'path', + maxResults: MAX_FILE_FINDER_RESULTS, + }); }, filteredBlobsLength() { return this.filteredBlobs.length; diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index 6c373a92776..2c184ea726c 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -52,7 +52,10 @@ export default { methods: { ...mapActions(['toggleFileFinder']), mousetrapStopCallback(e, el, combo) { - if (combo === 't' && el.classList.contains('dropdown-input-field')) { + if ( + (combo === 't' && el.classList.contains('dropdown-input-field')) || + el.classList.contains('inputarea') + ) { return true; } else if (combo === 'command+p' || combo === 'ctrl+p') { return false; @@ -99,12 +102,12 @@ export default { class="ide-empty-state" > <div class="row js-empty-state"> - <div class="col-xs-12"> + <div class="col-12"> <div class="svg-content svg-250"> <img :src="emptyStateSvgPath" /> </div> </div> - <div class="col-xs-12"> + <div class="col-12"> <div class="text-content text-center"> <h4> Welcome to the GitLab IDE @@ -120,8 +123,6 @@ export default { </template> </div> </div> - <ide-status-bar - :file="activeFile" - /> + <ide-status-bar :file="activeFile"/> </article> </template> diff --git a/app/assets/javascripts/ide/components/ide_file_buttons.vue b/app/assets/javascripts/ide/components/ide_file_buttons.vue index a6c6f46a144..30b00abf6ed 100644 --- a/app/assets/javascripts/ide/components/ide_file_buttons.vue +++ b/app/assets/javascripts/ide/components/ide_file_buttons.vue @@ -32,14 +32,14 @@ export default { <template> <div v-if="showButtons" - class="pull-right ide-btn-group" + class="float-right ide-btn-group" > <a v-tooltip v-if="!file.binary" :href="file.blamePath" :title="__('Blame')" - class="btn btn-xs btn-transparent blame" + class="btn btn-sm btn-transparent blame" > <icon name="blame" @@ -50,7 +50,7 @@ export default { v-tooltip :href="file.commitsPath" :title="__('History')" - class="btn btn-xs btn-transparent history" + class="btn btn-sm btn-transparent history" > <icon name="history" @@ -61,7 +61,7 @@ export default { v-tooltip :href="file.permalink" :title="__('Permalink')" - class="btn btn-xs btn-transparent permalink" + class="btn btn-sm btn-transparent permalink" > <icon name="link" @@ -72,7 +72,7 @@ export default { v-tooltip :href="file.rawPath" target="_blank" - class="btn btn-xs btn-transparent prepend-left-10 raw" + class="btn btn-sm btn-transparent prepend-left-10 raw" rel="noopener noreferrer" :title="rawDownloadButtonLabel"> <icon diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue index 70c6d53c3ab..6f60cfbf184 100644 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ b/app/assets/javascripts/ide/components/ide_status_bar.vue @@ -1,14 +1,16 @@ <script> -import { mapGetters } from 'vuex'; +import { mapActions, mapState, mapGetters } from 'vuex'; import icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import timeAgoMixin from '~/vue_shared/mixins/timeago'; +import CiIcon from '../../vue_shared/components/ci_icon.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; export default { components: { icon, userAvatarImage, + CiIcon, }, directives: { tooltip, @@ -27,8 +29,16 @@ export default { }; }, computed: { + ...mapState(['currentBranchId', 'currentProjectId']), ...mapGetters(['currentProject', 'lastCommit']), }, + watch: { + lastCommit() { + if (!this.isPollingInitialized) { + this.initPipelinePolling(); + } + }, + }, mounted() { this.startTimer(); }, @@ -36,13 +46,21 @@ export default { if (this.intervalId) { clearInterval(this.intervalId); } + if (this.isPollingInitialized) { + this.stopPipelinePolling(); + } }, methods: { + ...mapActions(['pipelinePoll', 'stopPipelinePolling']), startTimer() { this.intervalId = setInterval(() => { this.commitAgeUpdate(); }, 1000); }, + initPipelinePolling() { + this.pipelinePoll(); + this.isPollingInitialized = true; + }, commitAgeUpdate() { if (this.lastCommit) { this.lastCommitFormatedAge = this.timeFormated(this.lastCommit.committed_date); @@ -61,6 +79,23 @@ export default { class="ide-status-branch" v-if="lastCommit && lastCommitFormatedAge" > + <span + class="ide-status-pipeline" + v-if="lastCommit.pipeline && lastCommit.pipeline.details" + > + <ci-icon + :status="lastCommit.pipeline.details.status" + v-tooltip + :title="lastCommit.pipeline.details.status.text" + /> + Pipeline + <a + class="monospace" + :href="lastCommit.pipeline.details.status.details_path">#{{ lastCommit.pipeline.id }}</a> + {{ lastCommit.pipeline.details.status.text }} + for + </span> + <icon name="commit" /> diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue index a0ce1c9dac7..f0b29702497 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/index.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue @@ -57,7 +57,7 @@ export default { <div class="dropdown" :class="{ - open: dropdownOpen, + show: dropdownOpen, }" > <button @@ -69,12 +69,12 @@ export default { <icon name="plus" :size="12" - css-classes="pull-left" + css-classes="float-left" /> <icon name="arrow-down" :size="12" - css-classes="pull-left" + css-classes="float-left" /> </button> <ul diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index a95a0225950..d83a90f71e1 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -70,12 +70,12 @@ export default { @submit="createEntryInStore" > <form - class="form-horizontal" slot="body" @submit.prevent="createEntryInStore" + class="form-group row append-bottom-0" > <fieldset class="form-group append-bottom-0"> - <label class="label-light col-sm-3 ide-new-modal-label"> + <label class="label-light col-form-label col-sm-3 ide-new-modal-label"> {{ __('Name') }} </label> <div class="col-sm-9"> diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index f8678b602ac..a281ecb95c8 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -43,9 +43,13 @@ export default { }, }, watch: { - file(oldVal, newVal) { + file(newVal, oldVal) { + if (oldVal.pending) { + this.removePendingTab(oldVal); + } + // Compare key to allow for files opened in review mode to be cached differently - if (newVal.key !== this.file.key) { + if (oldVal.key !== this.file.key) { this.initMonaco(); if (this.currentActivityView !== activityBarViews.edit) { @@ -99,6 +103,7 @@ export default { 'setFileViewMode', 'setFileEOL', 'updateViewer', + 'removePendingTab', ]), initMonaco() { if (this.shouldHideEditor) return; @@ -192,7 +197,7 @@ export default { > <div class="ide-mode-tabs clearfix" > <ul - class="nav-links pull-left" + class="nav-links float-left" v-if="!shouldHideEditor && isEditModeActive" > <li :class="editTabCSS"> diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue index 7bc865058c6..442697e1c80 100644 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ b/app/assets/javascripts/ide/components/repo_file.vue @@ -144,7 +144,7 @@ export default { :file="file" /> </span> - <span class="pull-right ide-file-icon-holder"> + <span class="float-right ide-file-icon-holder"> <mr-file-icon v-if="file.mrChange" /> @@ -177,7 +177,7 @@ export default { :project-id="file.projectId" :branch="file.branchId" :path="file.path" - class="pull-right prepend-left-8" + class="float-right prepend-left-8" /> </div> </div> diff --git a/app/assets/javascripts/ide/components/repo_loading_file.vue b/app/assets/javascripts/ide/components/repo_loading_file.vue index 79af8c0b0c7..3e47da88050 100644 --- a/app/assets/javascripts/ide/components/repo_loading_file.vue +++ b/app/assets/javascripts/ide/components/repo_loading_file.vue @@ -25,13 +25,13 @@ /> </td> <template v-if="!leftPanelCollapsed"> - <td class="hidden-sm hidden-xs"> + <td class="d-none d-sm-none d-md-block"> <skeleton-loading-container :small="true" /> </td> - <td class="hidden-xs"> + <td class="d-none d-sm-block"> <skeleton-loading-container class="animation-container-right" :small="true" diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js index adca85dc65b..a21cec4e8d8 100644 --- a/app/assets/javascripts/ide/ide_router.js +++ b/app/assets/javascripts/ide/ide_router.js @@ -41,7 +41,7 @@ const router = new VueRouter({ component: EmptyRouterComponent, children: [ { - path: ':targetmode(edit|tree|blob)/:branch/*', + path: ':targetmode(edit|tree|blob)/*', component: EmptyRouterComponent, }, { @@ -63,23 +63,27 @@ router.beforeEach((to, from, next) => { .then(() => { const fullProjectId = `${to.params.namespace}/${to.params.project}`; - if (to.params.branch) { - store.dispatch('setCurrentBranchId', to.params.branch); + const baseSplit = to.params[0].split('/-/'); + const branchId = baseSplit[0].slice(-1) === '/' ? baseSplit[0].slice(0, -1) : baseSplit[0]; + + if (branchId) { + const basePath = baseSplit.length > 1 ? baseSplit[1] : ''; + + store.dispatch('setCurrentBranchId', branchId); store.dispatch('getBranchData', { projectId: fullProjectId, - branchId: to.params.branch, + branchId, }); store .dispatch('getFiles', { projectId: fullProjectId, - branchId: to.params.branch, + branchId, }) .then(() => { - if (to.params[0]) { - const path = - to.params[0].slice(-1) === '/' ? to.params[0].slice(0, -1) : to.params[0]; + if (basePath) { + const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath; const treeEntryKey = Object.keys(store.state.entries).find( key => key === path && !store.state.entries[key].pending, ); diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js index a12e637616a..e8b51f2b516 100644 --- a/app/assets/javascripts/ide/services/index.js +++ b/app/assets/javascripts/ide/services/index.js @@ -75,4 +75,8 @@ export default { }, }); }, + lastCommitPipelines({ getters }) { + const commitSha = getters.lastCommit.id; + return Api.commitPipelines(getters.currentProject.path_with_namespace, commitSha); + }, }; diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index b6baa693104..13aea91d8ba 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -63,7 +63,9 @@ export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive const file = state.entries[path]; commit(types.TOGGLE_LOADING, { entry: file }); return service - .getFileData(`${gon.relative_url_root ? gon.relative_url_root : ''}${file.url}`) + .getFileData( + `${gon.relative_url_root ? gon.relative_url_root : ''}${file.url.replace('/-/', '/')}`, + ) .then(res => { const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); setPageTitle(pageTitle); diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index eff9bc03651..cece9154c82 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -1,6 +1,11 @@ +import Visibility from 'visibilityjs'; import flash from '~/flash'; +import { __ } from '~/locale'; import service from '../../services'; import * as types from '../mutation_types'; +import Poll from '../../../lib/utils/poll'; + +let eTagPoll; export const getProjectData = ( { commit, state, dispatch }, @@ -21,7 +26,7 @@ export const getProjectData = ( }) .catch(() => { flash( - 'Error loading project data. Please try again.', + __('Error loading project data. Please try again.'), 'alert', document, null, @@ -59,7 +64,7 @@ export const getBranchData = ( }) .catch(() => { flash( - 'Error loading branch data. Please try again.', + __('Error loading branch data. Please try again.'), 'alert', document, null, @@ -73,25 +78,74 @@ export const getBranchData = ( } }); -export const refreshLastCommitData = ( - { commit, state, dispatch }, - { projectId, branchId } = {}, -) => service - .getBranchData(projectId, branchId) - .then(({ data }) => { - commit(types.SET_BRANCH_COMMIT, { - projectId, - branchId, - commit: data.commit, +export const refreshLastCommitData = ({ commit, state, dispatch }, { projectId, branchId } = {}) => + service + .getBranchData(projectId, branchId) + .then(({ data }) => { + commit(types.SET_BRANCH_COMMIT, { + projectId, + branchId, + commit: data.commit, + }); + }) + .catch(() => { + flash(__('Error loading last commit.'), 'alert', document, null, false, true); }); - }) - .catch(() => { - flash( - 'Error loading last commit.', - 'alert', - document, - null, - false, - true, + +export const pollSuccessCallBack = ({ commit, state, dispatch }, { data }) => { + if (data.pipelines && data.pipelines.length) { + const lastCommitHash = + state.projects[state.currentProjectId].branches[state.currentBranchId].commit.id; + const lastCommitPipeline = data.pipelines.find( + pipeline => pipeline.commit.id === lastCommitHash, ); + commit(types.SET_LAST_COMMIT_PIPELINE, { + projectId: state.currentProjectId, + branchId: state.currentBranchId, + pipeline: lastCommitPipeline || {}, + }); + } + + return data; +}; + +export const pipelinePoll = ({ getters, dispatch }) => { + eTagPoll = new Poll({ + resource: service, + method: 'lastCommitPipelines', + data: { + getters, + }, + successCallback: ({ data }) => dispatch('pollSuccessCallBack', { data }), + errorCallback: () => { + flash( + __('Something went wrong while fetching the latest pipeline status.'), + 'alert', + document, + null, + false, + true, + ); + }, }); + + if (!Visibility.hidden()) { + eTagPoll.makeRequest(); + } + + Visibility.change(() => { + if (!Visibility.hidden()) { + eTagPoll.restart(); + } else { + eTagPoll.stop(); + } + }); +}; + +export const stopPipelinePolling = () => { + eTagPoll.stop(); +}; + +export const restartPipelinePolling = () => { + eTagPoll.restart(); +}; diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js index 7c82ce7976b..699710055e3 100644 --- a/app/assets/javascripts/ide/stores/index.js +++ b/app/assets/javascripts/ide/stores/index.js @@ -5,6 +5,7 @@ import * as actions from './actions'; import * as getters from './getters'; import mutations from './mutations'; import commitModule from './modules/commit'; +import pipelines from './modules/pipelines'; Vue.use(Vuex); @@ -15,5 +16,6 @@ export default new Vuex.Store({ getters, modules: { commit: commitModule, + pipelines, }, }); diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js index b85246b2502..cd25c3060f2 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/actions.js +++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js @@ -204,17 +204,23 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo dispatch('updateViewer', 'editor', { root: true }); router.push( - `/project/${rootState.currentProjectId}/blob/${getters.branchName}/${ + `/project/${rootState.currentProjectId}/blob/${getters.branchName}/-/${ rootGetters.activeFile.path }`, ); } }) .then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH)) - .then(() => dispatch('refreshLastCommitData', { - projectId: rootState.currentProjectId, - branchId: rootState.currentBranchId, - }, { root: true })); + .then(() => + dispatch( + 'refreshLastCommitData', + { + projectId: rootState.currentProjectId, + branchId: rootState.currentBranchId, + }, + { root: true }, + ), + ); }) .catch(err => { let errMsg = __('Error committing changes. Please try again.'); diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js new file mode 100644 index 00000000000..07f7b201f2e --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -0,0 +1,49 @@ +import { __ } from '../../../../locale'; +import Api from '../../../../api'; +import flash from '../../../../flash'; +import * as types from './mutation_types'; + +export const requestLatestPipeline = ({ commit }) => commit(types.REQUEST_LATEST_PIPELINE); +export const receiveLatestPipelineError = ({ commit }) => { + flash(__('There was an error loading latest pipeline')); + commit(types.RECEIVE_LASTEST_PIPELINE_ERROR); +}; +export const receiveLatestPipelineSuccess = ({ commit }, pipeline) => + commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, pipeline); + +export const fetchLatestPipeline = ({ dispatch, rootState }, sha) => { + dispatch('requestLatestPipeline'); + + return Api.pipelines(rootState.currentProjectId, { sha, per_page: '1' }) + .then(({ data }) => { + dispatch('receiveLatestPipelineSuccess', data.pop()); + }) + .catch(() => dispatch('receiveLatestPipelineError')); +}; + +export const requestJobs = ({ commit }) => commit(types.REQUEST_JOBS); +export const receiveJobsError = ({ commit }) => { + flash(__('There was an error loading jobs')); + commit(types.RECEIVE_JOBS_ERROR); +}; +export const receiveJobsSuccess = ({ commit }, data) => commit(types.RECEIVE_JOBS_SUCCESS, data); + +export const fetchJobs = ({ dispatch, state, rootState }, page = '1') => { + dispatch('requestJobs'); + + Api.pipelineJobs(rootState.currentProjectId, state.latestPipeline.id, { + page, + }) + .then(({ data, headers }) => { + const nextPage = headers && headers['x-next-page']; + + dispatch('receiveJobsSuccess', data); + + if (nextPage) { + dispatch('fetchJobs', nextPage); + } + }) + .catch(() => dispatch('receiveJobsError')); +}; + +export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js new file mode 100644 index 00000000000..d6c91f5b64d --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js @@ -0,0 +1,7 @@ +export const hasLatestPipeline = state => !state.isLoadingPipeline && !!state.latestPipeline; + +export const failedJobs = state => + state.stages.reduce( + (acc, stage) => acc.concat(stage.jobs.filter(job => job.status === 'failed')), + [], + ); diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/index.js b/app/assets/javascripts/ide/stores/modules/pipelines/index.js new file mode 100644 index 00000000000..b44c3141b81 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pipelines/index.js @@ -0,0 +1,12 @@ +import state from './state'; +import * as actions from './actions'; +import mutations from './mutations'; +import * as getters from './getters'; + +export default { + namespaced: true, + state: state(), + actions, + mutations, + getters, +}; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js new file mode 100644 index 00000000000..6b5701670a6 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutation_types.js @@ -0,0 +1,7 @@ +export const REQUEST_LATEST_PIPELINE = 'REQUEST_LATEST_PIPELINE'; +export const RECEIVE_LASTEST_PIPELINE_ERROR = 'RECEIVE_LASTEST_PIPELINE_ERROR'; +export const RECEIVE_LASTEST_PIPELINE_SUCCESS = 'RECEIVE_LASTEST_PIPELINE_SUCCESS'; + +export const REQUEST_JOBS = 'REQUEST_JOBS'; +export const RECEIVE_JOBS_ERROR = 'RECEIVE_JOBS_ERROR'; +export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS'; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js new file mode 100644 index 00000000000..2b16e57b386 --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pipelines/mutations.js @@ -0,0 +1,53 @@ +/* eslint-disable no-param-reassign */ +import * as types from './mutation_types'; + +export default { + [types.REQUEST_LATEST_PIPELINE](state) { + state.isLoadingPipeline = true; + }, + [types.RECEIVE_LASTEST_PIPELINE_ERROR](state) { + state.isLoadingPipeline = false; + }, + [types.RECEIVE_LASTEST_PIPELINE_SUCCESS](state, pipeline) { + state.isLoadingPipeline = false; + + if (pipeline) { + state.latestPipeline = { + id: pipeline.id, + status: pipeline.status, + }; + } + }, + [types.REQUEST_JOBS](state) { + state.isLoadingJobs = true; + }, + [types.RECEIVE_JOBS_ERROR](state) { + state.isLoadingJobs = false; + }, + [types.RECEIVE_JOBS_SUCCESS](state, jobs) { + state.isLoadingJobs = false; + + state.stages = jobs.reduce((acc, job) => { + let stage = acc.find(s => s.title === job.stage); + + if (!stage) { + stage = { + title: job.stage, + jobs: [], + }; + + acc.push(stage); + } + + stage.jobs = stage.jobs.concat({ + id: job.id, + name: job.name, + status: job.status, + stage: job.stage, + duration: job.duration, + }); + + return acc; + }, state.stages); + }, +}; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/state.js b/app/assets/javascripts/ide/stores/modules/pipelines/state.js new file mode 100644 index 00000000000..6f22542aaea --- /dev/null +++ b/app/assets/javascripts/ide/stores/modules/pipelines/state.js @@ -0,0 +1,6 @@ +export default () => ({ + isLoadingPipeline: false, + isLoadingJobs: false, + latestPipeline: null, + stages: [], +}); diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index a3fb3232f1d..0a3f8d031c4 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -23,6 +23,7 @@ export const SET_BRANCH = 'SET_BRANCH'; export const SET_BRANCH_COMMIT = 'SET_BRANCH_COMMIT'; export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE'; export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN'; +export const SET_LAST_COMMIT_PIPELINE = 'SET_LAST_COMMIT_PIPELINE'; // Tree mutation types export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA'; diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js index e09f88878f4..f17ec4da308 100644 --- a/app/assets/javascripts/ide/stores/mutations/branch.js +++ b/app/assets/javascripts/ide/stores/mutations/branch.js @@ -14,6 +14,10 @@ export default { treeId: `${projectPath}/${branchName}`, active: true, workingReference: '', + commit: { + ...branch.commit, + pipeline: {}, + }, }, }, }); @@ -28,4 +32,9 @@ export default { commit, }); }, + [types.SET_LAST_COMMIT_PIPELINE](state, { projectId, branchId, pipeline }) { + Object.assign(state.projects[projectId].branches[branchId].commit, { + pipeline, + }); + }, }; diff --git a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js index d249b05f47c..0a1c253c637 100644 --- a/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js +++ b/app/assets/javascripts/ide/stores/workers/files_decorator_worker.js @@ -26,7 +26,7 @@ self.addEventListener('message', e => { id: folderPath, name: folderName, path: folderPath, - url: `/${projectId}/tree/${branchId}/${folderPath}/`, + url: `/${projectId}/tree/${branchId}/-/${folderPath}/`, type: 'tree', parentTreeUrl: parentFolder ? parentFolder.url : `/${projectId}/tree/${branchId}/`, tempFile, @@ -64,7 +64,7 @@ self.addEventListener('message', e => { id: path, name: blobName, path, - url: `/${projectId}/blob/${branchId}/${path}`, + url: `/${projectId}/blob/${branchId}/-/${path}`, type: 'blob', parentTreeUrl: fileFolder ? fileFolder.url : `/${projectId}/blob/${branchId}`, tempFile, diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index a539506bce2..7ef5e679881 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -51,7 +51,7 @@ <template> <div class="prepend-top-default append-bottom-default clearfix"> <button - class="btn btn-save pull-left" + class="btn btn-save float-left" :class="{ disabled: formState.updateLoading || !isSubmitEnabled }" type="submit" :disabled="formState.updateLoading || !isSubmitEnabled" @@ -64,14 +64,14 @@ </i> </button> <button - class="btn btn-default pull-right" + class="btn btn-default float-right" type="button" @click="closeForm"> Cancel </button> <button v-if="shouldShowDeleteButton" - class="btn btn-danger pull-right append-right-default" + class="btn btn-danger float-right append-right-default" :class="{ disabled: deleteLoading }" type="button" :disabled="deleteLoading" diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue index 779705e19ac..ab8bd34762f 100644 --- a/app/assets/javascripts/issue_show/components/form.vue +++ b/app/assets/javascripts/issue_show/components/form.vue @@ -84,7 +84,7 @@ <div :class="{ 'col-sm-8 col-lg-9': hasIssuableTemplates, - 'col-xs-12': !hasIssuableTemplates, + 'col-12': !hasIssuableTemplates, }" > <title-field diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index ace45e9dd29..c67bd7fb0c6 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -1,5 +1,6 @@ import $ from 'jquery'; import _ from 'underscore'; +import StickyFill from 'stickyfilljs'; import axios from './lib/utils/axios_utils'; import { visitUrl } from './lib/utils/url_utility'; import bp from './breakpoints'; @@ -82,17 +83,11 @@ export default class Job { /** If the browser does not support position sticky, it returns the position as static. If the browser does support sticky, then we allow the browser to handle it, if not - then we default back to Bootstraps affix + then we use a polyfill **/ if (this.$topBar.css('position') !== 'static') return; - const offsetTop = this.$buildTrace.offset().top; - - this.$topBar.affix({ - offset: { - top: offsetTop, - }, - }); + StickyFill.add(this.$topBar); } // eslint-disable-next-line class-methods-use-this diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue index 21b545d6cab..c1044f4cd42 100644 --- a/app/assets/javascripts/jobs/components/header.vue +++ b/app/assets/javascripts/jobs/components/header.vue @@ -56,7 +56,7 @@ export default { actions.push({ label: 'New issue', path: this.job.new_issue_path, - cssClass: 'js-new-issue btn btn-new btn-inverted visible-md-block visible-lg-block', + cssClass: 'js-new-issue btn btn-new btn-inverted d-none d-md-block d-lg-block d-xl-block', type: 'link', }); } diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue index dfe87d89a39..83560a8ff0e 100644 --- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue +++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue @@ -39,7 +39,7 @@ <span v-if="hasHelpURL" - class="help-button pull-right" + class="help-button float-right" > <a :href="helpUrl" diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index db19dc9b238..82fec7e936b 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -48,7 +48,7 @@ export default { return `${this.job.runner.description} (#${this.job.runner.id})`; }, retryButtonClass() { - let className = 'js-retry-button pull-right btn btn-retry visible-md-block visible-lg-block'; + let className = 'js-retry-button pull-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; className += this.job.status && this.job.recoverable ? ' btn-primary' @@ -105,7 +105,7 @@ export default { type="button" :aria-label="__('Toggle Sidebar')" class="btn btn-blank gutter-toggle pull-right - visible-xs-block visible-sm-block js-sidebar-build-toggle" + d-block d-sm-block d-md-none js-sidebar-build-toggle" > <i aria-hidden="true" diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index e230dbbd4ac..6af22ecc990 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -35,7 +35,7 @@ export default class LabelManager { const $label = $(`#${$btn.data('domId')}`); const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); - $tooltip.tooltip('destroy'); + $tooltip.tooltip('dispose'); _this.toggleLabelPriority($label, action); _this.toggleEmptyState($label, $btn, action); } diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 9b62cfb8206..eafdaf4a672 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -120,7 +120,7 @@ export default class LabelsSelect { $sidebarLabelTooltip .attr('title', labelTooltipTitle) - .tooltip('fixTitle'); + .tooltip('_fixTitle'); $('.has-tooltip', $value).tooltip({ container: 'body' diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 9ff2042475b..8b5445d012b 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -51,7 +51,7 @@ export const rstrip = (val) => { return val; }; -export const updateTooltipTitle = ($tooltipEl, newTitle) => $tooltipEl.attr('title', newTitle).tooltip('fixTitle'); +export const updateTooltipTitle = ($tooltipEl, newTitle) => $tooltipEl.attr('title', newTitle).tooltip('_fixTitle'); export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventName = 'input') => { const field = $(fieldSelector); diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index c3d94d63c13..0ff23bbb061 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -2,10 +2,7 @@ import $ from 'jquery'; import timeago from 'timeago.js'; import dateFormat from 'vendor/date.format'; import { pluralize } from './text_utility'; -import { - languageCode, - s__, -} from '../../locale'; +import { languageCode, s__ } from '../../locale'; window.timeago = timeago; window.dateFormat = dateFormat; @@ -17,11 +14,37 @@ window.dateFormat = dateFormat; * * @param {Boolean} abbreviated */ -const getMonthNames = (abbreviated) => { +const getMonthNames = abbreviated => { if (abbreviated) { - return [s__('Jan'), s__('Feb'), s__('Mar'), s__('Apr'), s__('May'), s__('Jun'), s__('Jul'), s__('Aug'), s__('Sep'), s__('Oct'), s__('Nov'), s__('Dec')]; + return [ + s__('Jan'), + s__('Feb'), + s__('Mar'), + s__('Apr'), + s__('May'), + s__('Jun'), + s__('Jul'), + s__('Aug'), + s__('Sep'), + s__('Oct'), + s__('Nov'), + s__('Dec'), + ]; } - return [s__('January'), s__('February'), s__('March'), s__('April'), s__('May'), s__('June'), s__('July'), s__('August'), s__('September'), s__('October'), s__('November'), s__('December')]; + return [ + s__('January'), + s__('February'), + s__('March'), + s__('April'), + s__('May'), + s__('June'), + s__('July'), + s__('August'), + s__('September'), + s__('October'), + s__('November'), + s__('December'), + ]; }; /** @@ -29,7 +52,8 @@ const getMonthNames = (abbreviated) => { * @param {date} date * @returns {String} */ -export const getDayName = date => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()]; +export const getDayName = date => + ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()]; /** * @example @@ -55,7 +79,7 @@ export function getTimeago() { if (!timeagoInstance) { const localeRemaining = function getLocaleRemaining(number, index) { return [ - [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')], + [s__('Timeago|less than a minute ago'), s__('Timeago|right now')], [s__('Timeago|less than a minute ago'), s__('Timeago|%s seconds remaining')], [s__('Timeago|about a minute ago'), s__('Timeago|1 minute remaining')], [s__('Timeago|%s minutes ago'), s__('Timeago|%s minutes remaining')], @@ -73,7 +97,7 @@ export function getTimeago() { }; const locale = function getLocale(number, index) { return [ - [s__('Timeago|less than a minute ago'), s__('Timeago|in a while')], + [s__('Timeago|less than a minute ago'), s__('Timeago|right now')], [s__('Timeago|less than a minute ago'), s__('Timeago|in %s seconds')], [s__('Timeago|about a minute ago'), s__('Timeago|in 1 minute')], [s__('Timeago|%s minutes ago'), s__('Timeago|in %s minutes')], @@ -102,7 +126,7 @@ export function getTimeago() { * For the given element, renders a timeago instance. * @param {jQuery} $els */ -export const renderTimeago = ($els) => { +export const renderTimeago = $els => { const timeagoEls = $els || document.querySelectorAll('.js-timeago-render'); // timeago.js sets timeouts internally for each timeago value to be updated in real time @@ -119,7 +143,7 @@ export const localTimeAgo = ($timeagoEls, setTimeago = true) => { if (setTimeago) { // Recreate with custom template $(el).tooltip({ - template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', + template: '<div class="tooltip local-timeago" role="tooltip"><div class="arrow"></div><div class="tooltip-inner"></div></div>', }); } @@ -141,7 +165,9 @@ export const timeFor = (time, expiredLabel) => { if (new Date(time) < new Date()) { return expiredLabel || s__('Timeago|Past due'); } - return getTimeago().format(time, `${timeagoLanguageCode}-remaining`).trim(); + return getTimeago() + .format(time, `${timeagoLanguageCode}-remaining`) + .trim(); }; export const getDayDifference = (a, b) => { @@ -161,7 +187,7 @@ export const getDayDifference = (a, b) => { export function timeIntervalInWords(intervalInSeconds) { const secondsInteger = parseInt(intervalInSeconds, 10); const minutes = Math.floor(secondsInteger / 60); - const seconds = secondsInteger - (minutes * 60); + const seconds = secondsInteger - minutes * 60; let text = ''; if (minutes >= 1) { @@ -178,8 +204,34 @@ export function dateInWords(date, abbreviated = false, hideYear = false) { const month = date.getMonth(); const year = date.getFullYear(); - const monthNames = [s__('January'), s__('February'), s__('March'), s__('April'), s__('May'), s__('June'), s__('July'), s__('August'), s__('September'), s__('October'), s__('November'), s__('December')]; - const monthNamesAbbr = [s__('Jan'), s__('Feb'), s__('Mar'), s__('Apr'), s__('May'), s__('Jun'), s__('Jul'), s__('Aug'), s__('Sep'), s__('Oct'), s__('Nov'), s__('Dec')]; + const monthNames = [ + s__('January'), + s__('February'), + s__('March'), + s__('April'), + s__('May'), + s__('June'), + s__('July'), + s__('August'), + s__('September'), + s__('October'), + s__('November'), + s__('December'), + ]; + const monthNamesAbbr = [ + s__('Jan'), + s__('Feb'), + s__('Mar'), + s__('Apr'), + s__('May'), + s__('Jun'), + s__('Jul'), + s__('Aug'), + s__('Sep'), + s__('Oct'), + s__('Nov'), + s__('Dec'), + ]; const monthName = abbreviated ? monthNamesAbbr[month] : monthNames[month]; @@ -210,7 +262,7 @@ export const monthInWords = (date, abbreviated = false) => { * * @param {Date} date */ -export const totalDaysInMonth = (date) => { +export const totalDaysInMonth = date => { if (!date) { return 0; } @@ -223,12 +275,20 @@ export const totalDaysInMonth = (date) => { * * @param {Date} date */ -export const getSundays = (date) => { +export const getSundays = date => { if (!date) { return []; } - const daysToSunday = ['Saturday', 'Friday', 'Thursday', 'Wednesday', 'Tuesday', 'Monday', 'Sunday']; + const daysToSunday = [ + 'Saturday', + 'Friday', + 'Thursday', + 'Wednesday', + 'Tuesday', + 'Monday', + 'Sunday', + ]; const month = date.getMonth(); const year = date.getFullYear(); diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index 247aeb481c6..9803bebfd10 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -46,9 +46,9 @@ document.addEventListener('beforeunload', () => { // Unbind scroll events $(document).off('scroll'); // Close any open tooltips - $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy'); + $('.has-tooltip, [data-toggle="tooltip"]').tooltip('dispose'); // Close any open popover - $('[data-toggle="popover"]').popover('destroy'); + $('[data-toggle="popover"]').popover('dispose'); }); window.addEventListener('hashchange', handleLocationHash); @@ -111,7 +111,7 @@ document.addEventListener('DOMContentLoaded', () => { $('.remove-row').on('ajax:success', function removeRowAjaxSuccessCallback() { $(this) - .tooltip('destroy') + .tooltip('dispose') .closest('li') .fadeOut(); }); @@ -141,9 +141,9 @@ document.addEventListener('DOMContentLoaded', () => { }); // Initialize tooltips - $.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover'; $body.tooltip({ selector: '.has-tooltip, [data-toggle="tooltip"]', + trigger: 'hover', placement(tip, el) { return $(el).data('placement') || 'bottom'; }, @@ -196,7 +196,7 @@ document.addEventListener('DOMContentLoaded', () => { $container.remove(); }); - $('.navbar-toggle').on('click', () => { + $('.navbar-toggler').on('click', () => { $('.header-content').toggleClass('menu-expanded'); gl.lazyLoader.loadCheck(); }); diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js index db1d09eb2f2..70f185e3656 100644 --- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js +++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js @@ -351,7 +351,7 @@ import Cookies from 'js-cookie'; }, getCommitButtonText() { - const initial = 'Commit conflict resolution'; + const initial = 'Commit to source branch'; const inProgress = 'Committing...'; return this.state ? this.state.isSubmitting ? inProgress : initial : initial; diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 3f84f4b9499..3548c07aea8 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -362,7 +362,7 @@ export default class MergeRequestTabs { // // status - Boolean, true to show, false to hide toggleLoading(status) { - $('.mr-loading-status .loading').toggle(status); + $('.mr-loading-status .loading').toggleClass('hidden', status); } diffViewType() { diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue index a6dbe42a8f0..241627f9790 100644 --- a/app/assets/javascripts/monitoring/components/graph_group.vue +++ b/app/assets/javascripts/monitoring/components/graph_group.vue @@ -17,12 +17,12 @@ export default { <template> <div v-if="showPanels" - class="panel panel-default prometheus-panel" + class="card prometheus-panel" > - <div class="panel-heading"> + <div class="card-header"> <h4>{{ name }}</h4> </div> - <div class="panel-body prometheus-graph-group"> + <div class="card-body prometheus-graph-group"> <slot></slot> </div> </div> diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 96f2b3eac98..b2c1a26bbae 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1231,8 +1231,8 @@ export default class Notes { const isForced = forceShow === true || forceShow === false; const showNow = forceShow === true || (!isCurrentlyShown && !isForced); - targetRow.toggle(showNow); - notesContent.toggle(showNow); + targetRow.toggleClass('hide', !showNow); + notesContent.toggleClass('hide', !showNow); } if (addForm) { @@ -1675,7 +1675,7 @@ export default class Notes { <div class="note-header"> <div class="note-header-info"> <a href="/${_.escape(currentUsername)}"> - <span class="hidden-xs">${_.escape( + <span class="d-none d-sm-block">${_.escape( currentUsername, )}</span> <span class="note-headline-light">${_.escape( @@ -1694,7 +1694,7 @@ export default class Notes { </li>`, ); - $tempNote.find('.hidden-xs').text(_.escape(currentUserFullname)); + $tempNote.find('.d-none.d-sm-block').text(_.escape(currentUserFullname)); $tempNote .find('.note-headline-light') .text(`@${_.escape(currentUsername)}`); diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 48642c4a086..72d7e22fba0 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -321,7 +321,7 @@ Please check your network connection and try again.`; <li class="timeline-entry"> <div class="timeline-entry-inner"> <div class="flash-container error-alert timeline-content"></div> - <div class="timeline-icon hidden-xs hidden-sm"> + <div class="timeline-icon d-none d-sm-none d-md-block"> <user-avatar-link v-if="author" :link-href="author.path" @@ -369,7 +369,7 @@ js-gfm-input js-autosize markdown-area js-vue-textarea" </markdown-field> <div class="note-form-actions"> <div - class="pull-left btn-group + class="float-left btn-group append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"> <button @click.prevent="handleSave()" @@ -383,6 +383,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown" name="button" type="button" class="btn comment-btn note-type-toggle js-note-new-discussion dropdown-toggle" + data-display="static" data-toggle="dropdown" aria-label="Open comment type dropdown"> <i diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index e0f883a8e08..7f5aacaa3a2 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -100,7 +100,7 @@ export default { : 'div'; }, wrapperClass() { - return this.isDiffDiscussion ? '' : 'panel panel-default'; + return this.isDiffDiscussion ? '' : 'card'; }, }, mounted() { @@ -263,7 +263,7 @@ Please check your network connection and try again.`; class="discussion-reply-holder"> <template v-if="!isReplying && canReply"> <div - class="btn-group-justified discussion-with-resolve-btn" + class="btn-group d-flex discussion-with-resolve-btn" role="group"> <div class="btn-group" diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js index 9e6cf67dff0..00e27ca0e70 100644 --- a/app/assets/javascripts/notifications_form.js +++ b/app/assets/javascripts/notifications_form.js @@ -15,7 +15,7 @@ export default class NotificationsForm { toggleCheckbox(e) { const $checkbox = $(e.currentTarget); - const $parent = $checkbox.closest('.checkbox'); + const $parent = $checkbox.closest('.form-check'); this.saveEvent($checkbox, $parent); } diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js index 91f154b7ecd..ff4d6ab15f9 100644 --- a/app/assets/javascripts/pages/admin/admin.js +++ b/app/assets/javascripts/pages/admin/admin.js @@ -25,7 +25,7 @@ export default function adminInit() { $('body').on('click', '.js-toggle-colors-link', (e) => { e.preventDefault(); - $('.js-toggle-colors-container').toggle(); + $('.js-toggle-colors-container').toggleClass('hide'); }); $('.log-tabs a').on('click', function logTabsClick(e) { diff --git a/app/assets/javascripts/pages/ldap/omniauth_callbacks/index.js b/app/assets/javascripts/pages/ldap/omniauth_callbacks/index.js new file mode 100644 index 00000000000..edd7e38471b --- /dev/null +++ b/app/assets/javascripts/pages/ldap/omniauth_callbacks/index.js @@ -0,0 +1,3 @@ +import initU2F from '../../../shared/sessions/u2f'; + +document.addEventListener('DOMContentLoaded', initU2F); diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index fbdef329ab2..8e8f47c21d8 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -5,7 +5,7 @@ document.addEventListener('DOMContentLoaded', () => { const twoFactorNode = document.querySelector('.js-two-factor-auth'); const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true'; if (skippable) { - const button = `<a class="btn btn-xs btn-warning pull-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; + const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; const flashAlert = document.querySelector('.flash-alert .container-fluid'); if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button); } diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue index 25a88f846eb..17b91479ea5 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue @@ -42,7 +42,7 @@ </label> <span v-if="helpText" - class="help-block" + class="form-text text-muted" > {{ helpText }} </span> diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index 124bc2ba710..ca375007ec5 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -181,7 +181,7 @@ export default class UserTabs { toggleLoading(status) { return this.$parentEl.find('.loading-status .loading') - .toggle(status); + .toggleClass('hidden', status); } setCurrentAction(source) { @@ -195,6 +195,6 @@ export default class UserTabs { } getCurrentAction() { - return this.$parentEl.find('.nav-links .active a').data('action'); + return this.$parentEl.find('.nav-links a.active').data('action'); } } diff --git a/app/assets/javascripts/performance_bar/components/request_selector.vue b/app/assets/javascripts/performance_bar/components/request_selector.vue index 3ed07a4a47d..dd9578a6c7f 100644 --- a/app/assets/javascripts/performance_bar/components/request_selector.vue +++ b/app/assets/javascripts/performance_bar/components/request_selector.vue @@ -37,7 +37,7 @@ export default { <template> <div id="peek-request-selector" - class="pull-right" + class="float-right" > <select v-model="currentRequestId"> <option diff --git a/app/assets/javascripts/pipelines/components/blank_state.vue b/app/assets/javascripts/pipelines/components/blank_state.vue index 8d3d6223d7b..f3219b8291c 100644 --- a/app/assets/javascripts/pipelines/components/blank_state.vue +++ b/app/assets/javascripts/pipelines/components/blank_state.vue @@ -17,13 +17,13 @@ <template> <div class="row empty-state"> - <div class="col-xs-12"> + <div class="col-12"> <div class="svg-content"> <img :src="svgPath" /> </div> </div> - <div class="col-xs-12 text-center"> + <div class="col-12 text-center"> <div class="text-content"> <h4>{{ message }}</h4> </div> diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue index 10ac8c08bed..50c27bed9fd 100644 --- a/app/assets/javascripts/pipelines/components/empty_state.vue +++ b/app/assets/javascripts/pipelines/components/empty_state.vue @@ -19,13 +19,13 @@ </script> <template> <div class="row empty-state js-empty-state"> - <div class="col-xs-12"> + <div class="col-12"> <div class="svg-content svg-250"> <img :src="emptyStateSvgPath" /> </div> </div> - <div class="col-xs-12"> + <div class="col-12"> <div class="text-content"> <template v-if="canSetCi"> @@ -34,7 +34,7 @@ </h4> <p> - {{ s__(`Pipelines|Continous Integration can help + {{ s__(`Pipelines|Continuous Integration can help catch bugs by running your tests automatically, while Continuous Deployment can help you deliver code to your product environment.`) }} diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index 27b938c4985..dc16d395bcb 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -96,6 +96,7 @@ export default { :class="cssClassJobName" data-container="body" data-html="true" + data-boundary="viewport" class="js-pipeline-graph-job-link" > diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index ceb4d9ca604..4d965733f95 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -46,7 +46,7 @@ }; </script> <template> - <div class="table-section section-15 hidden-xs hidden-sm pipeline-tags"> + <div class="table-section section-15 d-none d-sm-none d-md-block pipeline-tags"> <a :href="pipeline.path" class="js-pipeline-url-link"> @@ -69,35 +69,35 @@ <span v-if="pipeline.flags.latest" v-tooltip - class="js-pipeline-url-latest label label-success" + class="js-pipeline-url-latest badge badge-success" title="Latest pipeline for this branch"> latest </span> <span v-if="pipeline.flags.yaml_errors" v-tooltip - class="js-pipeline-url-yaml label label-danger" + class="js-pipeline-url-yaml badge badge-danger" :title="pipeline.yaml_errors"> yaml invalid </span> <span v-if="pipeline.flags.failure_reason" v-tooltip - class="js-pipeline-url-failure label label-danger" + class="js-pipeline-url-failure badge badge-danger" :title="pipeline.failure_reason"> error </span> <a v-if="pipeline.flags.auto_devops" tabindex="0" - class="js-pipeline-url-autodevops label label-info autodevops-badge" + class="js-pipeline-url-autodevops badge badge-info autodevops-badge" v-popover="popoverOptions" role="button"> Auto DevOps </a> <span v-if="pipeline.flags.stuck" - class="js-pipeline-url-stuck label label-warning"> + class="js-pipeline-url-stuck badge badge-warning"> stuck </span> </div> diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index 3297af7bde4..e9bc3cf14ca 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -63,7 +63,7 @@ <loading-icon v-if="isLoading" /> </button> - <ul class="dropdown-menu dropdown-menu-align-right"> + <ul class="dropdown-menu dropdown-menu-right"> <li v-for="(action, i) in actions" :key="i" diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue index 1b9e0f917a4..31fcc9dd412 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue @@ -37,7 +37,7 @@ > </i> </button> - <ul class="dropdown-menu dropdown-menu-align-right"> + <ul class="dropdown-menu dropdown-menu-right"> <li v-for="(artifact, i) in artifacts" :key="i"> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue index 41986b827cd..4318abe97e0 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue @@ -37,6 +37,7 @@ return { pipelineId: '', endpoint: '', + cancelingPipeline: null, }; }, computed: { @@ -64,6 +65,7 @@ }, onSubmit() { eventHub.$emit('postAction', this.endpoint); + this.cancelingPipeline = this.pipelineId; }, }, }; @@ -106,6 +108,7 @@ :update-graph-dropdown="updateGraphDropdown" :auto-devops-help-path="autoDevopsHelpPath" :view-type="viewType" + :canceling-pipeline="cancelingPipeline" /> <modal diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 0f671ceea21..a3c17479e6f 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -46,12 +46,16 @@ type: String, required: true, }, + cancelingPipeline: { + type: String, + required: false, + default: null, + }, }, pipelinesTable: PIPELINES_TABLE, data() { return { isRetrying: false, - isCancelling: false, }; }, computed: { @@ -227,12 +231,14 @@ isChildView() { return this.viewType === 'child'; }, + + isCancelling() { + return this.cancelingPipeline === this.pipeline.id; + }, }, methods: { handleCancelClick() { - this.isCancelling = true; - eventHub.$emit('openConfirmationModal', { pipelineId: this.pipeline.id, endpoint: this.pipeline.cancel_path, @@ -325,7 +331,7 @@ <pipelines-artifacts-component v-if="pipeline.details.artifacts.length" - class="hidden-xs hidden-sm" + class="d-none d-sm-none d-md-block" :artifacts="pipeline.details.artifacts" /> diff --git a/app/assets/javascripts/pipelines/components/time_ago.vue b/app/assets/javascripts/pipelines/components/time_ago.vue index cd54d26c9d3..79dbdca4010 100644 --- a/app/assets/javascripts/pipelines/components/time_ago.vue +++ b/app/assets/javascripts/pipelines/components/time_ago.vue @@ -75,7 +75,7 @@ </p> <p - class="finished-at hidden-xs hidden-sm" + class="finished-at d-none d-sm-none d-md-block" v-if="hasFinishedTime" > diff --git a/app/assets/javascripts/profile/account/components/update_username.vue b/app/assets/javascripts/profile/account/components/update_username.vue index e5de3f69b01..a7a2a7235fd 100644 --- a/app/assets/javascripts/profile/account/components/update_username.vue +++ b/app/assets/javascripts/profile/account/components/update_username.vue @@ -86,7 +86,11 @@ Please update your Git repository remotes as soon as possible.`), <div class="form-group"> <label :for="$options.inputId">{{ s__('Profiles|Path') }}</label> <div class="input-group"> - <div class="input-group-addon">{{ rootUrl }}</div> + <div class="input-group-prepend"> + <div class="input-group-text"> + {{ rootUrl }} + </div> + </div> <input :id="$options.inputId" class="form-control" diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js index 6fedd94a6a9..f5cd1c3cc3e 100644 --- a/app/assets/javascripts/project_fork.js +++ b/app/assets/javascripts/project_fork.js @@ -4,6 +4,6 @@ export default () => { $('.js-fork-thumbnail').on('click', function forkThumbnailClicked() { if ($(this).hasClass('disabled')) return false; - return $('.js-fork-content').toggle(); + return $('.js-fork-content').toggleClass('hidden'); }); }; diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js index f31beb4dc78..6f06944ebb6 100644 --- a/app/assets/javascripts/project_label_subscription.js +++ b/app/assets/javascripts/project_label_subscription.js @@ -42,7 +42,7 @@ export default class ProjectLabelSubscription { const $button = $(button); if ($button.attr('data-original-title')) { - $button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle'); + $button.tooltip('hide').attr('data-original-title', newAction).tooltip('_fixTitle'); } return button; diff --git a/app/assets/javascripts/project_visibility.js b/app/assets/javascripts/project_visibility.js index 7c95c71e239..a52ac768e57 100644 --- a/app/assets/javascripts/project_visibility.js +++ b/app/assets/javascripts/project_visibility.js @@ -7,7 +7,7 @@ function setVisibilityOptions(namespaceSelector) { const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex]; const { name, visibility, visibilityLevel, showPath, editPath } = selectedNamespace.dataset; - document.querySelectorAll('.visibility-level-setting .radio').forEach((option) => { + document.querySelectorAll('.visibility-level-setting .form-check').forEach((option) => { const optionInput = option.querySelector('input[type=radio]'); const optionValue = optionInput ? optionInput.value : 0; const optionTitle = option.querySelector('.option-title'); diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 93603dfc14d..888b1d6ce33 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -66,8 +66,8 @@ const bindEvents = () => { .on('click', (e) => { e.preventDefault(); }) .popover({ title: $pushNewProjectTipTrigger.data('title'), - placement: 'auto bottom', - html: 'true', + placement: 'bottom', + html: true, content: $('.push-new-project-tip-template').html(), }) .on('shown.bs.popover', () => { diff --git a/app/assets/javascripts/projects_dropdown/components/search.vue b/app/assets/javascripts/projects_dropdown/components/search.vue index 0c46ed184be..7fcd62dcdb8 100644 --- a/app/assets/javascripts/projects_dropdown/components/search.vue +++ b/app/assets/javascripts/projects_dropdown/components/search.vue @@ -46,7 +46,7 @@ <template> <div - class="search-input-container hidden-xs" + class="search-input-container d-none d-sm-block" > <input type="search" diff --git a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js index 0a60f4845b2..1a75fdd75db 100644 --- a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js +++ b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js @@ -31,7 +31,7 @@ export default class PrometheusMetrics { /* eslint-disable class-methods-use-this */ handlePanelToggle(e) { const $toggleBtn = $(e.currentTarget); - const $currentPanelBody = $toggleBtn.closest('.panel').find('.panel-body'); + const $currentPanelBody = $toggleBtn.closest('.card').find('.card-body'); $currentPanelBody.toggleClass('hidden'); if ($toggleBtn.hasClass('fa-caret-down')) { $toggleBtn.removeClass('fa-caret-down').addClass('fa-caret-right'); diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index 2ce43ef0125..2fc3778820b 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -84,7 +84,7 @@ css-class="btn-default btn-transparent btn-clipboard" /> - <div class="controls hidden-xs pull-right"> + <div class="controls d-none d-sm-block float-right"> <button v-if="repo.canDelete" type="button" diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index 673b1db6769..e4a4b3bb129 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -120,7 +120,7 @@ <button v-if="item.canDelete" type="button" - class="js-delete-registry btn btn-danger hidden-xs pull-right" + class="js-delete-registry btn btn-danger d-none d-sm-block float-right" :title="s__('ContainerRegistry|Remove tag')" :aria-label="s__('ContainerRegistry|Remove tag')" data-container="body" diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 6eb0b62fa1c..2afcf4626b8 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -108,7 +108,7 @@ Sidebar.prototype.todoUpdateDone = function(data) { .attr('title', $el.data(`${attrPrefix}Text`)); if ($el.hasClass('has-tooltip')) { - $el.tooltip('fixTitle'); + $el.tooltip('_fixTitle'); } if ($el.data(`${attrPrefix}Icon`)) { diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue index 5eeb2a41bae..284a258d3c9 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue @@ -41,7 +41,7 @@ export default { </i> <a v-if="editable" - class="js-sidebar-dropdown-toggle edit-link pull-right" + class="js-sidebar-dropdown-toggle edit-link float-right" href="#" > {{ __('Edit') }} @@ -49,7 +49,7 @@ export default { <a v-if="showToggle" aria-label="Toggle sidebar" - class="gutter-toggle pull-right js-sidebar-toggle" + class="gutter-toggle float-right js-sidebar-toggle" href="#" role="button" > diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue index 2d00e8ac7e0..5a374d84796 100644 --- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue +++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue @@ -130,6 +130,7 @@ export default { v-tooltip data-container="body" data-placement="left" + data-boundary="viewport" :title="collapsedTooltipTitle" > <i diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue index 7f0de722f61..3f6e2f05396 100644 --- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue @@ -75,6 +75,7 @@ export default { v-tooltip data-container="body" data-placement="left" + data-boundary="viewport" :title="tooltipLabel" > <icon @@ -86,7 +87,7 @@ export default { {{ __('Confidentiality') }} <a v-if="isEditable" - class="pull-right confidential-edit" + class="float-right confidential-edit" href="#" @click.prevent="toggleForm" > diff --git a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue index 3783f71a848..4165aa19acf 100644 --- a/app/assets/javascripts/sidebar/components/confidential/edit_form.vue +++ b/app/assets/javascripts/sidebar/components/confidential/edit_form.vue @@ -32,7 +32,7 @@ export default { </script> <template> - <div class="dropdown open"> + <div class="dropdown show"> <div class="dropdown-menu sidebar-item-warning-message"> <div> <p diff --git a/app/assets/javascripts/sidebar/components/lock/edit_form.vue b/app/assets/javascripts/sidebar/components/lock/edit_form.vue index e1e4715826a..d392977e5e2 100644 --- a/app/assets/javascripts/sidebar/components/lock/edit_form.vue +++ b/app/assets/javascripts/sidebar/components/lock/edit_form.vue @@ -41,7 +41,7 @@ export default { </script> <template> - <div class="dropdown open"> + <div class="dropdown show"> <div class="dropdown-menu sidebar-item-warning-message"> <p class="text" diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue index 1a5e7b67eca..fb69c741dcd 100644 --- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue +++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue @@ -99,6 +99,7 @@ export default { v-tooltip data-container="body" data-placement="left" + data-boundary="viewport" :title="tooltipLabel" > <icon @@ -112,7 +113,7 @@ export default { {{ sprintf(__('Lock %{issuableDisplayName}'), { issuableDisplayName: issuableDisplayName }) }} <button v-if="isEditable" - class="pull-right lock-edit" + class="float-right lock-edit" type="button" @click.prevent="toggleForm" > diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue index 8f9e6761d20..0a945fc7fd5 100644 --- a/app/assets/javascripts/sidebar/components/participants/participants.vue +++ b/app/assets/javascripts/sidebar/components/participants/participants.vue @@ -84,6 +84,7 @@ v-tooltip data-container="body" data-placement="left" + data-boundary="viewport" :title="participantLabel" @click="onClickCollapsedIcon" > diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue index f0df759ef7a..6745c1aafff 100644 --- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue +++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue @@ -82,6 +82,7 @@ :title="notificationTooltip" data-container="body" data-placement="left" + data-boundary="viewport" > <icon :name="notificationIcon" @@ -91,12 +92,12 @@ /> </span> </div> - <span class="issuable-header-text hide-collapsed pull-left"> + <span class="issuable-header-text hide-collapsed float-left"> {{ __('Notifications') }} </span> <toggle-button ref="toggleButton" - class="pull-right hide-collapsed js-issuable-subscribe-button" + class="float-right hide-collapsed js-issuable-subscribe-button" :is-loading="showLoadingState" :value="subscribed" @change="toggleSubscription" diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue index 9d9ee9dea4d..209af1ce152 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue @@ -114,6 +114,7 @@ v-tooltip data-container="body" data-placement="left" + data-boundary="viewport" :title="tooltipText" > <icon name="timer" /> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue index 82c4562f9a9..6f79310b1cc 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue @@ -71,7 +71,7 @@ export default { </div> </div> <div class="compare-display-container"> - <div class="compare-display pull-left"> + <div class="compare-display float-left"> <span class="compare-label"> {{ s__('TimeTracking|Spent') }} </span> @@ -79,7 +79,7 @@ export default { {{ timeSpentHumanReadable }} </span> </div> - <div class="compare-display estimated pull-right"> + <div class="compare-display estimated float-right"> <span class="compare-label"> {{ s__('TimeTrackingEstimated|Est') }} </span> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index 8f5d0bee107..7d56d2fa5ee 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -116,7 +116,7 @@ export default { <div class="title hide-collapsed"> {{ __('Time tracking') }} <div - class="help-button pull-right" + class="help-button float-right" v-if="!showHelpState" @click="toggleHelpState(true)" > @@ -127,7 +127,7 @@ export default { </i> </div> <div - class="close-help-button pull-right" + class="close-help-button float-right" v-if="showHelpState" @click="toggleHelpState(false)" > diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 8486019897d..cd954f75613 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -202,7 +202,7 @@ function UsersSelect(currentUser, els, options = {}) { tooltipTitle = __('Assignee'); } $value.html(assigneeTemplate(user)); - $collapsedSidebar.attr('title', tooltipTitle).tooltip('fixTitle'); + $collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle'); return $collapsedSidebar.html(collapsedAssigneeTemplate(user)); }); }; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 1fea231c816..1608858da22 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -127,7 +127,7 @@ export default { </span> <loading-button v-if="deployment.stop_url" - container-class="btn btn-default btn-xs prepend-left-default" + container-class="btn btn-default btn-sm prepend-left-default" label="Stop environment" :loading="isStopping" @click="stopEnvironment" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue index cb6e9858736..8338fde61c7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue @@ -2,7 +2,7 @@ import tooltip from '../../vue_shared/directives/tooltip'; export default { - name: 'MRWidgetAuthor', + name: 'MrWidgetAuthor', directives: { tooltip, }, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue index 8f1fd809a81..644e4b7d81a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author_time.vue @@ -1,10 +1,10 @@ <script> - import mrWidgetAuthor from './mr_widget_author.vue'; + import MrWidgetAuthor from './mr_widget_author.vue'; export default { name: 'MRWidgetAuthorTime', components: { - mrWidgetAuthor, + MrWidgetAuthor, }, props: { actionText: { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue index 18ee4c62bf1..51a0fda6555 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue @@ -131,7 +131,7 @@ export default { aria-hidden="true"> </i> </button> - <ul class="dropdown-menu dropdown-menu-align-right"> + <ul class="dropdown-menu dropdown-menu-right"> <li> <a class="js-download-email-patches" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue index ebaf2b972eb..b404a592234 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue @@ -42,7 +42,7 @@ @click="refreshWidget" :disabled="isRefreshing" type="button" - class="btn btn-xs btn-default" + class="btn btn-sm btn-default" > <loading-icon v-if="isRefreshing" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue index dad4b0fe49d..5c1500ab801 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue @@ -43,13 +43,13 @@ To merge this request, first rebase locally.`) }} <a v-if="mr.canMerge && mr.conflictResolutionPath" :href="mr.conflictResolutionPath" - class="js-resolve-conflicts-button btn btn-default btn-xs" + class="js-resolve-conflicts-button btn btn-default btn-sm" > {{ s__("mrWidget|Resolve conflicts") }} </a> <button v-if="mr.canMerge" - class="js-merge-locally-button btn btn-default btn-xs" + class="js-merge-locally-button btn btn-default btn-sm" data-toggle="modal" data-target="#modal_merge_info" > diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue index 7d366c495f0..05fecd4de35 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue @@ -98,7 +98,7 @@ export default { </span> <button @click="refresh" - class="btn btn-default btn-xs js-refresh-button" + class="btn btn-default btn-sm js-refresh-button" type="button" > {{ s__("mrWidget|Refresh now") }} diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue index 7ff7fc7988a..e8352c362d6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue @@ -1,13 +1,13 @@ <script> import Flash from '../../../flash'; import statusIcon from '../mr_widget_status_icon.vue'; - import mrWidgetAuthor from '../../components/mr_widget_author.vue'; + import MrWidgetAuthor from '../../components/mr_widget_author.vue'; import eventHub from '../../event_hub'; export default { name: 'MRWidgetMergeWhenPipelineSucceeds', components: { - mrWidgetAuthor, + MrWidgetAuthor, statusIcon, }, props: { @@ -94,7 +94,7 @@ :disabled="isCancellingAutoMerge" role="button" href="#" - class="btn btn-xs btn-default js-cancel-auto-merge"> + class="btn btn-sm btn-default js-cancel-auto-merge"> <i v-if="isCancellingAutoMerge" class="fa fa-spinner fa-spin" @@ -129,7 +129,7 @@ :disabled="isRemovingSourceBranch" @click.prevent="removeSourceBranch" role="button" - class="btn btn-xs btn-default js-remove-source-branch" + class="btn btn-sm btn-default js-remove-source-branch" href="#" > <i diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index 3e36a3a10f9..8c7e0664559 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -118,7 +118,7 @@ <a v-if="mr.canRevertInCurrentMR" v-tooltip - class="btn btn-close btn-xs" + class="btn btn-close btn-sm" href="#modal-revert-commit" data-toggle="modal" data-container="body" @@ -129,7 +129,7 @@ <a v-else-if="mr.revertInForkPath" v-tooltip - class="btn btn-close btn-xs" + class="btn btn-close btn-sm" data-method="post" :href="mr.revertInForkPath" :title="revertTitle" @@ -139,7 +139,7 @@ <a v-if="mr.canCherryPickInCurrentMR" v-tooltip - class="btn btn-default btn-xs" + class="btn btn-default btn-sm" href="#modal-cherry-pick-commit" data-toggle="modal" data-container="body" @@ -150,7 +150,7 @@ <a v-else-if="mr.cherryPickInForkPath" v-tooltip - class="btn btn-default btn-xs" + class="btn btn-default btn-sm" data-method="post" :href="mr.cherryPickInForkPath" :title="cherryPickTitle" @@ -189,7 +189,7 @@ @click="removeSourceBranch" :disabled="isMakingRequest" type="button" - class="btn btn-xs btn-default js-remove-branch-button" + class="btn btn-sm btn-default js-remove-branch-button" > {{ s__("mrWidget|Remove Source Branch") }} </button> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue index 3d9161f6926..086dbabe77e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue @@ -18,10 +18,10 @@ export default { <template> <div class="mr-widget-body mr-widget-empty-state"> <div class="row"> - <div class="artwork col-sm-5 col-sm-push-7 col-xs-12 text-center"> + <div class="artwork col-md-5 order-md-last col-12 text-center"> <span v-html="emptyStateSVG"></span> </div> - <div class="text col-sm-7 col-sm-pull-5 col-xs-12"> + <div class="text col-md-7 order-md-first col-12"> <span> Merge requests are a place to propose changes you have made to a project and discuss those changes with others. diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 0264625a526..1d1c8ebc179 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -312,7 +312,7 @@ export default { v-else @click="toggleCommitMessageEditor" :disabled="isMergeButtonDisabled" - class="js-modify-commit-message-button btn btn-default btn-xs" + class="js-modify-commit-message-button btn btn-default btn-sm" type="button"> Modify commit message </button> @@ -329,7 +329,7 @@ export default { class="prepend-top-default commit-message-editor"> <div class="form-group clearfix"> <label - class="control-label" + class="col-form-label" for="commit-message"> Commit message </label> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue index a1f7e696795..8ea3f22ecc2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue @@ -28,7 +28,7 @@ export default { <a v-if="mr.createIssueToResolveDiscussionsPath" :href="mr.createIssueToResolveDiscussionsPath" - class="btn btn-default btn-xs js-create-issue" + class="btn btn-default btn-sm js-create-issue" > {{ s__("mrWidget|Create an issue to resolve them later") }} </a> diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue index 395a71acccf..7b5367ac19b 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/download_viewer.vue @@ -42,7 +42,7 @@ export default { target="_blank"> <icon name="download" - css-classes="pull-left append-right-8" + css-classes="float-left append-right-8" :size="16" /> {{ __('Download') }} diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue index dcf1489b37c..424af5a0293 100644 --- a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue +++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue @@ -87,7 +87,7 @@ <div :id="id" class="modal" - :class="id ? '' : 'show'" + :class="id ? '' : 'd-block'" role="dialog" tabindex="-1" > @@ -99,12 +99,12 @@ <div class="modal-content"> <div class="modal-header"> <slot name="header"> - <h4 class="modal-title pull-left"> + <h4 class="modal-title float-left"> {{ title }} </h4> <button type="button" - class="close pull-right" + class="close float-right" @click="emitCancel($event)" data-dismiss="modal" aria-label="Close" @@ -166,7 +166,7 @@ </div> <div v-if="!id" - class="modal-backdrop fade in" + class="modal-backdrop fade show" > </div> </div> diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue index f28e5e2715d..d5d5a7d3798 100644 --- a/app/assets/javascripts/vue_shared/components/gl_modal.vue +++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue @@ -53,6 +53,11 @@ export default { <div class="modal-content"> <div class="modal-header"> <slot name="header"> + <h4 class="modal-title"> + <slot name="title"> + {{ headerTitleText }} + </slot> + </h4> <button type="button" class="close js-modal-close-action" @@ -62,11 +67,6 @@ export default { > <span aria-hidden="true">×</span> </button> - <h4 class="modal-title"> - <slot name="title"> - {{ headerTitleText }} - </slot> - </h4> </slot> </div> diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index 088187ed348..ca17fa06a00 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -163,8 +163,8 @@ export default { <button v-if="hasSidebarButton" type="button" - class="btn btn-default visible-xs-block -visible-sm-block sidebar-toggle-btn js-sidebar-build-toggle js-sidebar-build-toggle-header" + class="btn btn-default d-block d-sm-none d-md-none +sidebar-toggle-btn js-sidebar-build-toggle js-sidebar-build-toggle-header" aria-label="Toggle Sidebar" id="toggleSidebar" > diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue index c0ee88bbf72..d63318f3da6 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue @@ -111,7 +111,7 @@ Attach a file </button> <button - class="btn btn-default btn-xs hide button-cancel-uploading-files" + class="btn btn-default btn-sm hide button-cancel-uploading-files" type="button" > Cancel diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue index 92d187e24bf..08d4936f480 100644 --- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue +++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue @@ -67,7 +67,7 @@ export default { <span v-if="shouldRenderBadge(tab.count)" - class="badge" + class="badge badge-pill" > {{ tab.count }} </span> diff --git a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue index 50b1508691b..eccba61a8c0 100644 --- a/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue +++ b/app/assets/javascripts/vue_shared/components/notes/placeholder_note.vue @@ -54,7 +54,7 @@ <div class="note-header"> <div class="note-header-info"> <a :href="getUserData.path"> - <span class="hidden-xs">{{ getUserData.name }}</span> + <span class="d-none d-sm-block">{{ getUserData.name }}</span> <span class="note-headline-light">@{{ getUserData.username }}</span> </a> </div> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue index 3fcacd156c5..71ec34f2c7a 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue @@ -116,7 +116,7 @@ v-if="isLoading" :inline="true" /> - <div class="pull-right"> + <div class="float-right"> <button v-if="editable && !editing" type="button" diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue index 34a07f33a23..3c400afdc1d 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue @@ -77,13 +77,13 @@ export default { <div class="clearfix"> <button type="button" - class="btn btn-primary pull-left js-new-label-btn disabled" + class="btn btn-primary float-left js-new-label-btn disabled" > {{ __('Create') }} </button> <button type="button" - class="btn btn-default pull-right js-cancel-label-btn" + class="btn btn-default float-right js-cancel-label-btn" > {{ __('Cancel') }} </button> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue index 7da82e90e29..9ac32ff13c6 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue @@ -21,7 +21,7 @@ export default { </i> <button type="button" - class="edit-link btn btn-blank pull-right js-sidebar-dropdown-toggle" + class="edit-link btn btn-blank float-right js-sidebar-dropdown-toggle" > {{ __('Edit') }} </button> diff --git a/app/assets/javascripts/vue_shared/directives/popover.js b/app/assets/javascripts/vue_shared/directives/popover.js index eb35294906b..c913bc34c68 100644 --- a/app/assets/javascripts/vue_shared/directives/popover.js +++ b/app/assets/javascripts/vue_shared/directives/popover.js @@ -17,6 +17,6 @@ export default { }, unbind(el) { - $(el).popover('destroy'); + $(el).popover('dispose'); }, }; diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js index b7f7e9fec15..155e6b6698a 100644 --- a/app/assets/javascripts/vue_shared/directives/tooltip.js +++ b/app/assets/javascripts/vue_shared/directives/tooltip.js @@ -6,10 +6,10 @@ export default { }, componentUpdated(el) { - $(el).tooltip('fixTitle'); + $(el).tooltip('_fixTitle'); }, unbind(el) { - $(el).tooltip('destroy'); + $(el).tooltip('dispose'); }, }; diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss new file mode 100644 index 00000000000..3b7ee5c73e6 --- /dev/null +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -0,0 +1,149 @@ +/* + * Scss to help with bootstrap 3 to 4 migration + */ + +$text-color: $gl-text-color; + +$brand-primary: $gl-primary; +$brand-success: $gl-success; +$brand-info: $gl-info; +$brand-warning: $gl-warning; +$brand-danger: $gl-danger; + +$border-radius-base: 3px !default; + +$modal-body-bg: $white-light; +$input-border: $border-color; +$input-border-focus: $focus-border-color; + +$padding-base-vertical: $gl-vert-padding; +$padding-base-horizontal: $gl-padding; + +html { + // Override default font size used in bs4 + font-size: 14px; +} + +button, +html [type="button"], +[type="reset"], +[type="submit"] { + // Override bootstrap reboot + -webkit-appearance: inherit; +} + +[role="button"] { + cursor: pointer; +} + +a { + color: $gl-link-color; +} + +.form-group.row .col-form-label { + // Bootstrap 4 aligns labels to the left + // for horizontal forms + @include media-breakpoint-up(md) { + text-align: right; + } +} + +table { + // Remove any table border lines + border-spacing: 0; +} + +.tooltip { + // Fix bootstrap4 bug whereby tooltips flicker when they are hovered over their borders + pointer-events: none; +} + +.popover { + font-size: 14px; +} + +@each $breakpoint in map-keys($grid-breakpoints) { + @include media-breakpoint-up($breakpoint) { + $infix: breakpoint-infix($breakpoint, $grid-breakpoints); + + .d#{$infix}-table-header-group { display: table-header-group !important; } + } +} + +.text-secondary { + // Override Bootstrap's light secondary color + // We have to use !important because bootstrap has that set as well + color: $gl-text-color-secondary !important; +} + +// Polyfill deprecated selectors + +.hidden { + display: none !important; + visibility: hidden !important; +} + +.hide { + display: none; +} + +.dropdown-toggle::after { + // Remove bootstrap's dropdown caret + display: none; +} + +.badge { + padding: 4px 5px; + font-size: 12px; + font-style: normal; + font-weight: $gl-font-weight-normal; + display: inline-block; + + &.badge-gray { + background-color: $label-gray-bg; + color: $gl-text-color; + text-shadow: none; + } + + &.badge-inverse { + background-color: $label-inverse-bg; + } +} + +.divider { + @extend .dropdown-divider; +} + +.info-well { + background: $theme-gray-50; + color: $gl-text-color; + border: 1px solid $border-color; + border-radius: 4px; + margin-bottom: 16px; + + .well-segment { + padding: 16px; + + &:not(:last-of-type) { + border-bottom: 1px solid $well-inner-border; + } + } +} + +.card { + &.card-without-border { + @extend .border-0; + } + + &.card-without-margin { + margin: 0; + } + + &.bg-light { + @extend .border-0; + } +} + +.nav-tabs .nav-link { + border: 0; +} diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 9bd35183d8a..7c28024001f 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -1,7 +1,7 @@ @import 'framework/variables'; @import 'framework/mixins'; -@import 'framework/tw_bootstrap_variables'; -@import 'framework/tw_bootstrap'; +@import '../../../node_modules/bootstrap/scss/bootstrap'; +@import 'bootstrap_migration'; @import 'framework/layout'; @import 'framework/animations'; diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 077d0424093..c1ec11e434a 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -31,7 +31,7 @@ .avatar { @extend .avatar-circle; - @include transition-property(none); + transition-property: none; width: 40px; height: 40px; diff --git a/app/assets/stylesheets/framework/badges.scss b/app/assets/stylesheets/framework/badges.scss index 6bbe32df772..57df9b969c3 100644 --- a/app/assets/stylesheets/framework/badges.scss +++ b/app/assets/stylesheets/framework/badges.scss @@ -1,4 +1,4 @@ -.badge { +.badge.badge-pill { font-weight: $gl-font-weight-normal; background-color: $badge-bg; color: $badge-color; diff --git a/app/assets/stylesheets/framework/banner.scss b/app/assets/stylesheets/framework/banner.scss index 02f3896d591..71bbab2065d 100644 --- a/app/assets/stylesheets/framework/banner.scss +++ b/app/assets/stylesheets/framework/banner.scss @@ -23,7 +23,7 @@ border-bottom: 1px solid $border-color; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { justify-content: center; flex-direction: column; align-items: center; diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index 9982a5779af..91dbb2a6365 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -37,7 +37,7 @@ flex: 0 0 100%; margin-bottom: 15px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { flex: 0 0 49%; &:nth-child(odd) { @@ -67,7 +67,7 @@ border: 1px solid $border-color; border-radius: $border-radius-default; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: flex; align-items: center; padding: 50px 30px; @@ -89,12 +89,12 @@ } .blank-state-body { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { text-align: center; margin-top: 20px; } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { padding-left: 20px; } } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index c60f65e7a85..c5be27f2d29 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -151,7 +151,7 @@ display: inline-block; margin-left: 5px; font-size: 18px; - color: $gray; + color: color("gray"); } p { @@ -195,7 +195,7 @@ } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { text-align: center; .avatar { @@ -315,7 +315,7 @@ } } - @media (max-width: $screen-sm-min) { + @include media-breakpoint-down(sm) { flex-direction: column; .inner-content { @@ -342,7 +342,7 @@ .btn { margin: $btn-side-margin 5px; - @media(max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { width: 100%; } } diff --git a/app/assets/stylesheets/framework/broadcast_messages.scss b/app/assets/stylesheets/framework/broadcast_messages.scss index 9b54fb94cdc..d3e7d751e63 100644 --- a/app/assets/stylesheets/framework/broadcast_messages.scss +++ b/app/assets/stylesheets/framework/broadcast_messages.scss @@ -19,3 +19,9 @@ @extend .broadcast-message; margin-bottom: 20px; } + +.toggle-colors { + input { + min-height: 34px; + } +} diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index cd9d60b96d3..0115f542c88 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -304,7 +304,8 @@ padding: 0 5px; } -.input-group-btn { +.input-group-prepend, +.input-group-append { .btn { @include btn-middle; @@ -393,7 +394,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .btn-wide-on-xs { width: 100%; } diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index c165ec0b94b..0b9dff64b0b 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -4,7 +4,7 @@ border-top: 0; direction: rtl; - @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { + @media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) { overflow-x: auto; } } diff --git a/app/assets/stylesheets/framework/ci_variable_list.scss b/app/assets/stylesheets/framework/ci_variable_list.scss index 5fe835dd8f9..7207e5119ce 100644 --- a/app/assets/stylesheets/framework/ci_variable_list.scss +++ b/app/assets/stylesheets/framework/ci_variable_list.scss @@ -10,14 +10,14 @@ display: flex; align-items: flex-start; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { align-items: flex-end; } &:not(:last-child) { margin-bottom: $gl-btn-padding; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { margin-bottom: 3 * $gl-btn-padding; } } @@ -26,7 +26,7 @@ .ci-variable-body-item:last-child { margin-right: $ci-variable-remove-button-width; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { margin-right: 0; } } @@ -35,7 +35,7 @@ display: none; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .ci-variable-row-body { margin-right: $ci-variable-remove-button-width; } @@ -48,7 +48,7 @@ align-items: flex-start; width: 100%; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: block; } } @@ -59,7 +59,7 @@ &:not(:last-child) { margin-right: $gl-btn-padding; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { margin-right: 0; margin-bottom: $gl-btn-padding; } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 2faea55a5f5..1e7b9534275 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -70,7 +70,7 @@ pre { padding: 0; } - &.well-pre { + &.card.card-body-pre { border: 1px solid $well-pre-bg; background: $gray-light; border-radius: 0; @@ -218,7 +218,7 @@ li.note { } .git_error_tips { - @extend .col-md-6; + @extend .col-lg-6; text-align: left; margin-top: 40px; @@ -308,7 +308,7 @@ img.emoji { .btn-sign-in { text-shadow: none; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-top: 8px; } } @@ -327,7 +327,7 @@ img.emoji { } } -.well { +.card.card-body { margin-bottom: $gl-padding; hr { @@ -336,7 +336,7 @@ img.emoji { } .search_box { - @extend .well; + @extend .card.card-body; text-align: center; } diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss index e2d97d0298f..1a415e1b852 100644 --- a/app/assets/stylesheets/framework/contextual_sidebar.scss +++ b/app/assets/stylesheets/framework/contextual_sidebar.scss @@ -1,11 +1,11 @@ .page-with-contextual-sidebar { transition: padding-left $sidebar-transition-duration; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { padding-left: $contextual-sidebar-collapsed-width; } - @media (min-width: $screen-lg-min) { + @include media-breakpoint-up(lg) { padding-left: $contextual-sidebar-width; } @@ -16,7 +16,7 @@ } .page-with-icon-sidebar { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { padding-left: $contextual-sidebar-collapsed-width; } } @@ -75,7 +75,7 @@ transform: translate3d(0, 0, 0); &:not(.sidebar-collapsed-desktop) { - @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { + @media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) { box-shadow: inset -2px 0 0 $border-color, 2px 1px 3px $dropdown-shadow-color; } @@ -88,7 +88,7 @@ overflow-x: hidden; } - .badge:not(.fly-out-badge), + .badge.badge-pill:not(.fly-out-badge), .sidebar-context-title, .nav-item-name { display: none; @@ -142,7 +142,7 @@ } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { left: (-$contextual-sidebar-width); } @@ -166,7 +166,7 @@ width: 100%; overflow: auto; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { overflow: hidden; } } @@ -207,7 +207,7 @@ > li { > a { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-right: 2px; } @@ -222,7 +222,7 @@ } .sidebar-sub-level-items { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { position: fixed; top: 0; left: 0; @@ -277,7 +277,7 @@ } } - .badge { + .badge.badge-pill { background-color: $inactive-badge-background; color: $gl-text-color-secondary; } @@ -291,7 +291,7 @@ padding-left: 11px; } - .badge { + .badge.badge-pill { font-weight: $gl-font-weight-bold; } @@ -339,7 +339,7 @@ } .toggle-sidebar-button { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: none; } } @@ -420,7 +420,7 @@ color: $gl-text-color-secondary; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: flex; align-items: center; @@ -429,7 +429,7 @@ } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { + .breadcrumbs-links { padding-left: $gl-padding; border-left: 1px solid $gl-text-color-quaternary; @@ -437,7 +437,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .close-nav-button { display: flex; } diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 664aade7375..1570b1f2eaa 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -24,7 +24,7 @@ display: none; } -.open { +.show.dropdown { .dropdown-menu, .dropdown-menu-nav { @include set-visible; @@ -32,7 +32,7 @@ max-height: $dropdown-max-height; overflow-y: auto; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { width: 100%; } } @@ -184,7 +184,7 @@ text-decoration: none; - .badge { + .badge.badge-pill { background-color: darken($dropdown-link-hover-bg, 5%); } } @@ -210,11 +210,10 @@ .dropdown-menu, .dropdown-menu-nav { - @include set-invisible; + display: none; position: absolute; width: auto; top: 100%; - left: 0; z-index: 300; min-width: 240px; max-width: 500px; @@ -328,7 +327,7 @@ color: $gl-text-color-secondary; } - .badge + span:not(.badge) { + .badge.badge-pill + span:not(.badge.badge-pill) { // Expects up to 3 digits on the badge margin-right: 40px; } @@ -392,7 +391,7 @@ transform: translateY(0); } -.comment-type-dropdown.open .dropdown-menu { +.comment-type-dropdown.show .dropdown-menu { display: block; } @@ -473,16 +472,11 @@ .dropdown-select { width: $dropdown-width; - @media (max-width: $screen-sm-min) { + @include media-breakpoint-down(sm) { width: 100%; } } -.dropdown-menu-align-right { - left: auto; - right: 0; -} - .dropdown-menu-selectable { li { a, @@ -783,7 +777,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .navbar-gitlab { li.header-projects, li.header-more, @@ -838,7 +832,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu { width: 70%; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex-direction: column; width: 100%; height: auto; @@ -894,7 +888,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu { height: 284px; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .projects-list-frequent-container { width: auto; height: auto; @@ -935,7 +929,7 @@ header.header-content .dropdown-menu.projects-dropdown-menu { color: $gl-text-color-secondary; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .project-item-metadata-container { float: none; } diff --git a/app/assets/stylesheets/framework/feature_highlight.scss b/app/assets/stylesheets/framework/feature_highlight.scss index 4f26cd015e4..cad915bc86f 100644 --- a/app/assets/stylesheets/framework/feature_highlight.scss +++ b/app/assets/stylesheets/framework/feature_highlight.scss @@ -79,7 +79,7 @@ border-right-color: $dropdown-border-color; } - .popover-content { + .popover-body { padding: 0; } } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index d835d49d8b2..f77ec4b6a2c 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -75,7 +75,7 @@ padding: 8px $gl-padding; border-bottom: 1px solid $border-color; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { text-align: left; } @@ -125,7 +125,7 @@ &.wiki { padding: $gl-padding; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { padding: $gl-padding * 2; } } @@ -364,7 +364,7 @@ span.idiff { } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: block; .file-actions { diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 621a4adc0cb..0ee5748952a 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -9,19 +9,19 @@ float: right; margin-right: 0; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { float: none; } } } .filters-section { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: inline-block; } } -@media (min-width: $screen-sm-min) { +@include media-breakpoint-up(sm) { .filter-item:not(:last-child) { margin-right: 6px; } @@ -37,7 +37,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .filter-item { display: block; margin: 0 0 10px; @@ -53,7 +53,7 @@ display: -webkit-flex; display: flex; - @media (max-width: $screen-xs-min) { + @include media-breakpoint-down(xs) { -webkit-flex-direction: column; flex-direction: column; } @@ -193,7 +193,7 @@ border: 1px solid $border-color; background-color: $white-light; - @media (max-width: $screen-xs-min) { + @include media-breakpoint-down(xs) { -webkit-flex: 1 1 auto; flex: 1 1 auto; margin-bottom: 10px; @@ -264,7 +264,7 @@ max-width: 280px; overflow: auto; - @media (max-width: $screen-xs-min) { + @include media-breakpoint-down(xs) { width: auto; left: 0; right: 0; @@ -315,7 +315,7 @@ .filtered-search-history-dropdown { width: 40%; - @media (max-width: $screen-xs-min) { + @include media-breakpoint-down(xs) { left: 0; right: 0; max-width: none; @@ -353,7 +353,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .issues-details-filters { padding: 0 0 10px; background-color: $white-light; @@ -361,7 +361,7 @@ } } -@media (max-width: $screen-xs) { +@include media-breakpoint-down(sm) { .filter-dropdown-container { .dropdown-toggle, .dropdown, diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index cb2f71b0033..52c3f18a682 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -8,19 +8,19 @@ .flash-notice { @extend .alert; - @extend .alert-info; + background-color: $blue-500; margin: 0; } .flash-warning { @extend .alert; - @extend .alert-warning; + background-color: $orange-500; margin: 0; } .flash-alert { @extend .alert; - @extend .alert-danger; + background-color: $red-500; margin: 0; .flash-text, @@ -42,14 +42,16 @@ .flash-success { @extend .alert; - @extend .alert-success; + background-color: $green-500; margin: 0; } .flash-notice, .flash-alert, - .flash-success { + .flash-success, + .flash-warning { border-radius: $border-radius-default; + color: $white-light; .container-fluid, .container-fluid.container-limited { @@ -72,7 +74,7 @@ } } -@media (max-width: $screen-sm-max) { +@include media-breakpoint-down(sm) { ul.notes { .flash-container.timeline-content { margin-left: 0; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 2c30311b1c1..c76ea532912 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -36,8 +36,8 @@ label { } } -.control-label { - @extend .col-sm-2; +.form-control-label { + @extend .col-md-2; } .inline-input-group { @@ -48,21 +48,21 @@ label { width: 150px; } -@media (min-width: $screen-sm-min) { +@include media-breakpoint-up(sm) { .custom-form-control { width: 150px; } } /* Medium devices (desktops, 992px and up) */ -@media (min-width: $screen-md-min) { +@include media-breakpoint-up(md) { .custom-form-control { width: 170px; } } /* Large devices (large desktops, 1200px and up) */ -@media (min-width: $screen-lg-min) { +@include media-breakpoint-up(lg) { .custom-form-control { width: 200px; } @@ -72,7 +72,7 @@ label { margin-left: 0; margin-right: 0; - .control-label { + .form-control-label { font-weight: $gl-font-weight-bold; padding-top: 4px; } @@ -83,7 +83,8 @@ label { font-family: $monospace_font; } - .input-group-btn .btn { + .input-group-prepend .btn, + .input-group-append .btn { padding: 3px $gl-btn-padding; background-color: $gray-light; border: 1px solid $border-color; @@ -102,10 +103,10 @@ label { } } - @media(max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding: 0 $gl-padding; - .control-label, + .form-control-label, .text-block { padding-left: 0; } @@ -120,6 +121,14 @@ label { @include box-shadow(none); border-radius: 2px; padding: $gl-vert-padding $gl-input-padding; + + &.input-short { + width: $input-short-width; + + @include media-breakpoint-up(md) { + width: $input-short-md-width; + } + } } .select-wrapper { @@ -155,8 +164,8 @@ label { margin-top: 35px; } -.form-group .control-label, -.form-group .control-label-full-width { +.form-group .form-control-label, +.form-group .form-control-label-full-width { font-weight: $gl-font-weight-normal; } @@ -170,17 +179,19 @@ label { max-width: 180px; } - .input-group-addon { + .input-group-prepend, + .input-group-append { background-color: $input-group-addon-bg; } - .input-group-addon:not(:first-child):not(:last-child) { + .input-group-prepend:not(:first-child):not(:last-child), + .input-group-append:not(:first-child):not(:last-child) { border-left: 0; border-right: 0; } } -.help-block { +.form-text.text-muted { margin-bottom: 0; margin-top: #{$grid-size / 2}; } @@ -221,7 +232,7 @@ label { } } -@media(max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .remember-me { .remember-me-checkbox { margin-top: 0; diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 0bbd6eb27c1..d6ae8cbb416 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -21,7 +21,7 @@ } .container-fluid { - .navbar-toggle { + .navbar-toggler { border-left: 1px solid lighten($color-700, 10%); } } @@ -63,7 +63,7 @@ &:hover, &:focus { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { background-color: rgba($color-200, 0.2); } @@ -165,14 +165,14 @@ } } - .sidebar-top-level-items > li.active .badge { + .sidebar-top-level-items > li.active .badge.badge-pill { color: $color-800; } - .nav-links li.active a { - border-bottom-color: $color-500; + .nav-links li a.active { + border-bottom: 2px solid $color-500; - .badge { + .badge.badge-pill { font-weight: $gl-font-weight-bold; } } @@ -277,8 +277,8 @@ body { } .container-fluid { - .navbar-toggle, - .navbar-toggle:hover { + .navbar-toggler, + .navbar-toggler:hover { color: $theme-gray-700; border-left: 1px solid $theme-gray-200; } @@ -328,7 +328,7 @@ body { } } - .sidebar-top-level-items > li.active .badge { + .sidebar-top-level-items > li.active .badge.badge-pill { color: $theme-gray-900; } } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 0136af76a13..2085e5646ef 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -37,6 +37,7 @@ } .header-content { + width: 100%; display: -webkit-flex; display: flex; justify-content: space-between; @@ -91,7 +92,7 @@ border-radius: $border-radius-default; .tanuki-logo { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-right: 8px; } } @@ -110,7 +111,7 @@ } &.menu-expanded { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .title-container { display: none; } @@ -133,13 +134,13 @@ border-top: 0; padding: 0; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex: 1 1 auto; } .nav { - > li:not(.hidden-xs) a { - @media (max-width: $screen-xs-max) { + > li:not(.d-none) a { + @include media-breakpoint-down(xs) { margin-left: 0; min-width: 100%; } @@ -156,7 +157,7 @@ } } - .navbar-toggle { + .navbar-toggler { right: -10px; border-radius: 0; min-width: 45px; @@ -181,14 +182,14 @@ } .navbar-nav { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: -webkit-flex; display: flex; padding-right: 10px; } li { - .badge { + .badge.badge-pill { box-shadow: none; font-weight: $gl-font-weight-bold; } @@ -197,7 +198,7 @@ .nav > li { &.header-user { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding-left: 10px; } } @@ -208,7 +209,7 @@ padding: 6px 8px; height: 32px; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding: 0; } @@ -324,8 +325,8 @@ fill: currentColor; } -.header-user .dropdown-menu-nav, -.header-new .dropdown-menu-nav { +.header-user .dropdown-menu, +.header-new .dropdown-menu { margin-top: $dropdown-vertical-offset; } @@ -378,7 +379,7 @@ margin-bottom: 0; line-height: 16px; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex-wrap: wrap; } @@ -410,7 +411,7 @@ .breadcrumb-item-text { text-decoration: inherit; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { @include str-truncated(128px); } } @@ -452,7 +453,7 @@ .navbar-nav { li { - .badge { + .badge.badge-pill { position: inherit; font-weight: $gl-font-weight-normal; margin-left: -6px; @@ -478,7 +479,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .navbar-gitlab .container-fluid { font-size: 18px; @@ -493,7 +494,7 @@ margin-left: -8px; margin-right: -10px; - .nav > li:not(.hidden-xs) { + .nav > li:not(.d-none) { display: table-cell !important; width: 25%; @@ -514,7 +515,7 @@ } .header-user { - .dropdown-menu-nav { + .dropdown-menu { width: auto; min-width: 160px; margin-top: 4px; diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index d8c57a0e2d9..1d247671761 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -11,7 +11,7 @@ padding: 5px 11px; margin-top: 4px; /* Small devices (tablets, 768px and up) */ - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { padding: 0 $gl-btn-padding; margin-top: 5px; } diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index d107422e517..0536c39cee7 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -15,7 +15,7 @@ body { background-color: $white-light !important; } - &.card-content { + &.board-card-content { background-color: $gray-darker; .content-wrapper { @@ -74,7 +74,7 @@ body { } /* Center alert text and alert action links on smaller screens */ - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { .alert { text-align: center; } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 45517416e93..17f4958d535 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -2,7 +2,7 @@ * Well styled list * */ -.well-list { +.card-body-list { position: relative; margin: 0; padding: 0; @@ -72,7 +72,7 @@ } } - .well-title { + .card.card-body-title { font-size: $list-font-size; line-height: 18px; } @@ -159,7 +159,7 @@ ul.content-list { &:last-child { margin-right: 0; - @media(max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { margin: 0 auto; } } @@ -173,7 +173,7 @@ ul.content-list { .member-controls { float: none; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { float: right; } } @@ -228,7 +228,7 @@ ul.content-list { } } - .label-default { + .badge-secondary { color: $gl-text-color-secondary; } @@ -237,7 +237,7 @@ ul.content-list { } } -.panel > .content-list > li { +.card > .content-list > li { padding: $gl-padding-top $gl-padding; } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 7b5d1c2cf8b..b893151e4fe 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -74,7 +74,7 @@ } .md-header-tab { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex: 1; width: 100%; border-bottom: 1px solid $border-color; @@ -90,7 +90,7 @@ &.active { display: block; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex: none; display: flex; justify-content: center; @@ -192,7 +192,7 @@ margin-left: $gl-padding; margin-right: -5px; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { margin-left: 0; margin-right: 0; } @@ -268,7 +268,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .atwho-view-ul { width: 350px; } diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 9e03bb98b8e..6244fb86fea 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -1,5 +1,5 @@ /** Common mobile (screen XS, SM) styles **/ -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .container .content { margin-top: 20px; } @@ -14,7 +14,7 @@ font-size: 12px; margin-right: 3px; - .badge { + .badge.badge-pill { display: none; } } @@ -86,7 +86,7 @@ } } -@media (max-width: $screen-sm-max) { +@include media-breakpoint-down(sm) { .issues-filters { .milestone-filter { display: none; diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss index eb789cc64b0..667661d8b5c 100644 --- a/app/assets/stylesheets/framework/modal.scss +++ b/app/assets/stylesheets/framework/modal.scss @@ -46,7 +46,7 @@ margin-left: $grid-size; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex-direction: column; .btn + .btn { @@ -55,7 +55,7 @@ } } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { .btn:first-of-type { margin-left: auto; } @@ -74,7 +74,7 @@ body.modal-open { } } -@media (min-width: $screen-lg-min) { +@include media-breakpoint-up(lg) { .modal-full { width: 98%; } @@ -84,7 +84,7 @@ body.modal-open { background-color: $black-transparent; z-index: 2100; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { .modal-dialog { margin: 30px auto; } diff --git a/app/assets/stylesheets/framework/page_header.scss b/app/assets/stylesheets/framework/page_header.scss index 0c879f40930..660e3dcac8d 100644 --- a/app/assets/stylesheets/framework/page_header.scss +++ b/app/assets/stylesheets/framework/page_header.scss @@ -3,7 +3,7 @@ padding: 10px 0; margin-bottom: 0; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: flex; align-items: center; @@ -19,7 +19,7 @@ margin-right: 3px; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .btn { width: 100%; margin-top: 10px; @@ -35,7 +35,7 @@ @extend .avatar-inline; margin-left: 0; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-left: 4px; } } diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss index c3ec9db0f07..d3e013590b6 100644 --- a/app/assets/stylesheets/framework/pagination.scss +++ b/app/assets/stylesheets/framework/pagination.scss @@ -6,6 +6,7 @@ .pagination { padding: 0; + margin: 20px 0; a { cursor: pointer; @@ -30,7 +31,7 @@ } } -.panel > .gl-pagination { +.card > .gl-pagination { margin: 0; } @@ -44,7 +45,7 @@ display: none; } - .page { + .page-item { display: none; &.active { @@ -57,13 +58,13 @@ /** * Small screen pagination */ -@media (max-width: $screen-xs-min) { +@include media-breakpoint-down(xs) { .gl-pagination { .pagination li a { padding: 6px 10px; } - .page { + .page-item { display: none; &.active { @@ -76,9 +77,9 @@ /** * Medium screen pagination */ -@media (min-width: $screen-xs-min) and (max-width: $screen-md-max) { +@media (min-width: map-get($grid-breakpoints, xs)) and (max-width: map-get($grid-breakpoints, sm)) { .gl-pagination { - .page { + .page-item { display: none; &.active, diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index e8d69e62194..a8e28104a94 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -1,14 +1,14 @@ -.panel { +.card { margin-bottom: $gl-padding; } -.panel-slim { - @extend .panel; +.card-slim { + @extend .card; margin-bottom: $gl-vert-padding; } -.panel-heading { +.card-header { padding: $gl-vert-padding $gl-padding; line-height: 36px; @@ -21,7 +21,7 @@ line-height: 20px; } - .badge { + .badge.badge-pill { margin-top: -2px; margin-left: 5px; } @@ -41,12 +41,13 @@ } } -.panel-empty-heading { +.card-empty-heading { border-bottom: 0; } -.panel-body { +.card-body { padding: $gl-padding; + background-color: $white-light; .form-actions { margin: -$gl-padding; @@ -54,7 +55,7 @@ } } -.panel-title { +.card-title { font-size: inherit; line-height: inherit; } diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss index 34fccf6f0a4..764bebd82c6 100644 --- a/app/assets/stylesheets/framework/responsive_tables.scss +++ b/app/assets/stylesheets/framework/responsive_tables.scss @@ -6,7 +6,7 @@ .gl-responsive-table-row-layout { width: 100%; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { display: flex; align-items: center; @@ -21,7 +21,7 @@ margin-top: 10px; border: 1px solid $border-color; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { margin: 0; padding: $gl-padding 0; border: 0; @@ -44,13 +44,13 @@ &.section-#{$width} { flex: 0 0 #{$width + '%'}; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { max-width: #{$width + '%'}; } } } - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { display: flex; align-self: stretch; padding: 10px; @@ -65,7 +65,7 @@ &.section-wrap { white-space: normal; - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { flex-wrap: wrap; } } @@ -76,11 +76,11 @@ } .table-button-footer { - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { text-align: right; } - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { display: block; align-self: stretch; min-height: 0; @@ -122,7 +122,7 @@ .table-row-header { font-size: 13px; - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { display: none; } } @@ -132,13 +132,13 @@ color: $gl-text-color-secondary; text-align: left; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { display: none; } } .table-mobile-content { - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { @include flex-max-width(60); text-align: right; } @@ -154,7 +154,7 @@ overflow: hidden; text-overflow: ellipsis; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { flex: 0 0 90%; } diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 66dbe403385..c3c64adf3da 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -27,18 +27,18 @@ color: $black; border-bottom: 2px solid $gray-darkest; - .badge { + .badge.badge-pill { color: $black; } } - } - - &.active a { - color: $black; - font-weight: $gl-font-weight-bold; - .badge { + &.active { color: $black; + font-weight: $gl-font-weight-bold; + + .badge.badge-pill { + color: $black; + } } } } @@ -56,7 +56,7 @@ white-space: normal; /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { width: 100%; } } @@ -80,7 +80,7 @@ } /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { width: 100%; &.mobile-separator { @@ -124,21 +124,13 @@ position: relative; /* Medium devices (desktops, 992px and up) */ - @media (min-width: $screen-md-min) { width: 200px; } + @include media-breakpoint-up(md) { width: 200px; } /* Large devices (large desktops, 1200px and up) */ - @media (min-width: $screen-lg-min) { width: 250px; } - - &.input-short { - /* Medium devices (desktops, 992px and up) */ - @media (min-width: $screen-md-min) { width: 170px; } - - /* Large devices (large desktops, 1200px and up) */ - @media (min-width: $screen-lg-min) { width: 210px; } - } + @include media-breakpoint-up(lg) { width: 250px; } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding-bottom: 0; width: 100%; @@ -164,10 +156,6 @@ } } - .input-short { - width: 100%; - } - .icon-label { display: inline-block; } @@ -184,7 +172,7 @@ .nav-controls { width: auto; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { width: 100%; } } @@ -204,7 +192,7 @@ width: 100%; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex-flow: row wrap; .nav-controls { @@ -364,7 +352,7 @@ max-width: 300px; width: auto; - @media(max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { max-width: 250px; } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 64fff7463d2..8c716400913 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -31,7 +31,7 @@ .right-sidebar-collapsed { padding-right: 0; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { padding-right: $gutter_collapsed_width; } @@ -65,13 +65,13 @@ display: inline-flex; } - @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + @include media-breakpoint-only(sm) { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { padding-right: $gutter_collapsed_width; } } - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { .content-wrapper { padding-right: $gutter_width; } diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss index 430633bb01b..f80e9b1014b 100644 --- a/app/assets/stylesheets/framework/snippets.scss +++ b/app/assets/stylesheets/framework/snippets.scss @@ -40,7 +40,7 @@ } .snippet-actions { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { float: right; } } diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 5bde96caf42..a10bd1544c5 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -48,7 +48,7 @@ table { } .responsive-table { - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { th { width: 100%; } diff --git a/app/assets/stylesheets/framework/tabs.scss b/app/assets/stylesheets/framework/tabs.scss index c8ba14b7066..6b60149fbbb 100644 --- a/app/assets/stylesheets/framework/tabs.scss +++ b/app/assets/stylesheets/framework/tabs.scss @@ -1,6 +1,7 @@ .gitlab-tabs { background: $gray-light; border: 1px solid $border-color; + flex-wrap: nowrap; li { width: 50%; diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 373f35e71d8..75c11590547 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -4,7 +4,7 @@ padding: 0; &::before { - @include notes-media('max', $screen-xs-min) { + @include notes-media('max', map-get($grid-breakpoints, xs)) { background: none; } } @@ -34,7 +34,7 @@ .timeline-entry-inner { position: relative; - @include notes-media('max', $screen-xs-min) { + @include notes-media('max', map-get($grid-breakpoints, xs)) { .timeline-icon { display: none; } diff --git a/app/assets/stylesheets/framework/toggle.scss b/app/assets/stylesheets/framework/toggle.scss index 0cd83df218f..d5cc78a6680 100644 --- a/app/assets/stylesheets/framework/toggle.scss +++ b/app/assets/stylesheets/framework/toggle.scss @@ -121,7 +121,7 @@ cursor: not-allowed; } - @media (max-width: $screen-xs-min) { + @include media-breakpoint-down(xs) { width: 50px; &::before, diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss deleted file mode 100644 index 1c6e2bf3074..00000000000 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Twitter bootstrap with GitLab customizations/additions - * - */ - -// Core variables and mixins -@import "bootstrap/variables"; -@import "bootstrap/mixins"; - -// Reset -@import "bootstrap/normalize"; -@import "bootstrap/print"; - -// Core CSS -@import "bootstrap/scaffolding"; -@import "bootstrap/type"; -@import "bootstrap/code"; -@import "bootstrap/grid"; -@import "bootstrap/tables"; -@import "bootstrap/forms"; -@import "bootstrap/buttons"; - -// Components -@import "bootstrap/component-animations"; -// @import "bootstrap/dropdowns"; -@import "bootstrap/button-groups"; -@import "bootstrap/input-groups"; -@import "bootstrap/navs"; -@import "bootstrap/navbar"; -@import "bootstrap/breadcrumbs"; -@import "bootstrap/pagination"; -@import "bootstrap/pager"; -@import "bootstrap/labels"; -@import "bootstrap/badges"; -@import "bootstrap/alerts"; -@import "bootstrap/progress-bars"; -@import "bootstrap/list-group"; -@import "bootstrap/wells"; -@import "bootstrap/close"; -@import "bootstrap/panels"; - -// Components w/ JavaScript -@import "bootstrap/modals"; -@import "bootstrap/tooltip"; -@import "bootstrap/popovers"; - -// Utility classes -.clearfix { - @include clearfix(); -} - -.center-block { - @include center-block(); -} - -.pull-right { - float: right !important; -} - -.pull-left { - float: left !important; -} - -.hide { - display: none; -} - -.show { - display: block !important; -} - -.invisible { - visibility: hidden; -} - -.text-hide { - @include text-hide(); -} - -.hidden { - display: none !important; - visibility: hidden !important; -} - -.affix { - position: fixed; -} - -/* - * Fix <summary> elements on firefox - * See https://github.com/necolas/normalize.css/issues/640 - * and https://github.com/twbs/bootstrap/issues/21060 - * - */ -summary { - display: list-item; -} - -@import "bootstrap/responsive-utilities"; - -// Labels -.label { - padding: 4px 5px; - font-size: 12px; - font-style: normal; - font-weight: $gl-font-weight-normal; - display: inline-block; - - &.label-gray { - background-color: $label-gray-bg; - color: $gl-text-color; - text-shadow: none; - } - - &.label-inverse { - background-color: $label-inverse-bg; - } -} - - -/** - * fix to keep tooltips position in top navigation bar - * - */ -.navbar .nav > li { - position: relative; - white-space: nowrap; -} - -/** - * Add some extra stuff to panels - * - */ - -.panel { - box-shadow: none; - - .panel-body { - form, - pre { - margin: 0; - } - - .form-actions { - margin: -15px; - margin-top: 18px; - } - } - - .panel-footer { - .pagination { - margin: 0; - } - - .btn { - min-width: 124px; - } - - .btn-clipboard { - min-width: 0; - } - } - - &.panel-small { - .panel-heading { - padding: 6px 15px; - font-size: 13px; - font-weight: $gl-font-weight-normal; - - a { - color: $panel-heading-link-color; - } - } - } - - &.panel-without-border { - border: 0; - } - - &.panel-without-margin { - margin: 0; - } -} - -.panel-succes .panel-heading, -.panel-info .panel-heading, -.panel-danger .panel-heading, -.panel-warning .panel-heading, -.panel-primary .panel-heading, -.alert { - a:not(.btn) { - @extend .alert-link; - color: $white-light; - text-decoration: underline; - } -} - -// Prevent datetimes on tooltips to break into two lines -.local-timeago { - white-space: nowrap; -} diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss deleted file mode 100644 index d04e555769b..00000000000 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ /dev/null @@ -1,199 +0,0 @@ -// Override Bootstrap variables here (defaults from bootstrap-sass v3.3.3): -// For all variables see https://github.com/twbs/bootstrap-sass/blob/master/templates/project/_bootstrap-variables.sass -// -// Variables -// -------------------------------------------------- - - -//== Colors -// -//## Gray and brand colors for use across Bootstrap. - -// $gray-base: #000 -// $gray-darker: lighten($gray-base, 13.5%) // #222 -// $gray-dark: lighten($gray-base, 20%) // #333 -// $gray: lighten($gray-base, 33.5%) // #555 -// $gray-light: lighten($gray-base, 46.7%) // #777 -// $gray-lighter: lighten($gray-base, 93.5%) // #eee - -$brand-primary: $gl-primary; -$brand-success: $gl-success; -$brand-info: $gl-info; -$brand-warning: $gl-warning; -$brand-danger: $gl-danger; - -$border-radius-base: 3px !default; -$border-radius-large: 3px !default; -$border-radius-small: 3px !default; - - -//== Scaffolding -// -$text-color: $gl-text-color; -$link-color: $gl-link-color; -$link-hover-color: $gl-link-hover-color; - - -//== Typography -// -//## Font, line-height, and color for body text, headings, and more. - -$font-family-sans-serif: $regular_font; -$font-family-monospace: $monospace_font; -$font-size-base: $gl-font-size; - - -//== Components -// -//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). - -$padding-base-vertical: $gl-vert-padding; -$padding-base-horizontal: $gl-padding; -$component-active-color: $white-light; -$component-active-bg: $brand-info; - -//== Forms -// -//## - -$input-color: $text-color; -$input-border: $border-color; -$input-border-focus: $focus-border-color; -$legend-color: $text-color; - - -//== Pagination -// -//## - -$pagination-color: $gl-text-color; -$pagination-bg: $white-light; -$pagination-border: $border-color; - -$pagination-hover-color: $gl-text-color; -$pagination-hover-bg: $row-hover; -$pagination-hover-border: $border-color; - -$pagination-active-color: $white-light; -$pagination-active-bg: $gl-link-color; -$pagination-active-border: $gl-link-color; - -$pagination-disabled-color: #cdcdcd; -$pagination-disabled-bg: $gray-light; -$pagination-disabled-border: $border-color; - - -//== Form states and alerts -// -//## Define colors for form feedback states and, by default, alerts. - -$state-success-text: $white-light; -$state-success-bg: $brand-success; -$state-success-border: $brand-success; - -$state-info-text: $white-light; -$state-info-bg: $brand-info; -$state-info-border: $brand-info; - -$state-warning-text: $white-light; -$state-warning-bg: $brand-warning; -$state-warning-border: $brand-warning; - -$state-danger-text: $white-light; -$state-danger-bg: $brand-danger; -$state-danger-border: $brand-danger; - - -//== Alerts -// -//## Define alert colors, border radius, and padding. - -$alert-border-radius: 0; - - -//== Panels -// -//## - -$panel-border-radius: 2px; -$panel-default-text: $text-color; -$panel-default-border: $border-color; -$panel-default-heading-bg: $gray-light; -$panel-footer-bg: $gray-light; -$panel-inner-border: $border-color; - -$badge-bg: $badge-bg; -$badge-color: $badge-color; - -//== Wells -// -//## - -$well-bg: $gray-light; -$well-border: #eee; - -//== Code -// -//## - -$code-color: $red-600; -$code-bg: lighten($red-100, 2%); - -$kbd-color: $white-light; -$kbd-bg: #333; - -//== Buttons -// -//## -$btn-default-color: $gl-text-color; -$btn-default-bg: $white-light; -$btn-default-border: #e7e9ed; - -//== Nav -// -//## -$nav-link-padding: 13px $gl-padding; - -//== Code -// -//## -$pre-bg: $gray-light !default; -$pre-color: $gl-text-color !default; -$pre-border-color: $border-color; - -$table-bg-accent: $gray-light; - -$zindex-popover: 900; - -//== Modals -// -//## - -//** Padding applied to the modal body -$modal-inner-padding: $gl-padding; - -//** Padding applied to the modal title -$modal-title-padding: $gl-padding; -//** Modal title line-height -// $modal-title-line-height: $line-height-base - -//** Background color of modal content area -$modal-content-bg: $gray-light; -$modal-body-bg: $white-light; -//** Modal content border color -// $modal-content-border-color: rgba(0,0,0,.2) -//** Modal content border color **for IE8** -// $modal-content-fallback-border-color: #999 - -//** Modal backdrop background color -// $modal-backdrop-bg: #000 -//** Modal backdrop opacity -// $modal-backdrop-opacity: .5 -//** Modal header border color -// $modal-header-border-color: #e5e5e5 -//** Modal footer border color -// $modal-footer-border-color: $modal-header-border-color - -$modal-lg: 860px; -$modal-md: 540px; -// $modal-sm: 300px diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 9e1371648ed..ed0bfbbe08b 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -114,7 +114,7 @@ font-size: 0.95em; } - blockquote { + .blockquote { color: $gl-grayish-blue; font-size: inherit; padding: 8px 24px; @@ -122,12 +122,12 @@ border-left: 3px solid $white-dark; } - blockquote:dir(rtl) { + .blockquote:dir(rtl) { border-left: 0; border-right: 3px solid $white-dark; } - blockquote p { + .blockquote p { color: $gl-grayish-blue !important; font-size: inherit; line-height: 1.5; @@ -321,6 +321,16 @@ h6 { /** CODE **/ pre { font-family: $monospace_font; + display: block; + padding: $gl-padding-8; + margin: 0 0 $gl-padding-8; + font-size: 13px; + word-break: break-all; + word-wrap: break-word; + color: $gl-text-color; + background-color: $gray-light; + border: 1px solid $border-color; + border-radius: $border-radius-small; } code { @@ -391,7 +401,7 @@ h4 { } .text-right-lg { - @media (min-width: $screen-lg-min) { + @include media-breakpoint-up(lg) { text-align: right; } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index b5505538541..89b61530ddb 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -142,6 +142,11 @@ $border-gray-normal-dashed: darken($gray-normal, $darken-border-dashed-factor); $border-gray-dark: darken($white-normal, $darken-border-factor); /* + * Override Bootstrap 4 variables + */ +$secondary: $gray-light; + +/* * UI elements */ $border-color: #e5e5e5; @@ -230,7 +235,7 @@ $row-hover: $blue-50; $row-hover-border: $blue-200; $progress-color: #c0392b; $header-height: 40px; -$ide-statusbar-height: 27px; +$ide-statusbar-height: 25px; $fixed-layout-width: 1280px; $limited-layout-width: 990px; $limited-layout-width-sm: 790px; @@ -306,6 +311,11 @@ $gl-warning: $orange-500; $gl-danger: $red-500; $gl-btn-active-background: rgba(0, 0, 0, 0.16); $gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background; +// Bootstrap override states +$success: $gl-success; +$info: $gl-info; +$warning: $gl-warning; +$danger: $gl-danger; /* * Commit Diff Colors @@ -554,6 +564,8 @@ $input-danger-border: $red-400; $input-group-addon-bg: #f7f8fa; $gl-field-focus-shadow: rgba(0, 0, 0, 0.075); $gl-field-focus-shadow-error: rgba($red-500, 0.6); +$input-short-width: 200px; +$input-short-md-width: 280px; /* * Help diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss index 3fa7a260017..514fac82b1e 100644 --- a/app/assets/stylesheets/framework/wells.scss +++ b/app/assets/stylesheets/framework/wells.scss @@ -5,7 +5,7 @@ border-radius: $border-radius-default; margin-bottom: $gl-padding; - .well-segment { + .card.card-body-segment { padding: $gl-padding; &:not(:last-of-type) { @@ -59,7 +59,7 @@ } } - .label.label-gray { + .label-gray { background-color: $well-expand-item; } @@ -108,7 +108,7 @@ } } -.well-centered { +.card.card-body-centered { h1 { font-weight: $gl-font-weight-normal; text-align: center; diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss index 658ac26fca9..b5eda79e5ed 100644 --- a/app/assets/stylesheets/mailers/highlighted_diff_email.scss +++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss @@ -138,7 +138,7 @@ pre { margin: 0; } -blockquote { +.blockquote { color: $gl-grayish-blue; padding: 0 0 0 15px; margin: 0; diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 6bb40bae9ed..1c3d312f7ac 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -58,7 +58,7 @@ .boards-app { position: relative; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { transition: width $sidebar-transition-duration; width: 100%; @@ -81,11 +81,11 @@ white-space: nowrap; min-height: 200px; - @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + @include media-breakpoint-only(sm) { height: calc(100vh - #{$issue-board-list-difference-sm}); } - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { height: calc(100vh - #{$issue-board-list-difference-md}); } @@ -94,13 +94,13 @@ 100vh - #{$issue-board-list-difference-xs} - #{$performance-bar-height} ); - @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { + @include media-breakpoint-only(sm) { height: calc( 100vh - #{$issue-board-list-difference-sm} - #{$performance-bar-height} ); } - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { height: calc( 100vh - #{$issue-board-list-difference-md} - #{$performance-bar-height} ); @@ -117,7 +117,7 @@ white-space: normal; vertical-align: top; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 400px; } @@ -274,7 +274,7 @@ font-size: (26px / $issue-boards-font-size) * 1em; } -.card { +.board-card { position: relative; padding: 11px 10px 11px $gl-padding; background: $white-light; @@ -290,7 +290,7 @@ } &.is-active, - &.is-active .card-assignee:hover a { + &.is-active .board-card-assignee:hover a { background-color: $row-hover; &:first-child:not(:only-child) { @@ -298,7 +298,7 @@ } } - .label { + .badge { border: 0; outline: 0; } @@ -310,7 +310,7 @@ } } -.card-title { +.board-card-title { @include overflow-break-word(); margin: 0 30px 0 0; font-size: 1em; @@ -322,11 +322,11 @@ } } -.card-header { +.board-card-header { display: flex; min-height: 20px; - .card-assignee { + .board-card-assignee { display: flex; justify-content: flex-end; position: absolute; @@ -397,16 +397,16 @@ } } -.card-footer { +.board-card-footer { margin: 0 0 5px; - .label { + .badge { margin-top: 5px; margin-right: 6px; } } -.card-number { +.board-card-number { font-size: 12px; color: $gl-text-color-secondary; } @@ -564,11 +564,11 @@ .add-issues-list-column { width: 100%; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 50%; } - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { width: (100% / 3); } } @@ -583,11 +583,11 @@ margin-right: -$gl-vert-padding; overflow-y: scroll; - .card-parent { + .board-card-parent { padding: 0 5px 5px; } - .card { + .board-card { border: 1px solid $border-gray-dark; box-shadow: 0 1px 2px rgba($issue-boards-card-shadow, 0.3); cursor: pointer; @@ -637,7 +637,7 @@ display: none; margin-right: 10px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: block; } } @@ -645,7 +645,7 @@ .dropdown-menu-toggle { width: 100px; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { width: 140px; } } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 50f32660445..9ee02ca1d83 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -215,7 +215,7 @@ } .header-action-buttons { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .sidebar-toggle-btn { margin-top: 0; margin-left: 10px; @@ -277,10 +277,6 @@ &.coverage { padding: 0 16px 11px; } - - .btn-group-justified { - margin-top: 5px; - } } .block-last { @@ -305,7 +301,7 @@ background-color: $white-light; } - .label { + .badge.badge-pill { margin-left: 2px; } @@ -320,7 +316,7 @@ } } - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { display: block; .btn { diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 3fd13078131..2b6d92016d5 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -63,7 +63,7 @@ text-decoration: none; } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { > div { display: flex; align-items: center; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 944996159d7..a4ca82de90e 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -23,7 +23,7 @@ } .commit-hash-full { - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { width: 80px; white-space: nowrap; overflow: hidden; @@ -183,7 +183,7 @@ } .commit-actions { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { .fa-spinner { font-size: 12px; } @@ -327,8 +327,12 @@ } &.invalid { - @include status-color($gray-dark, $gray, $gray-darkest); + @include status-color($gray-dark, color("gray"), $gray-darkest); border-color: $gray-darkest; + + &:not(span):hover { + color: color("gray"); + } } } diff --git a/app/assets/stylesheets/pages/convdev_index.scss b/app/assets/stylesheets/pages/convdev_index.scss index fb1899284fd..bd338326154 100644 --- a/app/assets/stylesheets/pages/convdev_index.scss +++ b/app/assets/stylesheets/pages/convdev_index.scss @@ -53,19 +53,19 @@ $space-between-cards: 8px; padding: $space-between-cards / 2; position: relative; - @media (min-width: $screen-xs-min) { + @include media-breakpoint-up(xs) { width: percentage(1 / 4); } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: percentage(1 / 5); } - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { width: percentage(1 / 6); } - @media (min-width: $screen-lg-min) { + @include media-breakpoint-up(lg) { width: percentage(1 / 10); } } @@ -82,7 +82,7 @@ $space-between-cards: 8px; .convdev-card-low { border-top-color: $color-low-score; - .card-score-big { + .board-card-score-big { background-color: $red-50; } } @@ -90,7 +90,7 @@ $space-between-cards: 8px; .convdev-card-average { border-top-color: $color-average-score; - .card-score-big { + .board-card-score-big { background-color: $orange-50; } } @@ -98,7 +98,7 @@ $space-between-cards: 8px; .convdev-card-high { border-top-color: $color-high-score; - .card-score-big { + .board-card-score-big { background-color: $green-50; } } @@ -112,14 +112,14 @@ $space-between-cards: 8px; margin: 0 0 2px; } - .text-light { + .light-text { font-size: 13px; line-height: 1.25; color: $gl-text-color-secondary; } } -.card-scores { +.board-card-scores { display: flex; justify-content: space-around; align-items: center; @@ -127,22 +127,22 @@ $space-between-cards: 8px; line-height: 1; } -.card-score { +.board-card-score { color: $gl-text-color-secondary; - .card-score-name { + .board-card-score-name { font-size: 13px; margin-top: 4px; } } -.card-score-value { +.board-card-score-value { font-size: 16px; color: $gl-text-color; font-weight: $gl-font-weight-normal; } -.card-score-big { +.board-card-score-big { border-top: 2px solid $border-color; border-bottom: 1px solid $border-color; font-size: 22px; @@ -150,7 +150,7 @@ $space-between-cards: 8px; font-weight: $gl-font-weight-normal; } -.card-buttons { +.board-card-buttons { display: flex; > * { diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index cfef6476d4d..a22c666a525 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -15,7 +15,7 @@ max-width: 480px; padding: 0 $gl-padding; - @media (max-width: $screen-sm-min) { + @include media-breakpoint-down(sm) { margin: 0 auto; } } @@ -75,13 +75,13 @@ } } - .panel { + .card { .content-block { padding: 24px 0; border-bottom: 0; position: relative; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding: 6px 0 24px; } } @@ -89,7 +89,7 @@ .column { text-align: center; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding: 15px 0; } @@ -106,7 +106,7 @@ } &:last-child { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { text-align: center; } } @@ -213,7 +213,7 @@ .stage-panel { min-width: 968px; - .panel-heading { + .card-header { padding: 0; background-color: transparent; } @@ -266,7 +266,9 @@ &.issue-title, &.commit-title, &.merge-merquest-title { - @include text-overflow(); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; max-width: 100%; display: block; diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 2f2c04206e2..7b36bcb3c7d 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -14,7 +14,7 @@ white-space: nowrap; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: block; } } @@ -25,7 +25,7 @@ display: flex; flex-grow: 1; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { padding-left: 0; padding-right: 0; } @@ -36,7 +36,7 @@ flex-shrink: 0; flex: 0 0 auto; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { width: 100%; margin-top: 10px; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 70ce5de6a6c..f06c9dcdf8c 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -86,13 +86,13 @@ &.left-side-selected { td.line_content.parallel.right-side { - @include user-select(none); + user-select: none; } } &.right-side-selected { td.line_content.parallel.left-side { - @include user-select(none); + user-select: none; } } } @@ -109,7 +109,7 @@ .old_line, .new_line { - @include user-select(none); + user-select: none; margin: 0; border: 0; padding: 0 5px; @@ -592,14 +592,14 @@ } .commit-stat-summary { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-left: -$gl-padding; padding-left: $gl-padding; background-color: $white-light; } } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { position: -webkit-sticky; position: sticky; top: 24px; @@ -610,10 +610,6 @@ top: 76px; } - &:not(.is-stuck) .diff-stats-additions-deletions-collapsed { - display: none; - } - &.is-stuck { padding-top: 0; padding-bottom: 0; @@ -622,13 +618,21 @@ .diff-stats-additions-deletions-expanded, .inline-parallel-buttons { - display: none; + display: none !important; + } + } + } + + @include media-breakpoint-up(lg) { + &.is-stuck { + .diff-stats-additions-deletions-collapsed { + display: block !important; } } } } -@media (min-width: $screen-sm-min) { +@include media-breakpoint-up(sm) { .with-performance-bar { .diff-files-changed.diff-files-changed-merge-request { top: 76px + $performance-bar-height; @@ -641,7 +645,7 @@ width: 100%; z-index: 150; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { left: $gl-padding; } @@ -772,9 +776,9 @@ } } -.frame .badge, -.image-diff-avatar-link .badge, -.notes > .badge { +.frame .badge.badge-pill, +.image-diff-avatar-link .badge.badge-pill, +.notes > .badge.badge-pill { position: absolute; background-color: $blue-400; color: $white-light; @@ -788,7 +792,7 @@ } } -.frame .badge, +.frame .badge.badge-pill, .frame .image-comment-badge { // Center align badges on the frame transform: translate(-50%, -50%); @@ -811,14 +815,14 @@ .image-diff-avatar-link { position: relative; - .badge, + .badge.badge-pill, .image-comment-badge { top: 25px; right: 8px; } } -.notes > .badge { +.notes > .badge.badge-pill { display: none; left: -13px; } @@ -840,7 +844,7 @@ display: none; } - .notes > .badge { + .notes > .badge.badge-pill { display: block; } } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 8ecda50602d..437621299e0 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -37,7 +37,7 @@ padding-top: 7px; padding-bottom: 7px; - .pull-right { + .float-right { height: 20px; } } @@ -63,11 +63,11 @@ max-width: 450px; float: left; - @media(max-width: $screen-md-max) { + @media(max-width: map-get($grid-breakpoints, lg)-1) { width: 280px; } - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { width: 180px; } } @@ -111,10 +111,10 @@ } -@media(max-width: $screen-xs-max){ +@include media-breakpoint-down(xs) { .file-editor { .file-title { - .pull-right { + .float-right { height: auto; } } @@ -153,7 +153,7 @@ vertical-align: top; display: inline-block; - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { display: block; margin: 19px 0 12px; } @@ -166,7 +166,7 @@ padding: 0 0 0 14px; border-left: 1px solid $border-color; - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { display: block; width: 100%; margin: 5px 0; @@ -181,7 +181,7 @@ margin-top: 6px; line-height: 21px; - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { display: block; margin: 5px 0; } @@ -193,7 +193,7 @@ vertical-align: top; margin: 5px 0 0 8px; - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { display: block; width: 100%; margin: 0 0 16px; @@ -209,7 +209,7 @@ font-family: $regular_font; margin-top: -5px; - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { display: block; width: 100%; margin: 5px 0; @@ -223,7 +223,7 @@ width: 250px; vertical-align: top; - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { display: block; width: 100%; margin: 5px 0; @@ -237,7 +237,7 @@ display: inline-block; margin: 7px 0 0 10px; - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { display: block; width: 100%; margin: 20px 0; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 1f406cc1c2d..cd0d67613c3 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -1,4 +1,4 @@ -@media (max-width: $screen-md-max) { +@include media-breakpoint-down(md) { .deployments-container { width: 100%; overflow: auto; @@ -138,7 +138,7 @@ border-left: 0; border-right: 0; - @media (min-width: $screen-sm-max) { + @media (min-width: map-get($grid-breakpoints, md)-1) { border-top: 0; } } @@ -251,7 +251,7 @@ font-size: 16px; } - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { min-width: 100%; } } @@ -432,7 +432,7 @@ } } - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { .label-axis-text, .text-metric-usage, .legend-axis-text { diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index d9267f5cdf3..f79586b68b9 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -70,7 +70,7 @@ .md { font-size: $gl-font-size; - .label { + .badge.badge-pill { color: $gl-text-color; } @@ -173,7 +173,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .event-item { padding-left: 0; diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 409b7285f82..c2b42e02eee 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -2,7 +2,7 @@ @include str-truncated(90%); } -.dashboard .side .panel .panel-heading .input-group { +.dashboard .side .card .card-header .input-group { .form-control { height: 42px; @@ -40,7 +40,7 @@ flex: 1; } - .dropdown-menu-align-right { + .dropdown-menu-right { margin-top: 0; } @@ -102,7 +102,7 @@ } } - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { &, .dropdown, .dropdown .dropdown-toggle, @@ -149,14 +149,14 @@ padding: 50px 100px; overflow: hidden; - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { padding: 50px 0; } svg { float: right; - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { float: none; display: block; width: 250px; @@ -171,7 +171,7 @@ width: 460px; margin-top: 120px; - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { float: none; margin-top: 60px; width: auto; @@ -205,7 +205,7 @@ max-width: 480px; padding: 0 $gl-padding; - @media (max-width: $screen-sm-min) { + @include media-breakpoint-down(sm) { margin: 0 auto; } } diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index 9cc9e11bcd1..0350fe5752e 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -28,8 +28,8 @@ } .key { - @extend .label; - @extend .label-inverse; + @extend .badge.badge-pill; + background-color: $label-inverse-bg; font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; padding: 3px 5px; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index a8110f069d4..4aea9740735 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -373,7 +373,7 @@ /* Extra small devices (phones, less than 768px) */ display: none; /* Small devices (tablets, 768px and up) */ - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: block; } @@ -656,7 +656,7 @@ } .issuable-form-padding-top { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { padding-top: 7px; } } @@ -670,7 +670,7 @@ padding-left: 9px; padding-right: 9px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: inline-block; height: auto; align-self: center; @@ -678,7 +678,7 @@ } .issuable-gutter-toggle { - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { margin-left: $btn-side-margin; } } @@ -696,7 +696,7 @@ width: 100%; } - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { margin-bottom: $gl-padding; } } @@ -737,7 +737,7 @@ } } - @media(max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .issuable-meta { .controls li { margin-right: 0; @@ -772,7 +772,7 @@ } } - @media(max-width: $screen-md-max) { + @media(max-width: map-get($grid-breakpoints, lg)-1) { .task-status, .issuable-due-date, .project-ref-path { diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 0d17b9bae7e..19fb99bfa93 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -141,7 +141,7 @@ ul.related-merge-requests > li { } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .detail-page-header { .issuable-meta { line-height: 18px; @@ -203,7 +203,7 @@ ul.related-merge-requests > li { } } - .btn-group:not(.hide) { + .btn-group:not(.hidden) { display: flex; } @@ -245,7 +245,7 @@ ul.related-merge-requests > li { display: block; } -@media (min-width: $screen-sm-min) { +@include media-breakpoint-up(sm) { .emoji-block .row { display: flex; @@ -256,8 +256,8 @@ ul.related-merge-requests > li { } .create-mr-dropdown-wrap { - .btn-group:not(.hide) { - display: inline-block; + .btn-group:not(.hidden) { + display: inline-flex; } } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index d81236c5883..e178371d21f 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -62,13 +62,13 @@ display: inline-block; margin-bottom: 10px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 200px; margin-left: $gl-padding * 2; margin-bottom: 0; } - .label { + .badge { overflow: hidden; text-overflow: ellipsis; max-width: 100%; @@ -80,7 +80,7 @@ margin-bottom: 10px; margin-left: 50px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: inline-block; width: 100px; margin-left: 10px; @@ -101,7 +101,7 @@ color: $blue-600; } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: inline-block; max-width: 50%; margin-left: 10px; @@ -110,7 +110,7 @@ } } - .label { + .badge { padding: 4px $grid-size; font-size: $label-font-size; position: relative; @@ -122,6 +122,7 @@ padding: 0 $grid-size; line-height: 16px; border-radius: $label-border-radius; + color: $white-light; } .dropdown-labels-error { @@ -132,7 +133,7 @@ } .manage-labels-list { - @media(min-width: $screen-md-min) { + @media(min-width: map-get($grid-breakpoints, md)) { &.content-list li { padding: $gl-padding 0; } @@ -171,12 +172,12 @@ } .dropdown { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { float: right; } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .dropdown-menu { min-width: 100%; } @@ -283,7 +284,7 @@ } .label-subscribe-button { - @media(min-width: $screen-md-min) { + @media(min-width: map-get($grid-breakpoints, md)) { min-width: 105px; margin-left: $gl-padding; } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 97303d02666..c1b1d2e028d 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -191,9 +191,9 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .login-page { - .col-sm-5.pull-right { + .col-md-5.float-right { float: none !important; margin-bottom: 45px; } @@ -243,7 +243,7 @@ .navless-container { padding: 65px 15px; // height of footer + bottom padding of email confirmation link - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding: 0 15px 65px; } } diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 3422829de58..de2b5701e2d 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -9,9 +9,13 @@ } } +.member-sort-dropdown { + margin-left: $gl-padding-8; +} + .member { .list-item-name { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { float: left; width: 50%; } @@ -22,12 +26,12 @@ } .controls { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: -webkit-flex; display: flex; } - .dropdown-menu.dropdown-menu-align-right { + .dropdown-menu.dropdown-menu-right { margin-top: -2px; } } @@ -35,7 +39,7 @@ .form-horizontal { margin-top: 20px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: -webkit-flex; display: flex; margin-top: 3px; @@ -45,20 +49,20 @@ .btn-remove { width: 100%; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: auto; } } &.existing-title { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { float: left; } } } .member-form-control { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding-bottom: 5px; margin-left: 0; margin-right: 0; @@ -73,7 +77,7 @@ .member-search-form { position: relative; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { float: right; } @@ -86,7 +90,7 @@ width: 100%; } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-top: 0; width: 155px; } @@ -96,19 +100,9 @@ width: 100%; padding-right: 35px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 250px; } - - &.input-short { - @media (min-width: $screen-md-min) { - width: 170px; - } - - @media (min-width: $screen-lg-min) { - width: 210px; - } - } } } @@ -124,7 +118,7 @@ border: 0; outline: 0; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { right: 160px; } } @@ -135,7 +129,7 @@ align-items: center; justify-content: center; - @media (max-width: $screen-sm-min) { + @include media-breakpoint-down(sm) { display: block; .flex-project-title { @@ -151,7 +145,7 @@ text-overflow: ellipsis; } - .badge { + .badge.badge-pill { height: 17px; line-height: 16px; margin-right: 5px; @@ -166,14 +160,14 @@ } } -.panel { - .panel-heading { - .badge { +.card { + .card-header { + .badge.badge-pill { margin-top: 0; } - @media (max-width: $screen-sm-min) { - .badge { + @include media-breakpoint-down(sm) { + .badge.badge-pill { margin-right: 0; margin-left: 0; } @@ -217,7 +211,7 @@ margin-right: 0; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: block; .controls > .btn { @@ -245,7 +239,7 @@ } } -.panel-mobile { +.card-mobile { .content-list.members-list li { display: block; diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 04bde64c752..e76525fdbf6 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -286,6 +286,14 @@ $colors: ( } .resolve-conflicts-form { - padding-top: $gl-padding; + h4 { + margin-top: 0; + } + + .resolve-info { + @media(max-width: map-get($grid-breakpoints, lg)-1) { + margin-bottom: $gl-padding; + } + } } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 3581dd36a10..9eceb3e9a33 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -131,7 +131,7 @@ color: $gl-text-color; display: flex; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex-wrap: wrap; } } @@ -282,7 +282,7 @@ display: inline-block; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { p { font-size: 13px; } @@ -494,18 +494,18 @@ } } -.panel-new-merge-request { - .panel-heading { +.card-new-merge-request { + .card-header { padding: 5px 10px; font-weight: $gl-font-weight-bold; line-height: 25px; } - .panel-body { + .card-body { padding: 10px 5px; } - .panel-footer { + .card-footer { padding: 0; .btn { @@ -519,7 +519,7 @@ } .item-title { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 45%; } } @@ -550,7 +550,7 @@ margin-bottom: 0; } - @media (min-width: $screen-xs-min) { + @include media-breakpoint-up(xs) { float: left; width: 50%; margin-bottom: 0; @@ -648,7 +648,7 @@ background-color: $white-light; border-bottom: 1px solid $border-color; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { position: sticky; position: -webkit-sticky; } @@ -657,7 +657,7 @@ left: 0; transition: right .15s; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { right: 0; } @@ -700,7 +700,7 @@ display: flex; justify-content: space-between; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { flex-direction: column-reverse; } } @@ -736,7 +736,7 @@ display: flex; flex-wrap: wrap; - @media (min-width: $screen-xs) { + @include media-breakpoint-up(xs) { flex-wrap: nowrap; white-space: nowrap; } @@ -753,7 +753,7 @@ min-width: 100px; max-width: 150px; - @media (min-width: $screen-xs) { + @include media-breakpoint-up(xs) { min-width: 0; max-width: 100%; } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index bac3b70c734..dba83e56d72 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -31,7 +31,7 @@ } } - .panel-heading { + .card-header { line-height: $line-height-base; padding: 14px 16px; display: -webkit-flex; @@ -145,7 +145,7 @@ padding: 20px 0; } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .milestone-actions { @include clearfix(); padding-top: $gl-vert-padding; @@ -181,7 +181,7 @@ width: 100%; } - @media (min-width: $screen-xs-min) { + @include media-breakpoint-up(xs) { .milestone-buttons .verbose { display: inline; } @@ -229,7 +229,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .milestone-banner-text, .milestone-banner-link { display: inline; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 4a528bc2bb1..3b037d066dc 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -51,7 +51,7 @@ } .note-image-attach { - @extend .col-md-4; + @extend .col-lg-4; margin-left: 45px; float: none; } @@ -93,7 +93,7 @@ -webkit-flex-flow: row wrap; width: 100%; - .pull-right { + .float-right { // Flexbox quirk to make sure right-aligned items stay right-aligned. margin-left: auto; } @@ -185,12 +185,12 @@ } .notes.notes-form > li.timeline-entry { - @include notes-media('max', $screen-sm-max) { + @include notes-media('max', map-get($grid-breakpoints, md) - 1) { padding: 0; } .timeline-content { - @include notes-media('max', $screen-sm-max) { + @include notes-media('max', map-get($grid-breakpoints, md) - 1) { margin: 0; } } @@ -326,7 +326,7 @@ outline: 0; } - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { float: left; margin-right: $gl-padding; @@ -350,13 +350,13 @@ line-height: 16px; margin-top: 2px; - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { float: left; } } .note-form-actions { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .btn { float: none; width: 100%; @@ -375,7 +375,7 @@ left: 127px; top: 2px; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { position: relative; top: 0; left: 0; @@ -410,7 +410,7 @@ width: 298px; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: flex; width: 100%; margin-bottom: 10px; @@ -432,7 +432,7 @@ .uploading-container { float: right; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { float: left; margin-top: 5px; } @@ -444,7 +444,7 @@ } .uploading-error-message { - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { &::after { content: "\a"; white-space: pre; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index feee964f9bb..299eda53140 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -22,7 +22,7 @@ ul.notes { .discussion-body { padding-top: 8px; - .panel { + .card { margin-bottom: 0; } } @@ -42,7 +42,7 @@ ul.notes { position: relative; border-bottom: 0; - @include notes-media('min', $screen-sm-min) { + @include notes-media('min', map-get($grid-breakpoints, sm)) { padding-left: $note-icon-gutter-width; } @@ -66,7 +66,7 @@ ul.notes { } .timeline-icon { - @include notes-media('min', $screen-sm-min) { + @include notes-media('min', map-get($grid-breakpoints, sm)) { margin-left: -$note-icon-gutter-width; } } @@ -74,7 +74,7 @@ ul.notes { .timeline-content { margin-left: $note-icon-gutter-width; - @include notes-media('min', $screen-sm-min) { + @include notes-media('min', map-get($grid-breakpoints, sm)) { margin-left: 0; } } @@ -154,7 +154,7 @@ ul.notes { .note-header { - @include notes-media('max', $screen-xs-min) { + @include notes-media('max', map-get($grid-breakpoints, xs)) { .inline { display: block; } @@ -217,7 +217,7 @@ ul.notes { .timeline-icon { float: left; - @include notes-media('min', $screen-sm-min) { + @include notes-media('min', map-get($grid-breakpoints, sm)) { margin-left: 0; width: auto; } @@ -231,7 +231,7 @@ ul.notes { } .timeline-content { - @include notes-media('min', $screen-sm-min) { + @include notes-media('min', map-get($grid-breakpoints, sm)) { margin-left: 30px; } } @@ -423,7 +423,7 @@ ul.notes { } .note-header-author-name { - @include notes-media('max', $screen-xs-max) { + @include notes-media('max', map-get($grid-breakpoints, sm) - 1) { display: none; } } @@ -431,7 +431,7 @@ ul.notes { .note-headline-light { display: inline; - @include notes-media('max', $screen-xs-min) { + @include notes-media('max', map-get($grid-breakpoints, xs)) { display: block; } } @@ -486,7 +486,7 @@ ul.notes { margin-left: 10px; color: $gray-darkest; - @include notes-media('max', $screen-xs-max) { + @include notes-media('max', map-get($grid-breakpoints, sm) - 1) { float: none; margin-left: 0; } @@ -634,6 +634,10 @@ ul.notes { margin-left: -55px; position: absolute; z-index: 10; + + .new & { + margin-top: -10px; + } } .discussion-body, @@ -668,7 +672,7 @@ ul.notes { } .line-resolve-all-container { - @include notes-media('min', $screen-sm-min) { + @include notes-media('min', map-get($grid-breakpoints, sm)) { margin-right: 0; padding-left: $gl-padding; } diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss index bdf07a99daf..e98cb444f0a 100644 --- a/app/assets/stylesheets/pages/notifications.scss +++ b/app/assets/stylesheets/pages/notifications.scss @@ -2,7 +2,7 @@ line-height: 34px; .dropdown-menu { - @extend .dropdown-menu-align-right; + @extend .dropdown-menu-right; } } diff --git a/app/assets/stylesheets/pages/pipeline_schedules.scss b/app/assets/stylesheets/pages/pipeline_schedules.scss index bc7fa8a26d9..86e70955389 100644 --- a/app/assets/stylesheets/pages/pipeline_schedules.scss +++ b/app/assets/stylesheets/pages/pipeline_schedules.scss @@ -69,7 +69,7 @@ .cron-preset-radio-input { display: inline-block; - @media (max-width: $screen-md-max) { + @include media-breakpoint-down(md) { display: block; margin: 0 0 5px 5px; } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 1264d977b2f..f85f66b9c0b 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -1,3 +1,30 @@ +@mixin flat-connector-before($length: 44px) { + &::before { + content: ''; + position: absolute; + top: 48%; + left: -$length; + border-top: 2px solid $border-color; + width: $length; + height: 1px; + } +} + +@mixin build-content($border-radius: 30px) { + display: inline-block; + padding: 8px 10px 9px; + width: 100%; + border: 1px solid $border-color; + border-radius: $border-radius; + background-color: $white-light; + + &:hover { + background-color: $stage-hover-bg; + border: 1px solid $dropdown-toggle-active-border-color; + color: $gl-text-color; + } +} + .pipelines { .stage { max-width: 90px; @@ -16,13 +43,13 @@ margin: 0; white-space: normal; - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { justify-content: flex-end; } } .ci-table { - .label { + .badge { margin-bottom: 3px; } @@ -82,7 +109,7 @@ } } -@media (max-width: $screen-md-max) { +@include media-breakpoint-down(md) { .content-list { &.builds-content-list { width: 100%; @@ -150,14 +177,14 @@ color: $gl-link-color; } - .label { + .badge { margin-right: 4px; } .label-container { font-size: 0; - .label { + .badge { margin-top: 5px; } } @@ -226,7 +253,7 @@ .stage-cell { &.table-section { - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { min-width: 160px; /* Hack alert: Without this the mini graph pipeline won't work properly*/ margin-right: -4px; } @@ -357,14 +384,8 @@ &:not(:first-child) { margin-left: 44px; - .left-connector::before { - content: ''; - position: absolute; - top: 48%; - left: -44px; - border-top: 2px solid $border-color; - width: 44px; - height: 1px; + .left-connector { + @include flat-connector-before; } } } @@ -479,12 +500,7 @@ } .build-content { - display: inline-block; - padding: 8px 10px 9px; - width: 100%; - border: 1px solid $border-color; - border-radius: 30px; - background-color: $white-light; + @include build-content(); } a.build-content:hover, @@ -622,8 +638,7 @@ } } -// Dropdown button in mini pipeline graph -button.mini-pipeline-graph-dropdown-toggle { +@mixin mini-pipeline-item() { border-radius: 100px; background-color: $white-light; border-width: 1px; @@ -636,30 +651,6 @@ button.mini-pipeline-graph-dropdown-toggle { position: relative; vertical-align: middle; - > .fa.fa-caret-down { - position: absolute; - left: 20px; - top: 5px; - display: inline-block; - visibility: hidden; - opacity: 0; - color: inherit; - font-size: 12px; - transition: visibility 0.1s, opacity 0.1s linear; - } - - &:active, - &:focus, - &:hover { - outline: none; - width: 35px; - - .fa.fa-caret-down { - visibility: visible; - opacity: 1; - } - } - // Dropdown button animation in mini pipeline graph &.ci-status-icon-success { @include mini-pipeline-graph-color($green-100, $green-500, $green-600); @@ -691,6 +682,35 @@ button.mini-pipeline-graph-dropdown-toggle { } } +// Dropdown button in mini pipeline graph +button.mini-pipeline-graph-dropdown-toggle { + @include mini-pipeline-item(); + + > .fa.fa-caret-down { + position: absolute; + left: 20px; + top: 5px; + display: inline-block; + visibility: hidden; + opacity: 0; + color: inherit; + font-size: 12px; + transition: visibility 0.1s, opacity 0.1s linear; + } + + &:active, + &:focus, + &:hover { + outline: none; + width: 35px; + + .fa.fa-caret-down { + visibility: visible; + opacity: 1; + } + } +} + /** Action icons inside dropdowns: - mini graph in pipelines table @@ -744,7 +764,7 @@ button.mini-pipeline-graph-dropdown-toggle { } } - // SVGs in the commit widget and mr widget + // SVGs in the commit widget and mr widget a.ci-action-icon-container.ci-action-icon-wrapper svg { top: 2px; } @@ -800,7 +820,7 @@ button.mini-pipeline-graph-dropdown-toggle { display: block; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { max-width: 60%; } } @@ -887,7 +907,7 @@ button.mini-pipeline-graph-dropdown-toggle { transform: translate(-50%, 0); border-width: 0 5px 6px; - @media (max-width: $screen-sm-max) { + @include media-breakpoint-down(sm) { left: 100%; margin-left: -12px; } @@ -909,7 +929,7 @@ button.mini-pipeline-graph-dropdown-toggle { &.dropdown-menu { transform: translate(-80%, 0); - @media (min-width: $screen-md-min) { + @media(min-width: map-get($grid-breakpoints, md)) { transform: translate(-50%, 0); right: auto; left: 50%; diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index b199f9876d3..06078f1d12e 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -5,7 +5,7 @@ } .avatar-image { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { float: left; margin-bottom: 0; } @@ -119,7 +119,7 @@ .key-list-item { .key-list-item-info { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { float: left; } } @@ -188,7 +188,7 @@ .modal-dialog { width: 380px; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { width: auto; } @@ -242,7 +242,7 @@ left: 0; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .cover-block { padding-top: 20px; } @@ -352,7 +352,7 @@ table.u2f-registrations { } } - @media(max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { text-align: center; .bordered-box { @@ -414,7 +414,7 @@ table.u2f-registrations { } &.unverified { - @include status-color($gray-dark, $gray, $common-gray-dark); + @include status-color($gray-dark, color("gray"), $common-gray-dark); } } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index dd0cb2c2613..22964163e95 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -9,7 +9,7 @@ .new_project, .edit-project, .import-project { - .help-block { + .form-text.text-muted { margin-bottom: 10px; } @@ -34,7 +34,7 @@ } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .input-group > div { &:last-child { margin-bottom: 0; @@ -46,7 +46,8 @@ } } - .input-group-addon { + .input-group-prepend, + .input-group-append { overflow: hidden; text-overflow: ellipsis; line-height: unset; @@ -82,7 +83,7 @@ border: 1px solid $border-color; padding: 10px 32px; - @media (max-width: $screen-xs-min) { + @include media-breakpoint-down(xs) { padding: 10px 20px; } } @@ -134,7 +135,7 @@ max-width: 400px; } - @media (max-width: $screen-xs-min) { + @include media-breakpoint-down(xs) { padding-left: 20px; } } @@ -144,7 +145,7 @@ padding-top: 24px; padding-bottom: 24px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { border-bottom: 1px solid $border-color; } @@ -230,11 +231,11 @@ } .notification-dropdown .dropdown-menu { - @extend .dropdown-menu-align-right; + @extend .dropdown-menu-right; } .download-button { - @media (max-width: $screen-md-max) { + @include media-breakpoint-down(md) { margin-left: 0; } } @@ -444,11 +445,11 @@ height: 200px; width: calc((100% / 2) - #{$gl-padding * 2}); - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { width: calc((100% / 4) - #{$gl-padding * 2}); } - @media (min-width: $screen-lg-min) { + @include media-breakpoint-up(lg) { width: calc((100% / 5) - #{$gl-padding * 2}); } @@ -537,11 +538,12 @@ .template-input-group { position: relative; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: flex; } - .input-group-addon { + .input-group-prepend, + .input-group-append { flex: 1; text-align: left; padding-left: ($gl-padding * 3); @@ -601,12 +603,12 @@ margin: 0 auto 4px; font-size: 24px; - @media (min-width: $screen-xs-max) { + @media (min-width: map-get($grid-breakpoints, sm)-1) { top: 0; } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .btn-template-icon { display: inline-block; height: 14px; @@ -625,31 +627,31 @@ .create-project-options { display: flex; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { display: block; } .first-column { - @media (min-width: $screen-xs-min) { + @include media-breakpoint-up(xs) { max-width: 50%; padding-right: 30px; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { max-width: 100%; width: 100%; } } .second-column { - @media (min-width: $screen-xs-min) { + @include media-breakpoint-up(xs) { width: 50%; flex: 1; padding-left: 30px; position: relative; } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { max-width: 100%; width: 100%; padding-left: 0; @@ -657,7 +659,7 @@ } // Mobile - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { padding-top: 30px; } @@ -677,7 +679,7 @@ line-height: 20px; // Mobile - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { left: 50%; top: 0; transform: translateX(-50%); @@ -697,7 +699,7 @@ top: 0; // Mobile - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { top: 10px; left: 10px; right: 10px; @@ -735,7 +737,7 @@ vertical-align: top; margin-top: 0; - @media (min-width: $screen-lg-min) { + @include media-breakpoint-up(lg) { float: right; } } @@ -866,19 +868,14 @@ pre.light-well { } } -.panel .projects-list li { +.card .projects-list li { padding: 10px 15px; margin: 0; } -.commits-search-form { - .input-short { - min-width: 200px; - } -} - .git-clone-holder { width: 380px; + height: 28px; .btn-clipboard { border: 1px solid $border-color; @@ -894,11 +891,11 @@ pre.light-well { .form-control { @extend .monospace; - background: $white-light; + background-color: $white-light; + border-color: $border-color; font-size: 14px; margin-left: -1px; cursor: auto; - width: 101%; } } @@ -927,7 +924,8 @@ pre.light-well { } .project-tip-command { - > .input-group-btn:first-child { + > .input-group-prepend:first-child, + > .input-group-append:first-child { width: auto; } } @@ -978,7 +976,7 @@ pre.light-well { .dropdown-menu-projects { width: 300px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 500px; } @@ -992,7 +990,7 @@ pre.light-well { .inline-input-group { width: 100%; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 300px; } } @@ -1003,8 +1001,8 @@ pre.light-well { text-align: center; margin-top: -20px; - @media (min-width: $screen-sm-min) { - margin-top: 0; + @include media-breakpoint-up(sm) { + margin: 0 $gl-padding-8; width: auto; } } @@ -1042,7 +1040,7 @@ pre.light-well { } &.form-group { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-bottom: 0; } } @@ -1070,12 +1068,12 @@ pre.light-well { .project-feature { padding-top: 10px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { padding-left: 45px; } &.nested { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { padding-left: 90px; } } diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 175d2779bb7..17d7087bd85 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -22,7 +22,6 @@ height: calc(100vh - #{$header-height}); margin-top: 0; border-top: 1px solid $white-dark; - border-bottom: 1px solid $white-dark; padding-bottom: $ide-statusbar-height; &.is-collapsed { @@ -380,7 +379,7 @@ .ide-status-bar { border-top: 1px solid $white-dark; - padding: $gl-bar-padding $gl-padding; + padding: 2px $gl-padding-8 0; background: $white-light; display: flex; justify-content: space-between; @@ -391,12 +390,19 @@ left: 0; width: 100%; + font-size: 12px; + line-height: 22px; + + * { + font-size: inherit; + } + > div + div { padding-left: $gl-padding; } svg { - vertical-align: middle; + vertical-align: sub; } } @@ -848,7 +854,7 @@ } } - .help-block { + .form-text.text-muted { margin-top: 0; line-height: 0; } diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index 5fb97b13470..2734faec558 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -51,7 +51,7 @@ } } -@media (max-width: $screen-md-max) { +@include media-breakpoint-down(md) { .runners-content { width: 100%; overflow: auto; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index dbde0720993..a35c4ff7c80 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -47,6 +47,7 @@ input[type="checkbox"]:hover { } .location-badge { + white-space: nowrap; height: 32px; font-size: 12px; margin: -4px 4px -4px -4px; @@ -166,7 +167,7 @@ input[type="checkbox"]:hover { } .search-holder { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: -webkit-flex; display: flex; } @@ -178,7 +179,7 @@ input[type="checkbox"]:hover { position: relative; margin-right: 0; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-right: 5px; } } @@ -202,7 +203,7 @@ input[type="checkbox"]:hover { width: 100%; margin-top: 5px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: auto; margin-top: 0; margin-left: 5px; @@ -210,7 +211,7 @@ input[type="checkbox"]:hover { } .dropdown { - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { margin-left: 5px; margin-right: 5px; } @@ -220,7 +221,7 @@ input[type="checkbox"]:hover { width: 100%; margin-top: 5px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 180px; margin-top: 0; } diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index c410049bc0b..2b3773eebad 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -70,7 +70,7 @@ animation: none; } - @media(max-width: $screen-sm-max) { + @media(max-width: map-get($grid-breakpoints, md)-1) { padding-right: 20px; } @@ -98,8 +98,8 @@ } .bs-callout, - .checkbox:first-child, - .help-block { + .form-check:first-child, + .form-text.text-muted { margin-top: 0; } @@ -131,12 +131,12 @@ color: $gl-danger; } -.service-settings .control-label { +.service-settings .form-control-label { padding-top: 0; } .integration-settings-form { - .well { + .card.card-body { padding: $gl-padding / 2; box-shadow: none; } @@ -158,7 +158,7 @@ } .visibility-level-setting { - .radio { + .form-check { margin-bottom: 10px; i.fa { @@ -174,7 +174,7 @@ .option-description, .option-disabled-reason { - margin-left: 29px; + margin-left: 45px; color: $project-option-descr-color; } @@ -199,22 +199,22 @@ } .prometheus-metrics-monitoring { - .panel { - .panel-toggle { + .card { + .card-toggle { width: 14px; } - .badge { + .badge.badge-pill { font-size: 12px; line-height: 12px; } - .panel-heading .badge-count { + .card-header .label-count { color: $white-light; background: $common-gray-dark; } - .panel-body { + .card-body { padding: 0; } @@ -249,7 +249,7 @@ li { padding: $gl-padding; - .badge { + .badge.badge-pill { margin-left: 5px; background: $badge-bg; } diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index 8e2c42c1bd3..3f6f5f06075 100644 --- a/app/assets/stylesheets/pages/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss @@ -14,7 +14,10 @@ } #contributors-master { - @include make-md-column(12); + @include media-breakpoint-up(md) { + @include make-col-ready(); + @include make-col(12); + } svg { width: 100%; @@ -33,10 +36,14 @@ } .person { - @include make-md-column(6); + @include media-breakpoint-up(md) { + @include make-col-ready(); + @include make-col(6); + } + margin-top: 10px; - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { width: 100%; } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index ade5ddd147b..620297e589d 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -58,7 +58,7 @@ } } -.visible-xs-inline { +.d-block.d-sm-none-inline { .ci-status-link { position: relative; top: 2px; diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 4b9824fab0c..e5d7dd13915 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -126,7 +126,7 @@ color: $gl-grayish-blue; font-size: $gl-font-size; - .label { + .badge.badge-pill { color: $gl-text-color; } @@ -162,7 +162,7 @@ } } -@media (max-width: $screen-sm-max) { +@include media-breakpoint-down(sm) { .todos-filters { .dropdown-menu-toggle { width: 130px; @@ -174,7 +174,7 @@ } } -@media (max-width: $screen-xs-max) { +@include media-breakpoint-down(xs) { .todo { .avatar { display: none; @@ -214,7 +214,7 @@ margin-left: auto; margin-right: auto; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { -webkit-flex-direction: row; flex-direction: row; padding-top: 80px; @@ -233,7 +233,7 @@ margin-left: auto; margin-right: auto; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { width: 300px; margin-right: 0; -webkit-order: 2; @@ -244,7 +244,7 @@ .todos-all-done { padding-top: 20px; - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { padding-top: 50px; } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index e0ee7e9aa3d..efd26cb1f81 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -7,7 +7,7 @@ color: $gl-text-color-secondary; } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { display: flex; .tree-ref-container { @@ -41,15 +41,10 @@ position: relative; } } - - .add-to-tree-dropdown { - position: absolute; - left: 18px; - } } } - @media (max-width: $screen-xs-max) { + @include media-breakpoint-down(xs) { .repo-breadcrumb { margin-top: 10px; position: relative; @@ -121,7 +116,7 @@ margin-left: 5px; } - @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { + @include media-breakpoint-only(md) { @include str-truncated(450px); } diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index e70a57c2a67..800f5c68e39 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -65,7 +65,7 @@ display: block; } - @media (min-width: $screen-sm-min) { + @include media-breakpoint-up(sm) { &.has-sidebar-toggle { padding-right: 40px; } @@ -81,7 +81,7 @@ } } - @media (min-width: $screen-md-min) { + @include media-breakpoint-up(md) { &.has-sidebar-toggle { padding-right: 0; } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 8958eab0423..cdfe3d6ab1e 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -52,7 +52,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController private def set_application_setting - @application_setting = ApplicationSetting.current + @application_setting = ApplicationSetting.current_without_cache end def application_setting_params diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb index 7c2016f0326..e892d1f8dbf 100644 --- a/app/controllers/groups/boards_controller.rb +++ b/app/controllers/groups/boards_controller.rb @@ -2,19 +2,24 @@ class Groups::BoardsController < Groups::ApplicationController include BoardsResponses before_action :assign_endpoint_vars + before_action :boards, only: :index def index - @boards = Boards::ListService.new(group, current_user).execute - respond_with_boards end def show - @board = group.boards.find(params[:id]) + @board = boards.find(params[:id]) respond_with_board end + private + + def boards + @boards ||= Boards::ListService.new(group, current_user).execute + end + def assign_endpoint_vars @boards_endpoint = group_boards_url(group) @namespace_path = group.to_param diff --git a/app/controllers/groups/settings/badges_controller.rb b/app/controllers/groups/settings/badges_controller.rb index edb334a3d88..ccbd0a3bc02 100644 --- a/app/controllers/groups/settings/badges_controller.rb +++ b/app/controllers/groups/settings/badges_controller.rb @@ -1,12 +1,12 @@ module Groups module Settings class BadgesController < Groups::ApplicationController - include GrapeRouteHelpers::NamedRouteMatcher + include API::Helpers::RelatedResourcesHelpers before_action :authorize_admin_group! def index - @badge_api_endpoint = api_v4_groups_badges_path(id: @group.id) + @badge_api_endpoint = expose_url(api_v4_groups_badges_path(id: @group.id)) end end end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index ed89bed029b..27fd5f7ba37 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -26,11 +26,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController # Extend the standard message generation to accept our custom exception def failure_message - exception = env["omniauth.error"] + exception = request.env["omniauth.error"] error = exception.error_reason if exception.respond_to?(:error_reason) error ||= exception.error if exception.respond_to?(:error) error ||= exception.message if exception.respond_to?(:message) - error ||= env["omniauth.error.type"].to_s + error ||= request.env["omniauth.error.type"].to_s error.to_s.humanize if error end diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 949e54ff819..e7354a9e1f7 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -4,22 +4,25 @@ class Projects::BoardsController < Projects::ApplicationController before_action :check_issues_available! before_action :authorize_read_board!, only: [:index, :show] + before_action :boards, only: :index before_action :assign_endpoint_vars def index - @boards = Boards::ListService.new(project, current_user).execute - respond_with_boards end def show - @board = project.boards.find(params[:id]) + @board = boards.find(params[:id]) respond_with_board end private + def boards + @boards ||= Boards::ListService.new(project, current_user).execute + end + def assign_endpoint_vars @boards_endpoint = project_boards_path(project) @bulk_issues_path = bulk_update_project_issues_path(project) diff --git a/app/controllers/projects/clusters/applications_controller.rb b/app/controllers/projects/clusters/applications_controller.rb index 90c7fa62216..35885543622 100644 --- a/app/controllers/projects/clusters/applications_controller.rb +++ b/app/controllers/projects/clusters/applications_controller.rb @@ -5,9 +5,10 @@ class Projects::Clusters::ApplicationsController < Projects::ApplicationControll before_action :authorize_create_cluster!, only: [:create] def create - Clusters::Applications::ScheduleInstallationService.new(project, current_user, - application_class: @application_class, - cluster: @cluster).execute + application = @application_class.find_or_create_by!(cluster: @cluster) + + Clusters::Applications::ScheduleInstallationService.new(project, current_user).execute(application) + head :no_content rescue StandardError head :bad_request diff --git a/app/controllers/projects/prometheus/metrics_controller.rb b/app/controllers/projects/prometheus/metrics_controller.rb index 1dd886409a5..c6b6243b553 100644 --- a/app/controllers/projects/prometheus/metrics_controller.rb +++ b/app/controllers/projects/prometheus/metrics_controller.rb @@ -25,7 +25,7 @@ module Projects end def require_prometheus_metrics! - render_404 unless prometheus_adapter.can_query? + render_404 unless prometheus_adapter&.can_query? end end end diff --git a/app/controllers/projects/settings/badges_controller.rb b/app/controllers/projects/settings/badges_controller.rb index f7b70dd4b7b..7887bee49c5 100644 --- a/app/controllers/projects/settings/badges_controller.rb +++ b/app/controllers/projects/settings/badges_controller.rb @@ -1,12 +1,12 @@ module Projects module Settings class BadgesController < Projects::ApplicationController - include GrapeRouteHelpers::NamedRouteMatcher + include API::Helpers::RelatedResourcesHelpers before_action :authorize_admin_project! def index - @badge_api_endpoint = api_v4_projects_badges_path(id: @project.id) + @badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id)) end end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index aa4569500b8..f5d94ad96a1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -2,6 +2,11 @@ require 'digest/md5' require 'uri' module ApplicationHelper + # See https://docs.gitlab.com/ee/development/ee_features.html#code-in-app-views + def render_if_exists(partial, locals = {}) + render(partial, locals) if lookup_context.exists?(partial, [], true) + end + # Check if a particular controller is the current one # # args - One or more controller names to check diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index d339c01d492..43d92bde064 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -78,7 +78,7 @@ module AvatarsHelper user: commit_or_event.author, user_name: commit_or_event.author_name, user_email: commit_or_event.author_email, - css_class: 'hidden-xs' + css_class: 'd-none d-sm-inline' })) end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index e7a36e20050..3db28fd6da3 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -17,7 +17,9 @@ module BlobHelper end def ide_edit_path(project = @project, ref = @ref, path = @path, options = {}) - "#{ide_path}/project#{url_for([project, "edit", "blob", id: [ref, path], script_name: "/"])}" + segments = [ide_path, 'project', project.full_path, 'edit', ref] + segments.concat(['-', path]) if path.present? + File.join(segments) end def edit_blob_button(project = @project, ref = @ref, path = @path, options = {}) @@ -331,7 +333,6 @@ module BlobHelper if !on_top_of_branch?(project, ref) edit_disabled_button_tag(text, common_classes) # This condition only applies to users who are logged in - # Web IDE (Beta) requires the user to have this feature enabled elsif !current_user || (current_user && can_modify_blob?(blob, project, ref)) edit_link_tag(text, edit_path, common_classes) elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project) diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index f0afcac5986..5fce97164ae 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -92,7 +92,7 @@ module CiStatusHelper "pipeline-status/#{pipeline_status.sha}-#{pipeline_status.status}" end - def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left') + def render_project_pipeline_status(pipeline_status, tooltip_placement: 'left') project = pipeline_status.project path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref) @@ -103,7 +103,7 @@ module CiStatusHelper tooltip_placement: tooltip_placement) end - def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left') + def render_commit_status(commit, ref: nil, tooltip_placement: 'left') project = commit.project path = pipelines_project_commit_path(project, commit, ref: ref) @@ -114,7 +114,7 @@ module CiStatusHelper tooltip_placement: tooltip_placement) end - def render_pipeline_status(pipeline, tooltip_placement: 'auto left') + def render_pipeline_status(pipeline, tooltip_placement: 'left') project = pipeline.project path = project_pipeline_path(project, pipeline) render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement) @@ -125,7 +125,7 @@ module CiStatusHelper Ci::Runner.shared.blank? end - def render_status_with_link(type, status, path = nil, tooltip_placement: 'auto left', cssclass: '', container: 'body') + def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body') klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" title = "#{type.titleize}: #{ci_label_for_status(status)}" data = { toggle: 'tooltip', placement: tooltip_placement, container: container } diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index e594a1d0ba3..e5c3be47801 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -27,7 +27,7 @@ module CommitsHelper return unless @project && @ref # Add the root project link and the arrow icon - crumbs = content_tag(:li) do + crumbs = content_tag(:li, class: 'breadcrumb-item') do link_to( @project.path, project_commits_path(@project, @ref) @@ -38,7 +38,7 @@ module CommitsHelper parts = @path.split('/') parts.each_with_index do |part, i| - crumbs << content_tag(:li) do + crumbs << content_tag(:li, class: 'breadcrumb-item') do # The text is just the individual part, but the link needs all the parts before it link_to( part, @@ -62,8 +62,8 @@ module CommitsHelper # Returns a link formatted as a commit branch link def commit_branch_link(url, text) - link_to(url, class: 'label label-gray ref-name branch-link') do - sprite_icon('fork', size: 12, css_class: 'fork-svg') + "#{text}" + link_to(url, class: 'badge badge-gray ref-name branch-link') do + sprite_icon('branch', size: 12, css_class: 'fork-svg') + "#{text}" end end @@ -76,8 +76,8 @@ module CommitsHelper # Returns a link formatted as a commit tag link def commit_tag_link(url, text) - link_to(url, class: 'label label-gray ref-name') do - icon('tag', class: 'append-right-5') + "#{text}" + link_to(url, class: 'badge badge-gray ref-name') do + sprite_icon('tag', size: 12, css_class: 'append-right-5 vertical-align-middle') + "#{text}" end end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 2f304b040c7..58372edff3c 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -60,7 +60,7 @@ module IconsHelper def spinner(text = nil, visible = false) css_class = 'loading' - css_class << ' hide' unless visible + css_class << ' hidden' unless visible content_tag :div, class: css_class do icon('spinner spin') + text diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index f39a62bccc8..8572c2b7276 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -157,15 +157,15 @@ module IssuablesHelper output = "" output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe output << content_tag(:strong) do - author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true) - author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg") + author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline-block", tooltip: true) + author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none") end output << " ".html_safe output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!')) - output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm") - output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg") + output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block") + output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none d-lg-none d-xl-inline-block") output.html_safe end @@ -199,7 +199,7 @@ module IssuablesHelper if display_count count = issuables_count_for_state(issuable_type, state) - html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge') + html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge badge-pill') end html.html_safe @@ -359,7 +359,8 @@ module IssuablesHelper url: project_todos_path(@project), delete_path: (dashboard_todo_path(todo) if todo), placement: (is_collapsed ? 'left' : nil), - container: (is_collapsed ? 'body' : nil) + container: (is_collapsed ? 'body' : nil), + boundary: 'viewport' } end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index c4a6a1e4bb3..e1b0e7a4a3e 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -81,7 +81,7 @@ module LabelsHelper # Intentionally not using content_tag here so that this method can be called # by LabelReferenceFilter - span = %(<span class="label color-label #{"has-tooltip" if tooltip}" ) + + span = %(<span class="badge color-label #{"has-tooltip" if tooltip}" ) + %(style="background-color: #{label.color}; color: #{text_color}" ) + %(title="#{escape_once(label.description)}" data-container="body">) + %(#{escape_once(label.name)}#{label_suffix}</span>) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index e8caab3e50c..15a15405f1d 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -89,7 +89,7 @@ module MilestonesHelper def milestone_progress_bar(milestone) options = { - class: 'progress-bar progress-bar-success', + class: 'progress-bar bg-success', style: "width: #{milestone.percent_complete(current_user)}%;" } @@ -112,8 +112,6 @@ module MilestonesHelper def milestone_tooltip_title(milestone) if milestone "#{milestone.title}<br />#{milestone_tooltip_due_date(milestone)}" - else - _('Milestone') end end @@ -173,6 +171,8 @@ module MilestonesHelper def milestone_tooltip_due_date(milestone) if milestone.due_date "#{milestone.due_date.to_s(:medium)} (#{remaining_days_in_words(milestone)})" + else + _('Milestone') end end diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index b3f2aeb08ca..5ba3a4a322c 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -56,6 +56,14 @@ module Emails mail_answer_thread(@merge_request, merge_request_thread_options(updated_by_user_id, recipient_id, reason)) end + def merge_request_unmergeable_email(recipient_id, merge_request_id, reason = nil) + setup_merge_request_mail(merge_request_id, recipient_id) + + @reasons = MergeRequestPresenter.new(@merge_request, current_user: current_user).unmergeable_reasons + + mail_answer_thread(@merge_request, merge_request_thread_options(@merge_request.author_id, recipient_id, reason)) + end + def resolved_all_discussions_email(recipient_id, merge_request_id, resolved_by_user_id, reason = nil) setup_merge_request_mail(merge_request_id, recipient_id) diff --git a/app/models/appearance.rb b/app/models/appearance.rb index f8713138a93..67cc84a9140 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -1,6 +1,6 @@ class Appearance < ActiveRecord::Base + include CacheableAttributes include CacheMarkdownField - include AfterCommitQueue include ObjectStorage::BackgroundMove include WithUploads @@ -15,16 +15,9 @@ class Appearance < ActiveRecord::Base mount_uploader :logo, AttachmentUploader mount_uploader :header_logo, AttachmentUploader - CACHE_KEY = "current_appearance:#{Gitlab::VERSION}".freeze - - after_commit :flush_redis_cache - - def self.current - Rails.cache.fetch(CACHE_KEY) { first } - end - - def flush_redis_cache - Rails.cache.delete(CACHE_KEY) + # Overrides CacheableAttributes.current_without_cache + def self.current_without_cache + first end def single_appearance_row diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 451e512aef7..e8ccb320fae 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1,11 +1,11 @@ class ApplicationSetting < ActiveRecord::Base + include CacheableAttributes include CacheMarkdownField include TokenAuthenticatable add_authentication_token_field :runners_registration_token add_authentication_token_field :health_check_access_token - CACHE_KEY = 'application_setting.last'.freeze DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace | # or \s # any whitespace character @@ -229,40 +229,6 @@ class ApplicationSetting < ActiveRecord::Base after_commit do reset_memoized_terms - Rails.cache.write(CACHE_KEY, self) - end - - def self.current - ensure_cache_setup - - Rails.cache.fetch(CACHE_KEY) do - ApplicationSetting.last.tap do |settings| - # do not cache nils - raise 'missing settings' unless settings - end - end - rescue - # Fall back to an uncached value if there are any problems (e.g. redis down) - ApplicationSetting.last - end - - def self.expire - Rails.cache.delete(CACHE_KEY) - rescue - # Gracefully handle when Redis is not available. For example, - # omnibus may fail here during gitlab:assets:compile. - end - - def self.cached - value = Rails.cache.read(CACHE_KEY) - ensure_cache_setup if value.present? - value - end - - def self.ensure_cache_setup - # This is a workaround for a Rails bug that causes attribute methods not - # to be loaded when read from cache: https://github.com/rails/rails/issues/27348 - ApplicationSetting.define_attribute_methods end def self.defaults diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 495430931aa..75fd55a8f7b 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -618,7 +618,7 @@ module Ci variables.append(key: 'GITLAB_FEATURES', value: project.licensed_features.join(',')) variables.append(key: 'CI_SERVER_NAME', value: 'GitLab') variables.append(key: 'CI_SERVER_VERSION', value: Gitlab::VERSION) - variables.append(key: 'CI_SERVER_REVISION', value: Gitlab::REVISION) + variables.append(key: 'CI_SERVER_REVISION', value: Gitlab.revision) variables.append(key: 'CI_JOB_NAME', value: name) variables.append(key: 'CI_JOB_STAGE', value: stage) variables.append(key: 'CI_COMMIT_SHA', value: sha) diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb new file mode 100644 index 00000000000..b32459fdabf --- /dev/null +++ b/app/models/concerns/cacheable_attributes.rb @@ -0,0 +1,54 @@ +module CacheableAttributes + extend ActiveSupport::Concern + + included do + after_commit { self.class.expire } + end + + class_methods do + # Can be overriden + def current_without_cache + last + end + + def cache_key + "#{name}:#{Gitlab::VERSION}:#{Gitlab.migrations_hash}:json".freeze + end + + def defaults + {} + end + + def build_from_defaults(attributes = {}) + new(defaults.merge(attributes)) + end + + def cached + json_attributes = Rails.cache.read(cache_key) + return nil unless json_attributes.present? + + build_from_defaults(JSON.parse(json_attributes)) + end + + def current + cached_record = cached + return cached_record if cached_record.present? + + current_without_cache.tap { |current_record| current_record&.cache! } + rescue + # Fall back to an uncached value if there are any problems (e.g. Redis down) + current_without_cache + end + + def expire + Rails.cache.delete(cache_key) + rescue + # Gracefully handle when Redis is not available. For example, + # omnibus may fail here during gitlab:assets:compile. + end + end + + def cache! + Rails.cache.write(self.class.cache_key, attributes.to_json) + end +end diff --git a/app/models/concerns/resolvable_note.rb b/app/models/concerns/resolvable_note.rb index 668c5a079e3..4a0f8b92b3a 100644 --- a/app/models/concerns/resolvable_note.rb +++ b/app/models/concerns/resolvable_note.rb @@ -32,7 +32,7 @@ module ResolvableNote # Keep this method in sync with the `potentially_resolvable` scope def potentially_resolvable? - RESOLVABLE_TYPES.include?(self.class.name) && noteable.supports_resolvable_notes? + RESOLVABLE_TYPES.include?(self.class.name) && noteable&.supports_resolvable_notes? end # Keep this method in sync with the `resolvable` scope diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index f42d432cc66..a00b8d4e2c1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -105,24 +105,35 @@ class MergeRequest < ActiveRecord::Base state_machine :merge_status, initial: :unchecked do event :mark_as_unchecked do - transition [:can_be_merged, :cannot_be_merged] => :unchecked + transition [:can_be_merged, :unchecked] => :unchecked + transition [:cannot_be_merged, :cannot_be_merged_recheck] => :cannot_be_merged_recheck end event :mark_as_mergeable do - transition [:unchecked, :cannot_be_merged] => :can_be_merged + transition [:unchecked, :cannot_be_merged_recheck] => :can_be_merged end event :mark_as_unmergeable do - transition [:unchecked, :can_be_merged] => :cannot_be_merged + transition [:unchecked, :cannot_be_merged_recheck] => :cannot_be_merged end state :unchecked + state :cannot_be_merged_recheck state :can_be_merged state :cannot_be_merged around_transition do |merge_request, transition, block| Gitlab::Timeless.timeless(merge_request, &block) end + + after_transition unchecked: :cannot_be_merged do |merge_request, transition| + NotificationService.new.merge_request_unmergeable(merge_request) + TodoService.new.merge_request_became_unmergeable(merge_request) + end + + def check_state?(merge_status) + [:unchecked, :cannot_be_merged_recheck].include?(merge_status.to_sym) + end end validates :source_project, presence: true, unless: [:allow_broken, :importing?, :closed_without_fork?] @@ -328,6 +339,16 @@ class MergeRequest < ActiveRecord::Base update_column(:merge_jid, jid) end + def merge_participants + participants = [author] + + if merge_when_pipeline_succeeds? && !participants.include?(merge_user) + participants << merge_user + end + + participants + end + def first_commit merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end @@ -603,7 +624,7 @@ class MergeRequest < ActiveRecord::Base end def check_if_can_be_merged - return unless unchecked? && Gitlab::Database.read_write? + return unless self.class.state_machines[:merge_status].check_state?(merge_status) && Gitlab::Database.read_write? can_be_merged = !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch) diff --git a/app/models/project.rb b/app/models/project.rb index 35c873830a7..0fe9f8880b4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -894,6 +894,13 @@ class Project < ActiveRecord::Base Gitlab::Routing.url_helpers.project_url(self) end + def readme_url + readme = repository.readme + if readme + Gitlab::Routing.url_helpers.project_blob_url(self, File.join(default_branch, readme.path)) + end + end + def new_issuable_address(author, address_type) return unless Gitlab::IncomingEmail.supports_issue_creation? && author @@ -1424,8 +1431,8 @@ class Project < ActiveRecord::Base self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) end - def open_issues_count - Projects::OpenIssuesCountService.new(self).count + def open_issues_count(current_user = nil) + Projects::OpenIssuesCountService.new(self, current_user).count end def open_merge_requests_count diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 71b10fc6bc1..a4bf427ac0b 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -115,6 +115,6 @@ class DroneCiService < CiService def merge_request_valid?(data) data[:object_attributes][:state] == 'opened' && - data[:object_attributes][:merge_status] == 'unchecked' + MergeRequest.state_machines[:merge_status].check_state?(data[:object_attributes][:merge_status]) end end diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index 26cbfd784ad..84248f9590b 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -3,6 +3,7 @@ require "gemnasium/gitlab_service" class GemnasiumService < Service prop_accessor :token, :api_key validates :token, :api_key, presence: true, if: :activated? + validate :deprecation_validation def title 'Gemnasium' @@ -27,6 +28,18 @@ class GemnasiumService < Service %w(push) end + def deprecated? + true + end + + def deprecation_message + "Gemnasium has been acquired by GitLab in January 2018. Since May 15, 2018, the service provided by Gemnasium is no longer available." + end + + def deprecation_validation + errors[:base] << deprecation_message + end + def execute(data) return unless supported_events.include?(data[:object_kind]) diff --git a/app/models/repository.rb b/app/models/repository.rb index 44c6bff6b66..0e1bf11d7c0 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -596,7 +596,7 @@ class Repository cache_method :gitlab_ci_yml def xcode_project? - file_on_head(:xcode_config).present? + file_on_head(:xcode_config, :tree).present? end cache_method :xcode_project? @@ -920,11 +920,21 @@ class Repository end end - def file_on_head(type) - if head = tree(:head) - head.blobs.find do |blob| - Gitlab::FileDetector.type_of(blob.path) == type + def file_on_head(type, object_type = :blob) + return unless head = tree(:head) + + objects = + case object_type + when :blob + head.blobs + when :tree + head.trees + else + raise ArgumentError, "Object type #{object_type} is not supported" end + + objects.find do |object| + Gitlab::FileDetector.type_of(object.path) == type end end diff --git a/app/models/service.rb b/app/models/service.rb index f7e3f7590ad..831c2ea1141 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -253,7 +253,6 @@ class Service < ActiveRecord::Base emails_on_push external_wiki flowdock - gemnasium hipchat irker jira diff --git a/app/models/user.rb b/app/models/user.rb index 8ef3c3ceff0..0a838d34054 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -109,7 +109,7 @@ class User < ActiveRecord::Base has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :users_star_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :starred_projects, through: :users_star_projects, source: :project - has_many :project_authorizations + has_many :project_authorizations, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent has_many :authorized_projects, through: :project_authorizations, source: :project has_many :user_interacted_projects @@ -165,8 +165,7 @@ class User < ActiveRecord::Base validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id } before_validation :sanitize_attrs - before_validation :set_notification_email, if: :email_changed? - before_save :set_notification_email, if: :email_changed? # in case validation is skipped + before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? before_save :set_public_email, if: :public_email_changed? # in case validation is skipped before_save :ensure_incoming_email_token @@ -179,8 +178,21 @@ class User < ActiveRecord::Base after_update :username_changed_hook, if: :username_changed? after_destroy :post_destroy_hook after_destroy :remove_key_cache - after_commit :update_emails_with_primary_email, on: :update, if: -> { previous_changes.key?('email') } - after_commit :update_invalid_gpg_signatures, on: :update, if: -> { previous_changes.key?('email') } + after_commit(on: :update) do + if previous_changes.key?('email') + # Grab previous_email here since previous_changes changes after + # #update_emails_with_primary_email and #update_notification_email are called + previous_email = previous_changes[:email][0] + + update_emails_with_primary_email(previous_email) + update_invalid_gpg_signatures + + if previous_email == notification_email + self.notification_email = email + save + end + end + end after_initialize :set_projects_limit @@ -546,8 +558,7 @@ class User < ActiveRecord::Base # hash and `_was` variables getting munged. # By using an `after_commit` instead of `after_update`, we avoid the recursive callback # scenario, though it then requires us to use the `previous_changes` hash - def update_emails_with_primary_email - previous_email = previous_changes[:email][0] # grab this before the DestroyService is called + def update_emails_with_primary_email(previous_email) primary_email_record = emails.find_by(email: email) Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record @@ -772,13 +783,13 @@ class User < ActiveRecord::Base end def set_notification_email - if notification_email.blank? || !all_emails.include?(notification_email) + if notification_email.blank? || all_emails.exclude?(notification_email) self.notification_email = email end end def set_public_email - if public_email.blank? || !all_emails.include?(public_email) + if public_email.blank? || all_emails.exclude?(public_email) self.public_email = '' end end diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index 4b4132af2d0..ad839d9840a 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -20,6 +20,17 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated end end + def unmergeable_reasons + strong_memoize(:unmergeable_reasons) do + reasons = [] + reasons << "no commits" if merge_request.has_no_commits? + reasons << "source branch is missing" unless merge_request.source_branch_exists? + reasons << "target branch is missing" unless merge_request.target_branch_exists? + reasons << "has merge conflicts" unless merge_request.project.repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch) + reasons + end + end + def cancel_merge_when_pipeline_succeeds_path if can_cancel_merge_when_pipeline_succeeds?(current_user) cancel_merge_when_pipeline_succeeds_project_merge_request_path(project, merge_request) diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb index 7c4a79f555e..3025029755c 100644 --- a/app/services/boards/issues/create_service.rb +++ b/app/services/boards/issues/create_service.rb @@ -10,11 +10,15 @@ module Boards end def execute - create_issue(params.merge(label_ids: [list.label_id])) + create_issue(params.merge(issue_params)) end private + def issue_params + { label_ids: [list.label_id] } + end + def board @board ||= parent.boards.find(params.delete(:board_id)) end diff --git a/app/services/clusters/applications/schedule_installation_service.rb b/app/services/clusters/applications/schedule_installation_service.rb index eb8caa68ef7..9c5461e85e1 100644 --- a/app/services/clusters/applications/schedule_installation_service.rb +++ b/app/services/clusters/applications/schedule_installation_service.rb @@ -1,21 +1,10 @@ module Clusters module Applications class ScheduleInstallationService < ::BaseService - def execute - application_class.find_or_create_by!(cluster: cluster).try do |application| - application.make_scheduled! - ClusterInstallAppWorker.perform_async(application.name, application.id) - end - end - - private - - def application_class - params[:application_class] - end + def execute(application) + application.make_scheduled! - def cluster - params[:cluster] + ClusterInstallAppWorker.perform_async(application.name, application.id) end end end diff --git a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb index 850deb0ac7a..9a4e6eb2e88 100644 --- a/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb @@ -24,11 +24,7 @@ module MergeRequests pipeline_merge_requests(pipeline) do |merge_request| next unless merge_request.merge_when_pipeline_succeeds? - - unless merge_request.mergeable? - todo_service.merge_request_became_unmergeable(merge_request) - next - end + next unless merge_request.mergeable? merge_request.merge_async(merge_request.merge_user_id, merge_request.merge_params) end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 1fb1796b56c..0127d781686 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -3,6 +3,12 @@ module MergeRequests def execute(oldrev, newrev, ref) return true unless Gitlab::Git.branch_ref?(ref) + do_execute(oldrev, newrev, ref) + end + + private + + def do_execute(oldrev, newrev, ref) @oldrev, @newrev = oldrev, newrev @branch_name = Gitlab::Git.ref_name(ref) @@ -28,8 +34,6 @@ module MergeRequests true end - private - def close_upon_missing_source_branch_ref # MergeRequest#reload_diff ignores not opened MRs. This means it won't # create an `empty` diff for `closed` MRs without a source branch, keeping diff --git a/app/services/notification_recipient_service.rb b/app/services/notification_recipient_service.rb index 5658699664d..4fa38665abc 100644 --- a/app/services/notification_recipient_service.rb +++ b/app/services/notification_recipient_service.rb @@ -18,6 +18,10 @@ module NotificationRecipientService Builder::NewNote.new(*a).notification_recipients end + def self.build_merge_request_unmergeable_recipients(*a) + Builder::MergeRequestUnmergeable.new(*a).notification_recipients + end + module Builder class Base def initialize(*) @@ -330,5 +334,26 @@ module NotificationRecipientService note.author end end + + class MergeRequestUnmergeable < Base + attr_reader :target + def initialize(merge_request) + @target = merge_request + end + + def build! + target.merge_participants.each do |user| + add_recipients(user, :participating, nil) + end + end + + def custom_action + :unmergeable_merge_request + end + + def acting_user + nil + end + end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 55a1735e54b..636cfbf5b45 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -129,6 +129,7 @@ class NotificationService # When create a merge request we should send an email to: # + # * mr author # * mr assignee if their notification level is not Disabled # * project team members with notification level higher then Participating # * watchers of the mr's labels @@ -148,6 +149,15 @@ class NotificationService end end + # When a merge request is found to be unmergeable, we should send an email to: + # + # * mr author + # * mr merge user if set + # + def merge_request_unmergeable(merge_request) + merge_request_unmergeable_email(merge_request) + end + # When merge request text is updated, we should send an email to: # # * newly mentioned project team members with notification level higher than Participating @@ -484,6 +494,14 @@ class NotificationService end end + def merge_request_unmergeable_email(merge_request) + recipients = NotificationRecipientService.build_merge_request_unmergeable_recipients(merge_request) + + recipients.each do |recipient| + mailer.merge_request_unmergeable_email(recipient.user.id, merge_request.id).deliver_later + end + end + def mailer Notify end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index adbc498d0bf..077d27c5836 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -51,11 +51,11 @@ module Projects flush_caches(@project) - unless mv_repository(removal_path(repo_path), repo_path) + unless rollback_repository(removal_path(repo_path), repo_path) raise_error('Failed to restore project repository. Please contact the administrator.') end - unless mv_repository(removal_path(wiki_path), wiki_path) + unless rollback_repository(removal_path(wiki_path), wiki_path) raise_error('Failed to restore wiki repository. Please contact the administrator.') end end @@ -84,6 +84,9 @@ module Projects # Skip repository removal. We use this flag when remove user or group return true if params[:skip_repo] == true + # There is a possibility project does not have repository or wiki + return true unless repo_exists?(path) + new_path = removal_path(path) if mv_repository(path, new_path) @@ -98,8 +101,18 @@ module Projects end end - def mv_repository(from_path, to_path) + def rollback_repository(old_path, new_path) # There is a possibility project does not have repository or wiki + return true unless repo_exists?(old_path) + + mv_repository(old_path, new_path) + end + + def repo_exists?(path) + gitlab_shell.exists?(project.repository_storage, path + '.git') + end + + def mv_repository(from_path, to_path) return true unless gitlab_shell.exists?(project.repository_storage, from_path + '.git') gitlab_shell.mv_repository(project.repository_storage, from_path, to_path) diff --git a/app/services/projects/open_issues_count_service.rb b/app/services/projects/open_issues_count_service.rb index a975a06a05c..0a004677417 100644 --- a/app/services/projects/open_issues_count_service.rb +++ b/app/services/projects/open_issues_count_service.rb @@ -2,14 +2,42 @@ module Projects # Service class for counting and caching the number of open issues of a # project. class OpenIssuesCountService < Projects::CountService + include Gitlab::Utils::StrongMemoize + + def initialize(project, user = nil) + @user = user + + super(project) + end + def cache_key_name - 'open_issues_count' + public_only? ? 'public_open_issues_count' : 'total_open_issues_count' + end + + def public_only? + !user_is_at_least_reporter? + end + + def relation_for_count + self.class.query(@project, public_only: public_only?) + end + + def user_is_at_least_reporter? + strong_memoize(:user_is_at_least_reporter) do + @user && @project.team.member?(@user, Gitlab::Access::REPORTER) + end end - def self.query(project_ids) - # We don't include confidential issues in this number since this would - # expose the number of confidential issues to non project members. - Issue.opened.public_only.where(project: project_ids) + # We only show total issues count for reporters + # which are allowed to view confidential issues + # This will still show a discrepancy on issues number but should be less than before. + # Check https://gitlab.com/gitlab-org/gitlab-ce/issues/38418 description. + def self.query(projects, public_only: true) + if public_only + Issue.opened.public_only.where(project: projects) + else + Issue.opened.where(project: projects) + end end end end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index ffd48e842c2..f91cd03bf5c 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -98,12 +98,12 @@ class TodoService # When a build fails on the HEAD of a merge request we should: # - # * create a todo for author of MR to fix it - # * create a todo for merge_user to keep an eye on it + # * create a todo for each merge participant # def merge_request_build_failed(merge_request) - create_build_failed_todo(merge_request, merge_request.author) - create_build_failed_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? + merge_request.merge_participants.each do |user| + create_build_failed_todo(merge_request, user) + end end # When a new commit is pushed to a merge request we should: @@ -116,20 +116,22 @@ class TodoService # When a build is retried to a merge request we should: # - # * mark all pending todos related to the merge request for the author as done - # * mark all pending todos related to the merge request for the merge_user as done + # * mark all pending todos related to the merge request as done for each merge participant # def merge_request_build_retried(merge_request) - mark_pending_todos_as_done(merge_request, merge_request.author) - mark_pending_todos_as_done(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? + merge_request.merge_participants.each do |user| + mark_pending_todos_as_done(merge_request, user) + end end - # When a merge request could not be automatically merged due to its unmergeable state we should: + # When a merge request could not be merged due to its unmergeable state we should: # - # * create a todo for a merge_user + # * create a todo for each merge participant # def merge_request_became_unmergeable(merge_request) - create_unmergeable_todo(merge_request, merge_request.merge_user) if merge_request.merge_when_pipeline_succeeds? + merge_request.merge_participants.each do |user| + create_unmergeable_todo(merge_request, user) + end end # When create a note we should: @@ -275,9 +277,9 @@ class TodoService create_todos(todo_author, attributes) end - def create_unmergeable_todo(merge_request, merge_user) - attributes = attributes_for_todo(merge_request.project, merge_request, merge_user, Todo::UNMERGEABLE) - create_todos(merge_user, attributes) + def create_unmergeable_todo(merge_request, todo_author) + attributes = attributes_for_todo(merge_request.project, merge_request, todo_author, Todo::UNMERGEABLE) + create_todos(todo_author, attributes) end def attributes_for_target(target) diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb index a3549cada95..f2a8afccdeb 100644 --- a/app/uploaders/object_storage.rb +++ b/app/uploaders/object_storage.rb @@ -103,6 +103,7 @@ module ObjectStorage end included do + include AfterCommitQueue after_save on: [:create, :update] do background_upload(changed_mounts) end diff --git a/app/views/abuse_report_mailer/notify.html.haml b/app/views/abuse_report_mailer/notify.html.haml index d50b4071745..5abb0dc9182 100644 --- a/app/views/abuse_report_mailer/notify.html.haml +++ b/app/views/abuse_report_mailer/notify.html.haml @@ -4,7 +4,7 @@ #{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)} (@#{@abuse_report.reporter.username}). -%blockquote +.blockquote = @abuse_report.message %p diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml index 06be1a53318..278ad210543 100644 --- a/app/views/abuse_reports/new.html.haml +++ b/app/views/abuse_reports/new.html.haml @@ -2,20 +2,20 @@ %h3.page-title Report abuse %p Please use this form to report users who create spam issues, comments or behave inappropriately. %hr -= form_for @abuse_report, html: { class: 'form-horizontal js-quick-submit js-requires-input'} do |f| += form_for @abuse_report, html: { class: 'js-quick-submit js-requires-input'} do |f| = form_errors(@abuse_report) = f.hidden_field :user_id - .form-group - = f.label :user_id, class: 'control-label' + .form-group.row + = f.label :user_id, class: 'col-sm-2 col-form-label' .col-sm-10 - name = "#{@abuse_report.user.name} (@#{@abuse_report.user.username})" = text_field_tag :user_name, name, class: "form-control", readonly: true - .form-group - = f.label :message, class: 'control-label' + .form-group.row + = f.label :message, class: 'col-sm-2 col-form-label' .col-sm-10 = f.text_area :message, class: "form-control", rows: 2, required: true, value: sanitize(@ref_url) - .help-block + .form-text.text-muted Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment. .form-actions diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml index 18c6c559049..89a87f38968 100644 --- a/app/views/admin/abuse_reports/_abuse_report.html.haml +++ b/app/views/admin/abuse_reports/_abuse_report.html.haml @@ -1,7 +1,7 @@ - reporter = abuse_report.reporter - user = abuse_report.user %tr - %th.visible-xs-block.visible-sm-block + %th.d-block.d-sm-none.d-md-none %strong User %td - if user @@ -11,7 +11,7 @@ - else (removed) %td - %strong.subheading.visible-xs-block.visible-sm-block Reported by + %strong.subheading.d-block.d-sm-none.d-md-none Reported by - if reporter = link_to reporter.name, reporter - else @@ -19,7 +19,7 @@ .light.small = time_ago_with_tooltip(abuse_report.created_at) %td - %strong.subheading.visible-xs-block.visible-sm-block Message + %strong.subheading.d-block.d-sm-none.d-md-none Message .message = markdown_field(abuse_report, :message) %td diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml index 6a5e170ddd8..cc29657a439 100644 --- a/app/views/admin/abuse_reports/index.html.haml +++ b/app/views/admin/abuse_reports/index.html.haml @@ -5,7 +5,7 @@ - if @abuse_reports.present? .table-holder %table.table.responsive-table - %thead.hidden-sm.hidden-xs + %thead.d-none.d-sm-none.d-md-table-header-group %tr %th User %th Reported by diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 15bda97c3b5..5e08837255f 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -1,11 +1,11 @@ -= form_for @appearance, url: admin_appearances_path, html: { class: 'form-horizontal'} do |f| += form_for @appearance, url: admin_appearances_path do |f| = form_errors(@appearance) %fieldset.app_logo %legend Navigation bar: - .form-group - = f.label :header_logo, 'Header logo', class: 'control-label' + .form-group.row + = f.label :header_logo, 'Header logo', class: 'col-sm-2 col-form-label' .col-sm-10 - if @appearance.header_logo? = image_tag @appearance.header_logo_url, class: 'appearance-light-logo-preview' @@ -21,18 +21,18 @@ %fieldset.sign-in %legend Sign in/Sign up pages: - .form-group - = f.label :title, class: 'control-label' + .form-group.row + = f.label :title, class: 'col-sm-2 col-form-label' .col-sm-10 = f.text_field :title, class: "form-control" - .form-group - = f.label :description, class: 'control-label' + .form-group.row + = f.label :description, class: 'col-sm-2 col-form-label' .col-sm-10 = f.text_area :description, class: "form-control", rows: 10 .hint Description parsed with #{link_to "GitLab Flavored Markdown", help_page_path('user/markdown'), target: '_blank'}. - .form-group - = f.label :logo, class: 'control-label' + .form-group.row + = f.label :logo, class: 'col-sm-2 col-form-label' .col-sm-10 - if @appearance.logo? = image_tag @appearance.logo_url, class: 'appearance-logo-preview' @@ -48,8 +48,8 @@ %fieldset %legend New project pages: - .form-group - = f.label :new_project_guidelines, class: 'control-label' + .form-group.row + = f.label :new_project_guidelines, class: 'col-sm-2 col-form-label' .col-sm-10 = f.text_area :new_project_guidelines, class: "form-control", rows: 10 .hint @@ -63,5 +63,5 @@ = link_to 'New project page', new_project_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' - if @appearance.updated_at - %span.pull-right + %span.float-right Last edit #{time_ago_with_tooltip(@appearance.updated_at)} diff --git a/app/views/admin/application_settings/_abuse.html.haml b/app/views/admin/application_settings/_abuse.html.haml index bb3fa26a33e..6b9b2a17dd9 100644 --- a/app/views/admin/application_settings/_abuse.html.haml +++ b/app/views/admin/application_settings/_abuse.html.haml @@ -1,12 +1,12 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - = f.label :admin_notification_email, 'Abuse reports notification email', class: 'control-label col-sm-2' + .form-group.row + = f.label :admin_notification_email, 'Abuse reports notification email', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :admin_notification_email, class: 'form-control' - .help-block + .form-text.text-muted Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area. = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml index dd86c9ed2eb..f40dd347eb3 100644 --- a/app/views/admin/application_settings/_account_and_limit.html.haml +++ b/app/views/admin/application_settings/_account_and_limit.html.haml @@ -1,37 +1,37 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :gravatar_enabled do = f.check_box :gravatar_enabled Gravatar enabled - .form-group - = f.label :default_projects_limit, class: 'control-label col-sm-2' + .form-group.row + = f.label :default_projects_limit, class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :default_projects_limit, class: 'form-control' - .form-group - = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2' + .form-group.row + = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :max_attachment_size, class: 'form-control' - .form-group - = f.label :session_expire_delay, 'Session duration (minutes)', class: 'control-label col-sm-2' + .form-group.row + = f.label :session_expire_delay, 'Session duration (minutes)', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :session_expire_delay, class: 'form-control' - %span.help-block#session_expire_delay_help_block GitLab restart is required to apply changes - .form-group - = f.label :user_oauth_applications, 'User OAuth applications', class: 'control-label col-sm-2' + %span.form-text.text-muted#session_expire_delay_help_block GitLab restart is required to apply changes + .form-group.row + = f.label :user_oauth_applications, 'User OAuth applications', class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.label :user_oauth_applications do = f.check_box :user_oauth_applications Allow users to register any application to use GitLab as an OAuth provider - .form-group - = f.label :user_default_external, 'New users set to external', class: 'control-label col-sm-2' + .form-group.row + = f.label :user_default_external, 'New users set to external', class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.label :user_default_external do = f.check_box :user_default_external Newly registered users will by default be external diff --git a/app/views/admin/application_settings/_background_jobs.html.haml b/app/views/admin/application_settings/_background_jobs.html.haml index 8198a822a10..da7248337d2 100644 --- a/app/views/admin/application_settings/_background_jobs.html.haml +++ b/app/views/admin/application_settings/_background_jobs.html.haml @@ -1,4 +1,4 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset @@ -6,25 +6,25 @@ These settings require a = link_to 'restart', help_page_path('administration/restart_gitlab') to take effect. - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :sidekiq_throttling_enabled do = f.check_box :sidekiq_throttling_enabled Enable Sidekiq Job Throttling - .help-block + .form-text.text-muted Limit the amount of resources slow running jobs are assigned. - .form-group - = f.label :sidekiq_throttling_queues, 'Sidekiq queues to throttle', class: 'control-label col-sm-2' + .form-group.row + = f.label :sidekiq_throttling_queues, 'Sidekiq queues to throttle', class: 'col-form-label col-sm-2' .col-sm-10 = f.select :sidekiq_throttling_queues, sidekiq_queue_options_for_select, { include_hidden: false }, multiple: true, class: 'select2 select-wide', data: { field: 'sidekiq_throttling_queues' } - .help-block + .form-text.text-muted Choose which queues you wish to throttle. - .form-group - = f.label :sidekiq_throttling_factor, 'Throttling Factor', class: 'control-label col-sm-2' + .form-group.row + = f.label :sidekiq_throttling_factor, 'Throttling Factor', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :sidekiq_throttling_factor, class: 'form-control', min: '0.01', max: '0.99', step: '0.01' - .help-block + .form-text.text-muted The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive. = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml index b4d2a789df0..6a06271de2a 100644 --- a/app/views/admin/application_settings/_ci_cd.html.haml +++ b/app/views/admin/application_settings/_ci_cd.html.haml @@ -1,45 +1,45 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :auto_devops_enabled do = f.check_box :auto_devops_enabled - Enabled Auto DevOps (Beta) for projects by default - .help-block + Enabled Auto DevOps for projects by default + .form-text.text-muted It will automatically build, test, and deploy applications based on a predefined CI/CD configuration = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md') - .form-group - = f.label :auto_devops_domain, class: 'control-label col-sm-2' + .form-group.row + = f.label :auto_devops_domain, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :auto_devops_domain, class: 'form-control', placeholder: 'domain.com' - .help-block + .form-text.text-muted = s_("AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages.") - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :shared_runners_enabled do = f.check_box :shared_runners_enabled Enable shared runners for new projects - .form-group - = f.label :shared_runners_text, class: 'control-label col-sm-2' + .form-group.row + = f.label :shared_runners_text, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_area :shared_runners_text, class: 'form-control', rows: 4 - .help-block Markdown enabled - .form-group - = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' + .form-text.text-muted Markdown enabled + .form-group.row + = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :max_artifacts_size, class: 'form-control' - .help-block + .form-text.text-muted Set the maximum file size for each job's artifacts = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size') - .form-group - = f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'control-label col-sm-2' + .form-group.row + = f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :default_artifacts_expire_in, class: 'form-control' - .help-block + .form-text.text-muted Set the default expiration time for each job's artifacts. 0 for unlimited. = link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration') diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml index 6c89f1c4e98..7443f02ad15 100644 --- a/app/views/admin/application_settings/_email.html.haml +++ b/app/views/admin/application_settings/_email.html.haml @@ -1,24 +1,24 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :email_author_in_body do = f.check_box :email_author_in_body Include author name in notification email body - .help-block + .form-text.text-muted Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :html_emails_enabled do = f.check_box :html_emails_enabled Enable HTML emails - .help-block + .form-text.text-muted By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format. diff --git a/app/views/admin/application_settings/_gitaly.html.haml b/app/views/admin/application_settings/_gitaly.html.haml index 4acc5b3a0c5..859a1c6f45c 100644 --- a/app/views/admin/application_settings/_gitaly.html.haml +++ b/app/views/admin/application_settings/_gitaly.html.haml @@ -1,27 +1,27 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - = f.label :gitaly_timeout_default, 'Default Timeout Period', class: 'control-label col-sm-2' + .form-group.row + = f.label :gitaly_timeout_default, 'Default Timeout Period', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :gitaly_timeout_default, class: 'form-control' - .help-block + .form-text.text-muted Timeout for Gitaly calls from the GitLab application (in seconds). This timeout is not enforced for git fetch/push operations or Sidekiq jobs. - .form-group - = f.label :gitaly_timeout_fast, 'Fast Timeout Period', class: 'control-label col-sm-2' + .form-group.row + = f.label :gitaly_timeout_fast, 'Fast Timeout Period', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :gitaly_timeout_fast, class: 'form-control' - .help-block + .form-text.text-muted Fast operation timeout (in seconds). Some Gitaly operations are expected to be fast. If they exceed this threshold, there may be a problem with a storage shard and 'failing fast' can help maintain the stability of the GitLab instance. - .form-group - = f.label :gitaly_timeout_medium, 'Medium Timeout Period', class: 'control-label col-sm-2' + .form-group.row + = f.label :gitaly_timeout_medium, 'Medium Timeout Period', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :gitaly_timeout_medium, class: 'form-control' - .help-block + .form-text.text-muted Medium operation timeout (in seconds). This should be a value between the Fast and the Default timeout. = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml index 3bc101ddf04..97d397788c6 100644 --- a/app/views/admin/application_settings/_help_page.html.haml +++ b/app/views/admin/application_settings/_help_page.html.haml @@ -1,22 +1,22 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - = f.label :help_page_text, class: 'control-label col-sm-2' + .form-group.row + = f.label :help_page_text, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_area :help_page_text, class: 'form-control', rows: 4 - .help-block Markdown enabled - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-text.text-muted Markdown enabled + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :help_page_hide_commercial_content do = f.check_box :help_page_hide_commercial_content Hide marketing-related entries from help - .form-group - = f.label :help_page_support_url, 'Support page URL', class: 'control-label col-sm-2' + .form-group.row + = f.label :help_page_support_url, 'Support page URL', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :help_page_support_url, class: 'form-control', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block' - %span.help-block#support_help_block Alternate support URL for help page + %span.form-text.text-muted#support_help_block Alternate support URL for help page = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_influx.html.haml b/app/views/admin/application_settings/_influx.html.haml index a173fd38a9c..e5d699f9a2f 100644 --- a/app/views/admin/application_settings/_influx.html.haml +++ b/app/views/admin/application_settings/_influx.html.haml @@ -1,4 +1,4 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset @@ -8,60 +8,60 @@ = link_to 'restart', help_page_path('administration/restart_gitlab') to take effect. = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :metrics_enabled do = f.check_box :metrics_enabled Enable InfluxDB Metrics - .form-group - = f.label :metrics_host, 'InfluxDB host', class: 'control-label col-sm-2' + .form-group.row + = f.label :metrics_host, 'InfluxDB host', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :metrics_host, class: 'form-control', placeholder: 'influxdb.example.com' - .form-group - = f.label :metrics_port, 'InfluxDB port', class: 'control-label col-sm-2' + .form-group.row + = f.label :metrics_port, 'InfluxDB port', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :metrics_port, class: 'form-control', placeholder: '8089' - .help-block + .form-text.text-muted The UDP port to use for connecting to InfluxDB. InfluxDB requires that your server configuration specifies a database to store data in when sending messages to this port, without it metrics data will not be saved. - .form-group - = f.label :metrics_pool_size, 'Connection pool size', class: 'control-label col-sm-2' + .form-group.row + = f.label :metrics_pool_size, 'Connection pool size', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :metrics_pool_size, class: 'form-control' - .help-block + .form-text.text-muted The amount of InfluxDB connections to open. Connections are opened lazily. Users using multi-threaded application servers should ensure enough connections are available (at minimum the amount of application server threads). - .form-group - = f.label :metrics_timeout, 'Connection timeout', class: 'control-label col-sm-2' + .form-group.row + = f.label :metrics_timeout, 'Connection timeout', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :metrics_timeout, class: 'form-control' - .help-block + .form-text.text-muted The amount of seconds after which an InfluxDB connection will time out. - .form-group - = f.label :metrics_method_call_threshold, 'Method Call Threshold (ms)', class: 'control-label col-sm-2' + .form-group.row + = f.label :metrics_method_call_threshold, 'Method Call Threshold (ms)', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :metrics_method_call_threshold, class: 'form-control' - .help-block + .form-text.text-muted A method call is only tracked when it takes longer to complete than the given amount of milliseconds. - .form-group - = f.label :metrics_sample_interval, 'Sampler Interval (sec)', class: 'control-label col-sm-2' + .form-group.row + = f.label :metrics_sample_interval, 'Sampler Interval (sec)', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :metrics_sample_interval, class: 'form-control' - .help-block + .form-text.text-muted The sampling interval in seconds. Sampled data includes memory usage, retained Ruby objects, file descriptors and so on. - .form-group - = f.label :metrics_packet_size, 'Metrics per packet', class: 'control-label col-sm-2' + .form-group.row + = f.label :metrics_packet_size, 'Metrics per packet', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :metrics_packet_size, class: 'form-control' - .help-block + .form-text.text-muted The amount of points to store in a single UDP packet. More points results in fewer but larger UDP packets being sent. diff --git a/app/views/admin/application_settings/_ip_limits.html.haml b/app/views/admin/application_settings/_ip_limits.html.haml index b83ffc375d9..539ff9b5168 100644 --- a/app/views/admin/application_settings/_ip_limits.html.haml +++ b/app/views/admin/application_settings/_ip_limits.html.haml @@ -1,53 +1,53 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :throttle_unauthenticated_enabled do = f.check_box :throttle_unauthenticated_enabled Enable unauthenticated request rate limit - %span.help-block + %span.form-text.text-muted Helps reduce request volume (e.g. from crawlers or abusive bots) - .form-group - = f.label :throttle_unauthenticated_requests_per_period, 'Max requests per period per IP', class: 'control-label col-sm-2' + .form-group.row + = f.label :throttle_unauthenticated_requests_per_period, 'Max requests per period per IP', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :throttle_unauthenticated_requests_per_period, class: 'form-control' - .form-group - = f.label :throttle_unauthenticated_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2' + .form-group.row + = f.label :throttle_unauthenticated_period_in_seconds, 'Rate limit period in seconds', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :throttle_unauthenticated_period_in_seconds, class: 'form-control' - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :throttle_authenticated_api_enabled do = f.check_box :throttle_authenticated_api_enabled Enable authenticated API request rate limit - %span.help-block + %span.form-text.text-muted Helps reduce request volume (e.g. from crawlers or abusive bots) - .form-group - = f.label :throttle_authenticated_api_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2' + .form-group.row + = f.label :throttle_authenticated_api_requests_per_period, 'Max requests per period per user', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :throttle_authenticated_api_requests_per_period, class: 'form-control' - .form-group - = f.label :throttle_authenticated_api_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2' + .form-group.row + = f.label :throttle_authenticated_api_period_in_seconds, 'Rate limit period in seconds', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :throttle_authenticated_api_period_in_seconds, class: 'form-control' - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :throttle_authenticated_web_enabled do = f.check_box :throttle_authenticated_web_enabled Enable authenticated web request rate limit - %span.help-block + %span.form-text.text-muted Helps reduce request volume (e.g. from crawlers or abusive bots) - .form-group - = f.label :throttle_authenticated_web_requests_per_period, 'Max requests per period per user', class: 'control-label col-sm-2' + .form-group.row + = f.label :throttle_authenticated_web_requests_per_period, 'Max requests per period per user', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :throttle_authenticated_web_requests_per_period, class: 'form-control' - .form-group - = f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'control-label col-sm-2' + .form-group.row + = f.label :throttle_authenticated_web_period_in_seconds, 'Rate limit period in seconds', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :throttle_authenticated_web_period_in_seconds, class: 'form-control' diff --git a/app/views/admin/application_settings/_koding.html.haml b/app/views/admin/application_settings/_koding.html.haml index 17358cf775b..0532f49fd5b 100644 --- a/app/views/admin/application_settings/_koding.html.haml +++ b/app/views/admin/application_settings/_koding.html.haml @@ -1,20 +1,20 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :koding_enabled do = f.check_box :koding_enabled Enable Koding - .help-block + .form-text.text-muted Koding integration has been deprecated since GitLab 10.0. If you disable your Koding integration, you will not be able to enable it again. - .form-group - = f.label :koding_url, 'Koding URL', class: 'control-label col-sm-2' + .form-group.row + = f.label :koding_url, 'Koding URL', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :koding_url, class: 'form-control', placeholder: 'http://gitlab.your-koding-instance.com:8090' - .help-block + .form-text.text-muted Koding has integration enabled out of the box for the %strong gitlab team, and you need to provide that team's URL here. Learn more in the diff --git a/app/views/admin/application_settings/_logging.html.haml b/app/views/admin/application_settings/_logging.html.haml index 44a11ddc120..4341801b045 100644 --- a/app/views/admin/application_settings/_logging.html.haml +++ b/app/views/admin/application_settings/_logging.html.haml @@ -1,35 +1,35 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :sentry_enabled do = f.check_box :sentry_enabled Enable Sentry - .help-block + .form-text.text-muted %p This setting requires a restart to take effect. Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: %a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com - .form-group - = f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2' + .form-group.row + = f.label :sentry_dsn, 'Sentry DSN', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :sentry_dsn, class: 'form-control' - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :clientside_sentry_enabled do = f.check_box :clientside_sentry_enabled Enable Clientside Sentry - .help-block + .form-text.text-muted Sentry can also be used for reporting and logging clientside exceptions. %a{ href: 'https://sentry.io/for/javascript/', target: '_blank', rel: 'noopener noreferrer' } https://sentry.io/for/javascript/ - .form-group - = f.label :clientside_sentry_dsn, 'Clientside Sentry DSN', class: 'control-label col-sm-2' + .form-group.row + = f.label :clientside_sentry_dsn, 'Clientside Sentry DSN', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :clientside_sentry_dsn, class: 'form-control' diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml index d10f609006d..176cdfe9e1a 100644 --- a/app/views/admin/application_settings/_outbound.html.haml +++ b/app/views/admin/application_settings/_outbound.html.haml @@ -1,10 +1,10 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :allow_local_requests_from_hooks_and_services do = f.check_box :allow_local_requests_from_hooks_and_services Allow requests to the local network from hooks and services diff --git a/app/views/admin/application_settings/_pages.html.haml b/app/views/admin/application_settings/_pages.html.haml index b28ecf9a039..0100ef038e2 100644 --- a/app/views/admin/application_settings/_pages.html.haml +++ b/app/views/admin/application_settings/_pages.html.haml @@ -1,19 +1,19 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - = f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'control-label col-sm-2' + .form-group.row + = f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :max_pages_size, class: 'form-control' - .help-block 0 for unlimited - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-text.text-muted 0 for unlimited + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :pages_domain_verification_enabled do = f.check_box :pages_domain_verification_enabled Require users to prove ownership of custom domains - .help-block + .form-text.text-muted Domain verification is an essential security measure for public GitLab sites. Users are required to demonstrate they control a domain before it is enabled diff --git a/app/views/admin/application_settings/_performance.html.haml b/app/views/admin/application_settings/_performance.html.haml index 01d5a31aa9f..07d3a8b7cdf 100644 --- a/app/views/admin/application_settings/_performance.html.haml +++ b/app/views/admin/application_settings/_performance.html.haml @@ -1,14 +1,14 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :authorized_keys_enabled do = f.check_box :authorized_keys_enabled Write to "authorized_keys" file - .help-block + .form-text.text-muted By default, we write to the "authorized_keys" file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only uncheck this diff --git a/app/views/admin/application_settings/_performance_bar.html.haml b/app/views/admin/application_settings/_performance_bar.html.haml index 5344f030c97..8001b42e2f9 100644 --- a/app/views/admin/application_settings/_performance_bar.html.haml +++ b/app/views/admin/application_settings/_performance_bar.html.haml @@ -1,15 +1,15 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :performance_bar_enabled do = f.check_box :performance_bar_enabled Enable the Performance Bar - .form-group - = f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'control-label col-sm-2' + .form-group.row + = f.label :performance_bar_allowed_group_id, 'Allowed group', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :performance_bar_allowed_group_id, class: 'form-control', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path diff --git a/app/views/admin/application_settings/_plantuml.html.haml b/app/views/admin/application_settings/_plantuml.html.haml index 56764b3fb81..ee6d1d1a888 100644 --- a/app/views/admin/application_settings/_plantuml.html.haml +++ b/app/views/admin/application_settings/_plantuml.html.haml @@ -1,18 +1,18 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :plantuml_enabled do = f.check_box :plantuml_enabled Enable PlantUML - .form-group - = f.label :plantuml_url, 'PlantUML URL', class: 'control-label col-sm-2' + .form-group.row + = f.label :plantuml_url, 'PlantUML URL', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :plantuml_url, class: 'form-control', placeholder: 'http://gitlab.your-plantuml-instance.com:8080' - .help-block + .form-text.text-muted Allow rendering of = link_to "PlantUML", "http://plantuml.com" diagrams in Asciidoc documents using an external PlantUML service. diff --git a/app/views/admin/application_settings/_prometheus.html.haml b/app/views/admin/application_settings/_prometheus.html.haml index 48745db2991..8c95597e787 100644 --- a/app/views/admin/application_settings/_prometheus.html.haml +++ b/app/views/admin/application_settings/_prometheus.html.haml @@ -1,4 +1,4 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset @@ -11,14 +11,14 @@ = link_to 'restart', help_page_path('administration/restart_gitlab') to take effect. = link_to icon('question-circle'), help_page_path('administration/monitoring/prometheus/index') - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :prometheus_metrics_enabled do = f.check_box :prometheus_metrics_enabled Enable Prometheus Metrics - unless Gitlab::Metrics.metrics_folder_present? - .help-block + .form-text.text-muted %strong.cred WARNING: Environment variable %code prometheus_multiproc_dir diff --git a/app/views/admin/application_settings/_realtime.html.haml b/app/views/admin/application_settings/_realtime.html.haml index 0a53a75119e..63a592cc2fd 100644 --- a/app/views/admin/application_settings/_realtime.html.haml +++ b/app/views/admin/application_settings/_realtime.html.haml @@ -1,12 +1,12 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - = f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'control-label col-sm-2' + .form-group.row + = f.label :polling_interval_multiplier, 'Polling interval multiplier', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :polling_interval_multiplier, class: 'form-control' - .help-block + .form-text.text-muted Change this value to influence how frequently the GitLab UI polls for updates. If you set the value to 2 all polling intervals are multiplied by 2, which means that polling happens half as frequently. diff --git a/app/views/admin/application_settings/_registry.html.haml b/app/views/admin/application_settings/_registry.html.haml index 3451ef62458..8524cbfe4d9 100644 --- a/app/views/admin/application_settings/_registry.html.haml +++ b/app/views/admin/application_settings/_registry.html.haml @@ -1,9 +1,9 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - = f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'control-label col-sm-2' + .form-group.row + = f.label :container_registry_token_expire_delay, 'Authorization token duration (minutes)', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :container_registry_token_expire_delay, class: 'form-control' diff --git a/app/views/admin/application_settings/_repository_check.html.haml b/app/views/admin/application_settings/_repository_check.html.haml index fe335f30a62..739ed359e16 100644 --- a/app/views/admin/application_settings/_repository_check.html.haml +++ b/app/views/admin/application_settings/_repository_check.html.haml @@ -1,62 +1,62 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset .sub-section %h4 Repository checks - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :repository_checks_enabled do = f.check_box :repository_checks_enabled Enable Repository Checks - .help-block + .form-text.text-muted GitLab will periodically run %a{ href: 'https://git-scm.com/docs/git-fsck', target: 'blank' } 'git fsck' in all project and wiki repositories to look for silent disk corruption issues. - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 = link_to 'Clear all repository checks', clear_repository_check_states_admin_application_settings_path, data: { confirm: 'This will clear repository check states for ALL projects in the database. This cannot be undone. Are you sure?' }, method: :put, class: "btn btn-sm btn-remove" - .help-block + .form-text.text-muted If you got a lot of false alarms from repository checks you can choose to clear all repository check information from the database. .sub-section %h4 Housekeeping - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :housekeeping_enabled do = f.check_box :housekeeping_enabled Enable automatic repository housekeeping (git repack, git gc) - .help-block + .form-text.text-muted If you keep automatic housekeeping disabled for a long time Git repository access on your GitLab server will become slower and your repositories will use more disk space. We recommend to always leave this enabled. - .checkbox + .form-check = f.label :housekeeping_bitmaps_enabled do = f.check_box :housekeeping_bitmaps_enabled Enable Git pack file bitmap creation - .help-block + .form-text.text-muted Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance. - .form-group - = f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'control-label col-sm-2' + .form-group.row + = f.label :housekeeping_incremental_repack_period, 'Incremental repack period', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :housekeeping_incremental_repack_period, class: 'form-control' - .help-block + .form-text.text-muted Number of Git pushes after which an incremental 'git repack' is run. - .form-group - = f.label :housekeeping_full_repack_period, 'Full repack period', class: 'control-label col-sm-2' + .form-group.row + = f.label :housekeeping_full_repack_period, 'Full repack period', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :housekeeping_full_repack_period, class: 'form-control' - .help-block + .form-text.text-muted Number of Git pushes after which a full 'git repack' is run. - .form-group - = f.label :housekeeping_gc_period, 'Git GC period', class: 'control-label col-sm-2' + .form-group.row + = f.label :housekeeping_gc_period, 'Git GC period', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :housekeeping_gc_period, class: 'form-control' - .help-block + .form-text.text-muted Number of Git pushes after which 'git gc' is run. = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_repository_mirrors_form.html.haml b/app/views/admin/application_settings/_repository_mirrors_form.html.haml index 09183ec6260..9d05a5aa234 100644 --- a/app/views/admin/application_settings/_repository_mirrors_form.html.haml +++ b/app/views/admin/application_settings/_repository_mirrors_form.html.haml @@ -5,7 +5,7 @@ .form-group = f.label :mirror_available, 'Enable mirror configuration', class: 'control-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.label :mirror_available do = f.check_box :mirror_available Allow mirrors to be setup for projects diff --git a/app/views/admin/application_settings/_repository_storage.html.haml b/app/views/admin/application_settings/_repository_storage.html.haml index 4eebb59110a..be85210934c 100644 --- a/app/views/admin/application_settings/_repository_storage.html.haml +++ b/app/views/admin/application_settings/_repository_storage.html.haml @@ -1,58 +1,58 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset .sub-section - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :hashed_storage_enabled do = f.check_box :hashed_storage_enabled Create new projects using hashed storage paths - .help-block + .form-text.text-muted Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Project URL changes and may improve disk I/O performance. %em (EXPERIMENTAL) - .form-group - = f.label :repository_storages, 'Storage paths for new projects', class: 'control-label col-sm-2' + .form-group.row + = f.label :repository_storages, 'Storage paths for new projects', class: 'col-form-label col-sm-2' .col-sm-10 = f.select :repository_storages, repository_storages_options_for_select(@application_setting.repository_storages), {include_hidden: false}, multiple: true, class: 'form-control' - .help-block + .form-text.text-muted Manage repository storage paths. Learn more in the = succeed "." do = link_to "repository storages documentation", help_page_path("administration/repository_storage_paths") .sub-section %h4 Circuit breaker - .form-group - = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'control-label col-sm-2' + .form-group.row + = f.label :circuitbreaker_check_interval, _('Check interval'), class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :circuitbreaker_check_interval, class: 'form-control' - .help-block + .form-text.text-muted = circuitbreaker_check_interval_help_text - .form-group - = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'control-label col-sm-2' + .form-group.row + = f.label :circuitbreaker_access_retries, _('Number of access attempts'), class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :circuitbreaker_access_retries, class: 'form-control' - .help-block + .form-text.text-muted = circuitbreaker_access_retries_help_text - .form-group - = f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'control-label col-sm-2' + .form-group.row + = f.label :circuitbreaker_storage_timeout, _('Seconds to wait for a storage access attempt'), class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :circuitbreaker_storage_timeout, class: 'form-control' - .help-block + .form-text.text-muted = circuitbreaker_storage_timeout_help_text - .form-group - = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'control-label col-sm-2' + .form-group.row + = f.label :circuitbreaker_failure_count_threshold, _('Maximum git storage failures'), class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :circuitbreaker_failure_count_threshold, class: 'form-control' - .help-block + .form-text.text-muted = circuitbreaker_failure_count_help_text - .form-group - = f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'control-label col-sm-2' + .form-group.row + = f.label :circuitbreaker_failure_reset_time, _('Seconds before reseting failure information'), class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :circuitbreaker_failure_reset_time, class: 'form-control' - .help-block + .form-text.text-muted = circuitbreaker_failure_reset_time_help_text = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_signin.html.haml b/app/views/admin/application_settings/_signin.html.haml index 48331c40bca..4d74568d69a 100644 --- a/app/views/admin/application_settings/_signin.html.haml +++ b/app/views/admin/application_settings/_signin.html.haml @@ -1,60 +1,60 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :password_authentication_enabled_for_web do = f.check_box :password_authentication_enabled_for_web Password authentication enabled for web interface - .help-block + .form-text.text-muted When disabled, an external authentication provider must be used. - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :password_authentication_enabled_for_git do = f.check_box :password_authentication_enabled_for_git Password authentication enabled for Git over HTTP(S) - .help-block + .form-text.text-muted When disabled, a Personal Access Token - if Gitlab::Auth::LDAP::Config.enabled? or LDAP password must be used to authenticate. - if omniauth_enabled? && button_based_providers.any? - .form-group + .form-group.row = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth sign-in sources', class: 'control-label col-sm-2' = hidden_field_tag 'application_setting[enabled_oauth_sign_in_sources][]' .col-sm-10 .btn-group{ data: { toggle: 'buttons' } } - oauth_providers_checkboxes.each do |source| = source - .form-group - = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' + .form-group.row + = f.label :two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.label :require_two_factor_authentication do = f.check_box :require_two_factor_authentication Require all users to setup Two-factor authentication - .form-group - = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'control-label col-sm-2' + .form-group.row + = f.label :two_factor_authentication, 'Two-factor grace period (hours)', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :two_factor_grace_period, min: 0, class: 'form-control', placeholder: '0' - .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication - .form-group - = f.label :home_page_url, 'Home page URL', class: 'control-label col-sm-2' + .form-text.text-muted Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication + .form-group.row + = f.label :home_page_url, 'Home page URL', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block' - %span.help-block#home_help_block We will redirect non-logged in users to this page - .form-group - = f.label :after_sign_out_path, class: 'control-label col-sm-2' + %span.form-text.text-muted#home_help_block We will redirect non-logged in users to this page + .form-group.row + = f.label :after_sign_out_path, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :after_sign_out_path, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'after_sign_out_path_help_block' - %span.help-block#after_sign_out_path_help_block We will redirect users to this page after they sign out - .form-group - = f.label :sign_in_text, class: 'control-label col-sm-2' + %span.form-text.text-muted#after_sign_out_path_help_block We will redirect users to this page after they sign out + .form-group.row + = f.label :sign_in_text, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_area :sign_in_text, class: 'form-control', rows: 4 - .help-block Markdown enabled + .form-text.text-muted Markdown enabled = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_signup.html.haml b/app/views/admin/application_settings/_signup.html.haml index 85f311dd894..50c455f8686 100644 --- a/app/views/admin/application_settings/_signup.html.haml +++ b/app/views/admin/application_settings/_signup.html.haml @@ -1,58 +1,58 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :signup_enabled do = f.check_box :signup_enabled Sign-up enabled - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :send_user_confirmation_email do = f.check_box :send_user_confirmation_email Send confirmation email on sign-up - .form-group - = f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'control-label col-sm-2' + .form-group.row + = f.label :domain_whitelist, 'Whitelisted domains for sign-ups', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_area :domain_whitelist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8 - .help-block ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com - .form-group - = f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'control-label col-sm-2' + .form-text.text-muted ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com + .form-group.row + = f.label :domain_blacklist_enabled, 'Domain Blacklist', class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.label :domain_blacklist_enabled do = f.check_box :domain_blacklist_enabled Enable domain blacklist for sign ups - .form-group - .col-sm-offset-2.col-sm-10 - .radio + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = label_tag :blacklist_type_file do = radio_button_tag :blacklist_type, :file .option-title Upload blacklist file - .radio + .form-check = label_tag :blacklist_type_raw do = radio_button_tag :blacklist_type, :raw, @application_setting.domain_blacklist.present? || @application_setting.domain_blacklist.blank? .option-title Enter blacklist manually - .form-group.blacklist-file - = f.label :domain_blacklist_file, 'Blacklist file', class: 'control-label col-sm-2' + .form-group.row.blacklist-file + = f.label :domain_blacklist_file, 'Blacklist file', class: 'col-form-label col-sm-2' .col-sm-10 = f.file_field :domain_blacklist_file, class: 'form-control', accept: '.txt,.conf' - .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries. - .form-group.blacklist-raw - = f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'control-label col-sm-2' + .form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines or commas for multiple entries. + .form-group.row.blacklist-raw + = f.label :domain_blacklist, 'Blacklisted domains for sign-ups', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_area :domain_blacklist_raw, placeholder: 'domain.com', class: 'form-control', rows: 8 - .help-block Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com + .form-text.text-muted Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com - .form-group - = f.label :after_sign_up_text, class: 'control-label col-sm-2' + .form-group.row + = f.label :after_sign_up_text, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_area :after_sign_up_text, class: 'form-control', rows: 4 - .help-block Markdown enabled + .form-text.text-muted Markdown enabled = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_spam.html.haml b/app/views/admin/application_settings/_spam.html.haml index 25e89097dfe..58543a0359a 100644 --- a/app/views/admin/application_settings/_spam.html.haml +++ b/app/views/admin/application_settings/_spam.html.haml @@ -1,65 +1,65 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :recaptcha_enabled do = f.check_box :recaptcha_enabled Enable reCAPTCHA - %span.help-block#recaptcha_help_block Helps prevent bots from creating accounts + %span.form-text.text-muted#recaptcha_help_block Helps prevent bots from creating accounts - .form-group - = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2' + .form-group.row + = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :recaptcha_site_key, class: 'form-control' - .help-block + .form-text.text-muted Generate site and private keys at %a{ href: 'http://www.google.com/recaptcha', target: 'blank' } http://www.google.com/recaptcha - .form-group - = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2' + .form-group.row + = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :recaptcha_private_key, class: 'form-control' - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :akismet_enabled do = f.check_box :akismet_enabled Enable Akismet - %span.help-block#akismet_help_block Helps prevent bots from creating issues + %span.form-text.text-muted#akismet_help_block Helps prevent bots from creating issues - .form-group - = f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2' + .form-group.row + = f.label :akismet_api_key, 'Akismet API Key', class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :akismet_api_key, class: 'form-control' - .help-block + .form-text.text-muted Generate API key at %a{ href: 'http://www.akismet.com', target: 'blank' } http://www.akismet.com - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :unique_ips_limit_enabled do = f.check_box :unique_ips_limit_enabled Limit sign in from multiple ips - %span.help-block#unique_ip_help_block + %span.form-text.text-muted#unique_ip_help_block Helps prevent malicious users hide their activity - .form-group - = f.label :unique_ips_limit_per_user, 'IPs per user', class: 'control-label col-sm-2' + .form-group.row + = f.label :unique_ips_limit_per_user, 'IPs per user', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :unique_ips_limit_per_user, class: 'form-control' - .help-block + .form-text.text-muted Maximum number of unique IPs per user - .form-group - = f.label :unique_ips_limit_time_window, 'IP expiration time', class: 'control-label col-sm-2' + .form-group.row + = f.label :unique_ips_limit_time_window, 'IP expiration time', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :unique_ips_limit_time_window, class: 'form-control' - .help-block + .form-text.text-muted How many seconds an IP will be counted towards the limit = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_terminal.html.haml b/app/views/admin/application_settings/_terminal.html.haml index 36d8838803f..ae02d07e556 100644 --- a/app/views/admin/application_settings/_terminal.html.haml +++ b/app/views/admin/application_settings/_terminal.html.haml @@ -1,12 +1,12 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - = f.label :terminal_max_session_time, 'Max session time', class: 'control-label col-sm-2' + .form-group.row + = f.label :terminal_max_session_time, 'Max session time', class: 'col-form-label col-sm-2' .col-sm-10 = f.number_field :terminal_max_session_time, class: 'form-control' - .help-block + .form-text.text-muted Maximum time for web terminal websocket connection (in seconds). 0 for unlimited. diff --git a/app/views/admin/application_settings/_terms.html.haml b/app/views/admin/application_settings/_terms.html.haml index 724246ab7e7..32b060972ec 100644 --- a/app/views/admin/application_settings/_terms.html.haml +++ b/app/views/admin/application_settings/_terms.html.haml @@ -4,7 +4,7 @@ %fieldset .form-group .col-sm-12 - .checkbox + .form-check = f.label :enforce_terms do = f.check_box :enforce_terms = _("Require all users to accept Terms of Service when they access GitLab.") diff --git a/app/views/admin/application_settings/_usage.html.haml b/app/views/admin/application_settings/_usage.html.haml index 7684e2cfdd1..316c8b04dea 100644 --- a/app/views/admin/application_settings/_usage.html.haml +++ b/app/views/admin/application_settings/_usage.html.haml @@ -1,25 +1,25 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :version_check_enabled do = f.check_box :version_check_enabled Enable version check - .help-block + .form-text.text-muted GitLab will inform you if a new version is available. = link_to 'Learn more', help_page_path("user/admin_area/settings/usage_statistics", anchor: "version-check") about what information is shared with GitLab Inc. - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 - can_be_configured = @application_setting.usage_ping_can_be_configured? - .checkbox + .form-check = f.label :usage_ping_enabled do = f.check_box :usage_ping_enabled, disabled: !can_be_configured Enable usage ping - .help-block + .form-text.text-muted - if can_be_configured To help improve GitLab and its user experience, GitLab will periodically collect usage information. diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml index a75dd90fe6b..c37a89237f0 100644 --- a/app/views/admin/application_settings/_visibility_and_access.html.haml +++ b/app/views/admin/application_settings/_visibility_and_access.html.haml @@ -1,41 +1,41 @@ -= form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| += form_for @application_setting, url: admin_application_settings_path, html: { class: 'fieldset-form' } do |f| = form_errors(@application_setting) %fieldset - .form-group - = f.label :default_branch_protection, class: 'control-label col-sm-2' + .form-group.row + = f.label :default_branch_protection, class: 'col-form-label col-sm-2' .col-sm-10 = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control' - .form-group.visibility-level-setting - = f.label :default_project_visibility, class: 'control-label col-sm-2' + .form-group.row.visibility-level-setting + = f.label :default_project_visibility, class: 'col-form-label col-sm-2' .col-sm-10 = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new) - .form-group.visibility-level-setting - = f.label :default_snippet_visibility, class: 'control-label col-sm-2' + .form-group.row.visibility-level-setting + = f.label :default_snippet_visibility, class: 'col-form-label col-sm-2' .col-sm-10 = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: ProjectSnippet.new) - .form-group.visibility-level-setting - = f.label :default_group_visibility, class: 'control-label col-sm-2' + .form-group.row.visibility-level-setting + = f.label :default_group_visibility, class: 'col-form-label col-sm-2' .col-sm-10 = render('shared/visibility_radios', model_method: :default_group_visibility, form: f, selected_level: @application_setting.default_group_visibility, form_model: Group.new) - .form-group - = f.label :restricted_visibility_levels, class: 'control-label col-sm-2' + .form-group.row + = f.label :restricted_visibility_levels, class: 'col-form-label col-sm-2' .col-sm-10 - checkbox_name = 'application_setting[restricted_visibility_levels][]' = hidden_field_tag(checkbox_name) - restricted_level_checkboxes('restricted-visibility-help', checkbox_name).each do |level| - .checkbox + .form-check = level - %span.help-block#restricted-visibility-help + %span.form-text.text-muted#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets. If the public level is restricted, user profiles are only visible to logged in users. - .form-group - = f.label :import_sources, class: 'control-label col-sm-2' + .form-group.row + = f.label :import_sources, class: 'col-form-label col-sm-2' .col-sm-10 = hidden_field_tag 'application_setting[import_sources][]' - import_sources_checkboxes('import-sources-help').each do |source| - .checkbox= source - %span.help-block#import-sources-help + .form-check= source + %span.form-text.text-muted#import-sources-help Enabled sources for code import during project creation. OmniAuth must be configured for GitHub = link_to "(?)", help_page_path("integration/github") , Bitbucket @@ -43,24 +43,24 @@ and GitLab.com = link_to "(?)", help_page_path("integration/gitlab") - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.label :project_export_enabled do = f.check_box :project_export_enabled Project export enabled - .form-group - %label.control-label.col-sm-2 Enabled Git access protocols + .form-group.row + %label.col-form-label.col-sm-2 Enabled Git access protocols .col-sm-10 = select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control') - %span.help-block#clone-protocol-help + %span.form-text.text-muted#clone-protocol-help Allow only the selected protocols to be used for Git access. - ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type| - field_name = :"#{type}_key_restriction" - .form-group - = f.label field_name, "#{type.upcase} SSH keys", class: 'control-label col-sm-2' + .form-group.row + = f.label field_name, "#{type.upcase} SSH keys", class: 'col-form-label col-sm-2' .col-sm-10 = f.select field_name, key_restriction_options_for_select(type), {}, class: 'form-control' diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml index 93827d6a1ab..b5d79e1da13 100644 --- a/app/views/admin/applications/_form.html.haml +++ b/app/views/admin/applications/_form.html.haml @@ -1,34 +1,34 @@ -= form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f| += form_for [:admin, @application], url: @url, html: {role: 'form'} do |f| = form_errors(application) = content_tag :div, class: 'form-group' do - = f.label :name, class: 'col-sm-2 control-label' + = f.label :name, class: 'col-sm-2 col-form-label' .col-sm-10 = f.text_field :name, class: 'form-control' = doorkeeper_errors_for application, :name = content_tag :div, class: 'form-group' do - = f.label :redirect_uri, class: 'col-sm-2 control-label' + = f.label :redirect_uri, class: 'col-sm-2 col-form-label' .col-sm-10 = f.text_area :redirect_uri, class: 'form-control' = doorkeeper_errors_for application, :redirect_uri - %span.help-block + %span.form-text.text-muted Use one line per URI - if Doorkeeper.configuration.native_redirect_uri - %span.help-block + %span.form-text.text-muted Use %code= Doorkeeper.configuration.native_redirect_uri for local tests = content_tag :div, class: 'form-group' do - = f.label :trusted, class: 'col-sm-2 control-label' + = f.label :trusted, class: 'col-sm-2 col-form-label' .col-sm-10 = f.check_box :trusted - %span.help-block + %span.form-text.text-muted Trusted applications are automatically authorized on GitLab OAuth flow. - .form-group - = f.label :scopes, class: 'col-sm-2 control-label' + .form-group.row + = f.label :scopes, class: 'col-sm-2 col-form-label' .col-sm-10 = render 'shared/tokens/scopes_form', prefix: 'doorkeeper_application', token: application, scopes: @scopes diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml index 5125aa21b06..593a6d816e3 100644 --- a/app/views/admin/applications/show.html.haml +++ b/app/views/admin/applications/show.html.haml @@ -32,5 +32,5 @@ = render "shared/tokens/scopes_list", token: @application .form-actions - = link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide pull-left' + = link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide float-left' = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10' diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index f0cc4d7ee62..faa5854bb40 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -7,9 +7,9 @@ %hr - .panel.panel-default - .panel-heading Sidekiq running processes - .panel-body + .card + .card-header Sidekiq running processes + .card-body - if @sidekiq_processes.empty? %h4.cred %i.fa.fa-exclamation-triangle @@ -41,5 +41,5 @@ - .panel.panel-default + .card %iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: 0" } diff --git a/app/views/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml index 813ad451b44..7f34357f147 100644 --- a/app/views/admin/broadcast_messages/_form.html.haml +++ b/app/views/admin/broadcast_messages/_form.html.haml @@ -6,32 +6,32 @@ - else Your message here -= form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal js-quick-submit js-requires-input'} do |f| += form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form js-quick-submit js-requires-input'} do |f| = form_errors(@broadcast_message) - .form-group - = f.label :message, class: 'control-label' + .form-group.row + = f.label :message, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_area :message, class: "form-control js-autosize", required: true, data: { preview_path: preview_admin_broadcast_messages_path } - .form-group.js-toggle-colors-container - .col-sm-10.col-sm-offset-2 + .form-group.row.js-toggle-colors-container + .col-sm-10.offset-sm-2 = link_to 'Customize colors', '#', class: 'js-toggle-colors-link' - .form-group.js-toggle-colors-container.hide - = f.label :color, "Background Color", class: 'control-label' + .form-group.row.js-toggle-colors-container.toggle-colors.hide + = f.label :color, "Background Color", class: 'col-form-label col-sm-2' .col-sm-10 = f.color_field :color, class: "form-control" - .form-group.js-toggle-colors-container.hide - = f.label :font, "Font Color", class: 'control-label' + .form-group.row.js-toggle-colors-container.toggle-colors.hide + = f.label :font, "Font Color", class: 'col-form-label col-sm-2' .col-sm-10 = f.color_field :font, class: "form-control" - .form-group - = f.label :starts_at, _("Starts at (UTC)"), class: 'control-label' + .form-group.row + = f.label :starts_at, _("Starts at (UTC)"), class: 'col-form-label col-sm-2' .col-sm-10.datetime-controls = f.datetime_select :starts_at, {}, class: 'form-control form-control-inline' - .form-group - = f.label :ends_at, _("Ends at (UTC)"), class: 'control-label' + .form-group.row + = f.label :ends_at, _("Ends at (UTC)"), class: 'col-form-label col-sm-2' .col-sm-10.datetime-controls = f.datetime_select :ends_at, {}, class: 'form-control form-control-inline' .form-actions diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml index b806882eee3..9ef58faf8cc 100644 --- a/app/views/admin/broadcast_messages/index.html.haml +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -32,7 +32,7 @@ %td = message.ends_at %td - = link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs' - = link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger' + = link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-sm' + = link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-sm btn-danger' = paginate @broadcast_messages, theme: 'gitlab' diff --git a/app/views/admin/conversational_development_index/_card.html.haml b/app/views/admin/conversational_development_index/_card.html.haml index 6c8688e06ae..57eda06630b 100644 --- a/app/views/admin/conversational_development_index/_card.html.haml +++ b/app/views/admin/conversational_development_index/_card.html.haml @@ -3,20 +3,20 @@ .convdev-card-title %h3 = card.title - .text-light + .light-text = card.description - .card-scores - .card-score - .card-score-value + .board-card-scores + .board-card-score + .board-card-score-value = format_score(card.instance_score) - .card-score-name You - .card-score - .card-score-value + .board-card-score-name You + .board-card-score + .board-card-score-value = format_score(card.leader_score) - .card-score-name Lead - .card-score-big + .board-card-score-name Lead + .board-card-score-big = number_to_percentage(card.percentage_score, precision: 1) - .card-buttons + .board-card-buttons - if card.blog %a{ href: card.blog } = icon('info-circle', 'aria-hidden' => 'true') diff --git a/app/views/admin/conversational_development_index/_disabled.html.haml b/app/views/admin/conversational_development_index/_disabled.html.haml index 975d7df3da6..0a741b50960 100644 --- a/app/views/admin/conversational_development_index/_disabled.html.haml +++ b/app/views/admin/conversational_development_index/_disabled.html.haml @@ -1,5 +1,5 @@ .container.convdev-empty - .col-sm-6.col-sm-push-3.text-center + .col-sm-12.justify-content-center.text-center = custom_icon('convdev_no_index') %h4 Usage ping is not enabled %p diff --git a/app/views/admin/conversational_development_index/_no_data.html.haml b/app/views/admin/conversational_development_index/_no_data.html.haml index b23d2b5ec3a..d69c46194b4 100644 --- a/app/views/admin/conversational_development_index/_no_data.html.haml +++ b/app/views/admin/conversational_development_index/_no_data.html.haml @@ -1,5 +1,5 @@ .container.convdev-empty - .col-sm-6.col-sm-push-3.text-center + .col-sm-12.justify-content-center.text-center = custom_icon('convdev_no_data') %h4 Data is still calculating... %p diff --git a/app/views/admin/conversational_development_index/show.html.haml b/app/views/admin/conversational_development_index/show.html.haml index ed40e7b4d00..e3d1aa31dc2 100644 --- a/app/views/admin/conversational_development_index/show.html.haml +++ b/app/views/admin/conversational_development_index/show.html.haml @@ -21,11 +21,11 @@ score = link_to icon('question-circle', 'aria-hidden' => 'true'), help_page_path('user/admin_area/monitoring/convdev') - .convdev-cards.card-container + .convdev-cards.board-card-container - @metric.cards.each do |card| = render 'card', card: card - .convdev-steps.visible-lg + .convdev-steps.d-none.d-lg-block.d-xl-block - @metric.idea_to_production_steps.each_with_index do |step, index| .convdev-step{ class: "convdev-#{score_level(step.percentage_score)}-score" } = custom_icon("i2p_step_#{index + 1}") diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 41ef646fc0e..3df4ce93fa8 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -2,6 +2,8 @@ - breadcrumb_title "Dashboard" %div{ class: container_class } + = render_if_exists "admin/licenses/breakdown", license: @license + .admin-dashboard.prepend-top-default .row .col-sm-4 @@ -20,6 +22,7 @@ %h3.text-center Users: = approximate_count_with_delimiters(User) + = render_if_exists 'users_statistics' %hr = link_to 'New user', new_admin_user_path, class: "btn btn-new" .col-sm-4 @@ -38,35 +41,35 @@ %h4 Statistics %p Forks - %span.light.pull-right + %span.light.float-right = approximate_count_with_delimiters(ForkedProjectLink) %p Issues - %span.light.pull-right + %span.light.float-right = approximate_count_with_delimiters(Issue) %p Merge Requests - %span.light.pull-right + %span.light.float-right = approximate_count_with_delimiters(MergeRequest) %p Notes - %span.light.pull-right + %span.light.float-right = approximate_count_with_delimiters(Note) %p Snippets - %span.light.pull-right + %span.light.float-right = approximate_count_with_delimiters(Snippet) %p SSH Keys - %span.light.pull-right + %span.light.float-right = approximate_count_with_delimiters(Key) %p Milestones - %span.light.pull-right + %span.light.float-right = approximate_count_with_delimiters(Milestone) %p Active Users - %span.light.pull-right + %span.light.float-right = number_with_delimiter(User.active.count) .col-md-4 .info-well @@ -75,44 +78,47 @@ - sign_up = "Sign up" %p{ "aria-label" => "#{sign_up}: status " + (allow_signup? ? "on" : "off") } = sign_up - %span.light.pull-right + %span.light.float-right = boolean_to_icon allow_signup? - ldap = "LDAP" %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") } = ldap - %span.light.pull-right + %span.light.float-right = boolean_to_icon Gitlab.config.ldap.enabled - gravatar = "Gravatar" %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") } = gravatar - %span.light.pull-right + %span.light.float-right = boolean_to_icon gravatar_enabled? - omniauth = "OmniAuth" %p{ "aria-label" => "#{omniauth}: status " + (Gitlab.config.omniauth.enabled ? "on" : "off") } = omniauth - %span.light.pull-right + %span.light.float-right = boolean_to_icon Gitlab.config.omniauth.enabled - reply_email = "Reply by email" %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") } = reply_email - %span.light.pull-right + %span.light.float-right = boolean_to_icon Gitlab::IncomingEmail.enabled? + + = render_if_exists 'elastic_and_geo' + - container_reg = "Container Registry" %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") } = container_reg - %span.light.pull-right + %span.light.float-right = boolean_to_icon Gitlab.config.registry.enabled - gitlab_pages = 'GitLab Pages' - gitlab_pages_enabled = Gitlab.config.pages.enabled %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") } = gitlab_pages - %span.light.pull-right + %span.light.float-right = boolean_to_icon gitlab_pages_enabled - gitlab_shared_runners = 'Shared Runners' - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") } = gitlab_shared_runners - %span.light.pull-right + %span.light.float-right = boolean_to_icon gitlab_shared_runners_enabled .col-md-4 .info-well @@ -120,41 +126,44 @@ %h4 Components - if Gitlab::CurrentSettings.version_check_enabled - .pull-right + .float-right = version_status_badge %p GitLab - %span.pull-right + %span.float-right = Gitlab::VERSION - = "(#{Gitlab::REVISION})" + = "(#{Gitlab.revision})" %p GitLab Shell - %span.pull-right + %span.float-right = Gitlab::Shell.new.version %p GitLab Workhorse - %span.pull-right + %span.float-right = gitlab_workhorse_version %p GitLab API - %span.pull-right + %span.float-right = API::API::version - if Gitlab.config.pages.enabled %p GitLab Pages - %span.pull-right + %span.float-right = Gitlab::Pages::VERSION + + = render_if_exists 'geo' + %p Ruby - %span.pull-right + %span.float-right #{RUBY_VERSION}p#{RUBY_PATCHLEVEL} %p Rails - %span.pull-right + %span.float-right #{Rails::VERSION::STRING} %p = Gitlab::Database.adapter_name - %span.pull-right + %span.float-right = Gitlab::Database.version %p = link_to "Gitaly Servers", admin_gitaly_servers_path @@ -166,7 +175,7 @@ - @projects.each do |project| %p = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60' - %span.light.pull-right + %span.light.float-right #{time_ago_with_tooltip(project.created_at)} .col-md-4 .info-well @@ -176,7 +185,7 @@ %p = link_to [:admin, user], class: 'str-truncated-60' do = user.name - %span.light.pull-right + %span.light.float-right #{time_ago_with_tooltip(user.created_at)} .col-md-4 .info-well @@ -186,5 +195,5 @@ %p = link_to [:admin, group], class: 'str-truncated-60' do = group.full_name - %span.light.pull-right + %span.light.float-right #{time_ago_with_tooltip(group.created_at)} diff --git a/app/views/admin/deploy_keys/edit.html.haml b/app/views/admin/deploy_keys/edit.html.haml index 3a59282e578..b50adef362f 100644 --- a/app/views/admin/deploy_keys/edit.html.haml +++ b/app/views/admin/deploy_keys/edit.html.haml @@ -3,7 +3,7 @@ %hr %div - = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f| + = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f| = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } .form-actions = f.submit 'Save changes', class: 'btn-save btn' diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index 1420163fd5a..52ab8bae119 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -2,7 +2,7 @@ %h3.page-title.deploy-keys-title Public deploy keys (#{@deploy_keys.count}) - .pull-right + .float-right = link_to 'New deploy key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted' - if @deploy_keys.any? @@ -29,6 +29,6 @@ %span.cgray added #{time_ago_with_tooltip(deploy_key.created_at)} %td - .pull-right + .float-right = link_to 'Edit', edit_admin_deploy_key_path(deploy_key), class: 'btn btn-sm' = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove delete-key' diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml index 13f5259698f..d4f8e340b69 100644 --- a/app/views/admin/deploy_keys/new.html.haml +++ b/app/views/admin/deploy_keys/new.html.haml @@ -3,7 +3,7 @@ %hr %div - = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f| + = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f| = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } .form-actions = f.submit 'Create', class: 'btn-create btn' diff --git a/app/views/admin/gitaly_servers/index.html.haml b/app/views/admin/gitaly_servers/index.html.haml index 231f94dc95d..d0cf5761726 100644 --- a/app/views/admin/gitaly_servers/index.html.haml +++ b/app/views/admin/gitaly_servers/index.html.haml @@ -6,7 +6,7 @@ - if @gitaly_servers.any? .table-holder %table.table.responsive-table - %thead.hidden-sm.hidden-xs + %thead.d-none.d-sm-none.d-md-block %tr %th= _("Storage") %th= n_("Gitaly|Address") diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index d9f05003904..dc4dccc9e0d 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -1,23 +1,23 @@ -= form_for [:admin, @group], html: { class: "form-horizontal" } do |f| += form_for [:admin, @group] do |f| = form_errors(@group) = render 'shared/group_form', f: f - .form-group.group-description-holder - = f.label :avatar, "Group avatar", class: 'control-label' + .form-group.row.group-description-holder + = f.label :avatar, "Group avatar", class: 'col-form-label col-sm-2' .col-sm-10 = render 'shared/choose_group_avatar_button', f: f = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 = render 'shared/allow_request_access', form: f = render 'groups/group_admin_settings', f: f - if @group.new_record? - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 .alert.alert-info = render 'shared/group_tips' .form-actions diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index 47cc2d4d27e..e7c70a6f187 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -5,7 +5,7 @@ = link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn' = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove' .stats - %span.badge + %span.badge.badge-pill = storage_counter(group.storage_size) %span @@ -20,7 +20,7 @@ = visibility_level_icon(group.visibility_level, fw: false) .avatar-container.s40 - = group_icon(group, class: "avatar s40 hidden-xs") + = group_icon(group, class: "avatar s40 d-none d-sm-block") .title = link_to [:admin, group], class: 'group-name' do = group.full_name diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 324f3c0a22f..5ec612d0c72 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -4,14 +4,14 @@ %h3.page-title Group: #{@group.full_name} - = link_to admin_group_edit_path(@group), class: "btn pull-right" do + = link_to admin_group_edit_path(@group), class: "btn float-right" do %i.fa.fa-pencil-square-o Edit %hr .row .col-md-6 - .panel.panel-default - .panel-heading + .card + .card-header Group info: %ul.well-list %li @@ -58,46 +58,46 @@ = group_lfs_status(@group) = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - .panel.panel-default - .panel-heading - %h3.panel-title + .card + .card-header + %h3.card-title Projects - %span.badge + %span.badge.badge-pill #{@group.projects.count} %ul.well-list - @projects.each do |project| %li %strong = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project] - %span.badge + %span.badge.badge-pill = storage_counter(project.statistics.storage_size) - %span.pull-right.light + %span.float-right.light %span.monospace= project.full_path + '.git' - .panel-footer + .card-footer = paginate @projects, param_name: 'projects_page', theme: 'gitlab' - if @group.shared_projects.any? - .panel.panel-default - .panel-heading + .card + .card-header Projects shared with #{@group.name} - %span.badge + %span.badge.badge-pill #{@group.shared_projects.count} %ul.well-list - @group.shared_projects.sort_by(&:name).each do |project| %li %strong = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project] - %span.badge + %span.badge.badge-pill = storage_counter(project.statistics.storage_size) - %span.pull-right.light + %span.float-right.light %span.monospace= project.full_path + '.git' .col-md-6 - if can?(current_user, :admin_group_member, @group) - .panel.panel-default - .panel-heading + .card + .card-header Add user(s) to the group: - .panel-body.form-holder + .card-body.form-holder %p.light Read more about project permissions %strong= link_to "here", help_page_path("user/permissions"), class: "vlink" @@ -111,14 +111,14 @@ = button_tag 'Add users to group', class: "btn btn-create" = render 'shared/members/requests', membership_source: @group, requesters: @requesters, force_mobile_view: true - .panel.panel-default - .panel-heading + .card + .card-header %strong= @group.name group members - %span.badge= @group.members.size - .pull-right - = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-xs" + %span.badge.badge-pill= @group.members.size + .float-right + = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@group, :members]), class: "btn btn-sm" %ul.well-list.group-users-list.content-list.members-list = render partial: 'shared/members/member', collection: @members, as: :member, locals: { show_controls: false } - .panel-footer + .card-footer = paginate @members, param_name: 'members_page', theme: 'gitlab' diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index e31fb58b205..d51ac854b04 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -5,7 +5,7 @@ %div{ class: container_class } %h3.page-title= page_title .bs-callout.clearfix - .pull-left + .float-left %p #{ s_('HealthCheck|Access token is') } %code#health-check-token= Gitlab::CurrentSettings.health_check_access_token @@ -25,8 +25,8 @@ %code= metrics_url(token: Gitlab::CurrentSettings.health_check_access_token) %hr - .panel.panel-default - .panel-heading + .card + .card-header Current Status: - if no_errors = icon('circle', class: 'cgreen') @@ -34,7 +34,7 @@ - else = icon('warning', class: 'cred') #{ s_('HealthCheck|Unhealthy') } - .panel-body + .card-body - if no_errors #{ s_('HealthCheck|No Health Problems Detected') } - else diff --git a/app/views/admin/hook_logs/_index.html.haml b/app/views/admin/hook_logs/_index.html.haml index 91a8c0c62fe..1d7c9930b6a 100644 --- a/app/views/admin/hook_logs/_index.html.haml +++ b/app/views/admin/hook_logs/_index.html.haml @@ -18,8 +18,8 @@ %tr %td = render partial: 'shared/hook_logs/status_label', locals: { hook_log: hook_log } - %td.hidden-xs - %span.label.label-gray.deploy-project-label + %td.d-none.d-sm-block + %span.badge.badge-gray.deploy-project-label = hook_log.trigger.singularize.titleize %td = truncate(hook_log.url, length: 50) diff --git a/app/views/admin/hook_logs/show.html.haml b/app/views/admin/hook_logs/show.html.haml index 56127bacda2..2eb3ac85722 100644 --- a/app/views/admin/hook_logs/show.html.haml +++ b/app/views/admin/hook_logs/show.html.haml @@ -4,7 +4,7 @@ %hr -= link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), class: "btn btn-default pull-right prepend-left-10" += link_to 'Resend Request', retry_admin_hook_hook_log_path(@hook, @hook_log), class: "btn btn-default float-right prepend-left-10" = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log } diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml index a6324a97fd5..e54dbd20ef4 100644 --- a/app/views/admin/hooks/_form.html.haml +++ b/app/views/admin/hooks/_form.html.haml @@ -6,39 +6,39 @@ .form-group = form.label :token, 'Secret Token', class: 'label-light' = form.text_field :token, class: 'form-control' - %p.help-block + %p.form-text.text-muted Use this token to validate received payloads .form-group = form.label :url, 'Trigger', class: 'label-light' %ul.list-unstyled %li - .help-block + .form-text.text-muted System hook will be triggered on set of events like creating project or adding ssh key. But you can also enable extra triggers like Push events. .prepend-top-default - = form.check_box :repository_update_events, class: 'pull-left' + = form.check_box :repository_update_events, class: 'float-left' .prepend-left-20 = form.label :repository_update_events, class: 'list-label' do %strong Repository update events %p.light This URL will be triggered when repository is updated %li - = form.check_box :push_events, class: 'pull-left' + = form.check_box :push_events, class: 'float-left' .prepend-left-20 = form.label :push_events, class: 'list-label' do %strong Push events %p.light This URL will be triggered for each branch updated to the repository %li - = form.check_box :tag_push_events, class: 'pull-left' + = form.check_box :tag_push_events, class: 'float-left' .prepend-left-20 = form.label :tag_push_events, class: 'list-label' do %strong Tag push events %p.light This URL will be triggered when a new tag is pushed to the repository %li - = form.check_box :merge_requests_events, class: 'pull-left' + = form.check_box :merge_requests_events, class: 'float-left' .prepend-left-20 = form.label :merge_requests_events, class: 'list-label' do %strong Merge request events @@ -46,7 +46,7 @@ This URL will be triggered when a merge request is created/updated/merged .form-group = form.label :enable_ssl_verification, 'SSL verification', class: 'label-light checkbox' - .checkbox + .form-check = form.label :enable_ssl_verification do = form.check_box :enable_ssl_verification %strong Enable SSL verification diff --git a/app/views/admin/hooks/edit.html.haml b/app/views/admin/hooks/edit.html.haml index 629b1a9940f..b9a650e1f1f 100644 --- a/app/views/admin/hooks/edit.html.haml +++ b/app/views/admin/hooks/edit.html.haml @@ -9,12 +9,12 @@ %hr -= form_for @hook, as: :hook, url: admin_hook_path, html: { class: 'form-horizontal' } do |f| += form_for @hook, as: :hook, url: admin_hook_path do |f| = render partial: 'form', locals: { form: f, hook: @hook } .form-actions = f.submit 'Save changes', class: 'btn btn-create' = render 'shared/web_hooks/test_button', triggers: SystemHook.triggers, hook: @hook - = link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' } + = link_to 'Remove', admin_hook_path(@hook), method: :delete, class: 'btn btn-remove float-right', data: { confirm: 'Are you sure?' } %hr diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index d9e2ce5e74c..87f9b0e86a7 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -15,8 +15,8 @@ %hr - if @hooks.any? - .panel.panel-default - .panel-heading + .card + .card-header System hooks (#{@hooks.count}) %ul.content-list - @hooks.each do |hook| @@ -29,7 +29,7 @@ %div - SystemHook.triggers.each_value do |event| - if hook.public_send(event) - %span.label.label-gray= event.to_s.titleize - %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'} + %span.badge.badge-gray= event.to_s.titleize + %span.badge.badge-gray SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'} = render 'shared/plugins/index' diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml index 5381b854f5c..231c0f70882 100644 --- a/app/views/admin/identities/_form.html.haml +++ b/app/views/admin/identities/_form.html.haml @@ -1,13 +1,13 @@ -= form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f| += form_for [:admin, @user, @identity], html: { class: 'fieldset-form' } do |f| = form_errors(@identity) - .form-group - = f.label :provider, class: 'control-label' + .form-group.row + = f.label :provider, class: 'col-form-label col-sm-2' .col-sm-10 - values = Gitlab::Auth::OAuth::Provider.providers.map { |name| ["#{Gitlab::Auth::OAuth::Provider.label_for(name)} (#{name})", name] } = f.select :provider, values, { allow_blank: false }, class: 'form-control' - .form-group - = f.label :extern_uid, "Identifier", class: 'control-label' + .form-group.row + = f.label :extern_uid, "Identifier", class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :extern_uid, class: 'form-control', required: true diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml index ef5a3f1d969..50fe9478a78 100644 --- a/app/views/admin/identities/_identity.html.haml +++ b/app/views/admin/identities/_identity.html.haml @@ -4,9 +4,9 @@ %td = identity.extern_uid %td - = link_to edit_admin_user_identity_path(@user, identity), class: 'btn btn-xs btn-grouped' do + = link_to edit_admin_user_identity_path(@user, identity), class: 'btn btn-sm btn-grouped' do Edit = link_to [:admin, @user, identity], method: :delete, - class: 'btn btn-xs btn-danger', + class: 'btn btn-sm btn-danger', data: { confirm: "Are you sure you want to remove this identity?" } do Delete diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml index ff67e59cdac..ee51fb3fda1 100644 --- a/app/views/admin/identities/index.html.haml +++ b/app/views/admin/identities/index.html.haml @@ -1,7 +1,7 @@ - page_title "Identities", @user.name, "Users" = render 'admin/users/head' -= link_to 'New identity', new_admin_user_identity_path, class: 'pull-right btn btn-new' += link_to 'New identity', new_admin_user_identity_path, class: 'float-right btn btn-new' - if @identities.present? .table-holder %table.table diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml index d5e6bede36a..ee51b44e83e 100644 --- a/app/views/admin/labels/_form.html.haml +++ b/app/views/admin/labels/_form.html.haml @@ -1,21 +1,22 @@ -= form_for [:admin, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f| += form_for [:admin, @label], html: { class: 'label-form js-requires-input' } do |f| = form_errors(@label) - .form-group - = f.label :title, class: 'control-label' + .form-group.row + = f.label :title, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :title, class: "form-control", required: true - .form-group - = f.label :description, class: 'control-label' + .form-group.row + = f.label :description, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :description, class: "form-control js-quick-submit" - .form-group - = f.label :color, "Background color", class: 'control-label' + .form-group.row + = f.label :color, "Background color", class: 'col-form-label col-sm-2' .col-sm-10 .input-group - .input-group-addon.label-color-preview + .input-group-prepend + .input-group-text.label-color-preview = f.text_field :color, class: "form-control" - .help-block + .form-text.text-muted Choose any color. %br Or you can choose one of suggested colors below diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml index 77b174fbb27..009a47dd517 100644 --- a/app/views/admin/labels/_label.html.haml +++ b/app/views/admin/labels/_label.html.haml @@ -2,6 +2,6 @@ .label-row = render_colored_label(label, tooltip: false) = markdown_field(label, :description) - .pull-right + .float-right = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm' = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"} diff --git a/app/views/admin/labels/destroy.js.haml b/app/views/admin/labels/destroy.js.haml index 9d51762890f..7a0dcbdd1c6 100644 --- a/app/views/admin/labels/destroy.js.haml +++ b/app/views/admin/labels/destroy.js.haml @@ -1,2 +1,2 @@ - if @labels.size == 0 - $('.labels').load(document.URL + ' .light-well').hide().fadeIn(1000) + $('.labels').load(document.URL + ' .card.bg-light').hide().fadeIn(1000) diff --git a/app/views/admin/labels/index.html.haml b/app/views/admin/labels/index.html.haml index 05d6b9ed238..add38fb333e 100644 --- a/app/views/admin/labels/index.html.haml +++ b/app/views/admin/labels/index.html.haml @@ -1,7 +1,7 @@ - page_title "Labels" %div - = link_to new_admin_label_path, class: "pull-right btn btn-nr btn-new" do + = link_to new_admin_label_path, class: "float-right btn btn-nr btn-new" do New label %h3.page-title Labels @@ -13,6 +13,6 @@ = render @labels = paginate @labels, theme: 'gitlab' - else - .light-well + .card.bg-light .nothing-here-block There are no labels yet diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index 78757b6384f..a6c436cd1f4 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -2,7 +2,7 @@ - page_title "Logs" %div{ class: container_class } - %ul.nav-links.log-tabs + %ul.nav-links.log-tabs.nav.nav-tabs - @loggers.each do |klass| %li{ class: active_when(klass == @loggers.first) }> = link_to klass.file_name, "##{klass.file_name_noext}", data: { toggle: 'tab' } @@ -15,7 +15,7 @@ .js-file-title.file-title %i.fa.fa-file = klass.file_name - .pull-right + .float-right = link_to '#', class: 'log-bottom' do %i.fa.fa-arrow-down Scroll down diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml index b5d7b889504..00933d726d9 100644 --- a/app/views/admin/projects/_projects.html.haml +++ b/app/views/admin/projects/_projects.html.haml @@ -12,10 +12,10 @@ = s_('AdminProjects|Delete') .stats - %span.badge + %span.badge.badge-pill = storage_counter(project.statistics.storage_size) - if project.archived - %span.label.label-warning archived + %span.badge.badge-warning archived .title = link_to [:admin, project.namespace.becomes(Namespace), project] do .dash-project-avatar diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index c37d8ac45b9..57de792f92d 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -15,7 +15,7 @@ - namespace = Namespace.find(params[:namespace_id]) - toggle_text = "#{namespace.kind}: #{namespace.full_path}" = dropdown_toggle(toggle_text, { toggle: 'dropdown', is_filter: 'true' }, { toggle_class: 'js-namespace-select large' }) - .dropdown-menu.dropdown-select.dropdown-menu-align-right + .dropdown-menu.dropdown-select.dropdown-menu-right = dropdown_title('Namespaces') = dropdown_filter("Search for Namespace") = dropdown_content @@ -25,7 +25,7 @@ New Project = button_tag "Search", class: "btn btn-primary btn-search hide" - %ul.nav-links + %ul.nav-links.nav.nav-tabs - opts = params[:visibility_level].present? ? {} : { page: admin_projects_path } = nav_link(opts) do = link_to admin_projects_path do diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 75ca5106fd5..29ec712b6b7 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -3,15 +3,15 @@ - page_title @project.full_name, "Projects" %h3.page-title Project: #{@project.full_name} - = link_to edit_project_path(@project), class: "btn btn-nr pull-right" do + = link_to edit_project_path(@project), class: "btn btn-nr float-right" do %i.fa.fa-pencil-square-o Edit %hr - if @project.last_repository_check_failed? .row .col-md-12 - .panel - .panel-heading.alert.alert-danger + .card + .card-header.alert.alert-danger Last repository check = "(#{time_ago_with_tooltip(@project.last_repository_check_at)})" failed. See @@ -19,8 +19,8 @@ for error messages. .row .col-md-6 - .panel.panel-default - .panel-heading + .card + .card-header Project info: %ul.well-list %li @@ -110,13 +110,13 @@ = visibility_level_icon(@project.visibility_level) = visibility_level_label(@project.visibility_level) - .panel.panel-default - .panel-heading + .card + .card-header Transfer project - .panel-body - = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f| - .form-group - = f.label :new_namespace_id, "Namespace", class: 'control-label' + .card-body + = form_for @project, url: transfer_admin_project_path(@project), method: :put do |f| + .form-group.row + = f.label :new_namespace_id, "Namespace", class: 'col-form-label col-sm-2' .col-sm-10 .dropdown = dropdown_toggle('Search for Namespace', { toggle: 'dropdown', field_name: 'new_namespace_id' }, { toggle_class: 'js-namespace-select large' }) @@ -126,14 +126,14 @@ = dropdown_content = dropdown_loading - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 = f.submit 'Transfer', class: 'btn btn-primary' - .panel.panel-default.repository-check - .panel-heading + .card.repository-check + .card-header Repository check - .panel-body + .card-body = form_for @project, url: repository_check_admin_project_path(@project), method: :post do |f| .form-group - if @project.last_repository_check_at.nil? @@ -158,29 +158,29 @@ .col-md-6 - if @group - .panel.panel-default - .panel-heading + .card + .card-header %strong= @group.name group members - %span.badge= @group_members.size - .pull-right - = link_to admin_group_path(@group), class: 'btn btn-xs' do + %span.badge.badge-pill= @group_members.size + .float-right + = link_to admin_group_path(@group), class: 'btn btn-sm' do = icon('pencil-square-o', text: 'Manage access') %ul.well-list.content-list.members-list = render partial: 'shared/members/member', collection: @group_members, as: :member, locals: { show_controls: false } - .panel-footer + .card-footer = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' = render 'shared/members/requests', membership_source: @project, requesters: @requesters, force_mobile_view: true - .panel.panel-default - .panel-heading + .card + .card-header %strong= @project.name project members - %span.badge= @project.users.size - .pull-right - = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-xs" + %span.badge.badge-pill= @project.users.size + .float-right + = link_to icon('pencil-square-o', text: 'Manage access'), polymorphic_url([@project, :members]), class: "btn btn-sm" %ul.well-list.project_members.content-list.members-list = render partial: 'shared/members/member', collection: @project_members, as: :member, locals: { show_controls: false } - .panel-footer + .card-footer = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml index cb02a750490..adfc67d66d0 100644 --- a/app/views/admin/requests_profiles/index.html.haml +++ b/app/views/admin/requests_profiles/index.html.haml @@ -13,8 +13,8 @@ - if @profiles.present? .prepend-top-default - @profiles.each do |path, profiles| - .panel.panel-default.panel-small - .panel-heading + .card.card-small + .card-header %code= path %ul.content-list - profiles.each do |profile| diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 99fbbaec487..a6cd39edcf0 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -1,15 +1,15 @@ %tr{ id: dom_id(runner) } %td - if runner.shared? - %span.label.label-success shared + %span.badge.badge-success shared - elsif runner.group_type? - %span.label.label-success group + %span.badge.badge-success group - else - %span.label.label-info specific + %span.badge.badge-info specific - if runner.locked? - %span.label.label-warning locked + %span.badge.badge-warning locked - unless runner.active? - %span.label.label-danger paused + %span.badge.badge-danger paused %td = link_to admin_runner_path(runner) do @@ -29,7 +29,7 @@ #{runner.builds.count(:all)} %td - runner.tag_list.sort.each do |tag| - %span.label.label-primary + %span.badge.badge-primary = tag %td - if runner.contacted_at @@ -37,7 +37,7 @@ - else Never %td.admin-runner-btn-group-cell - .pull-right.btn-group + .float-right.btn-group = link_to admin_runner_path(runner), class: 'btn btn-sm btn-default has-tooltip', title: 'Edit', ref: 'tooltip', aria: { label: 'Edit' }, data: { placement: 'top', container: 'body'} do = icon('pencil') diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 1a3b5e58ed5..f38aeb151df 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -14,23 +14,23 @@ %span Each Runner can be in one of the following states: %ul %li - %span.label.label-success shared + %span.badge.badge-success shared \- Runner runs jobs from all unassigned projects %li - %span.label.label-success group + %span.badge.badge-success group \- Runner runs jobs from all unassigned projects in its group %li - %span.label.label-info specific + %span.badge.badge-info specific \- Runner runs jobs from assigned projects %li - %span.label.label-warning locked + %span.badge.badge-warning locked \- Runner cannot be assigned to other projects %li - %span.label.label-danger paused + %span.badge.badge-danger paused \- Runner will not receive any new jobs .bs-callout.clearfix - .pull-left + .float-left %p You can reset runners registration token by pressing a button below. .prepend-top-10 @@ -42,13 +42,13 @@ locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token } .append-bottom-20.clearfix - .pull-left + .float-left = form_tag admin_runners_path, id: 'runners-search', class: 'form-inline', method: :get do .form-group - = search_field_tag :search, params[:search], class: 'form-control', placeholder: 'Runner description or token', spellcheck: false + = search_field_tag :search, params[:search], class: 'form-control input-short', placeholder: 'Runner description or token', spellcheck: false = submit_tag 'Search', class: 'btn' - .pull-right.light + .float-right.light Runners with last contact more than a minute ago: #{@active_runners_cnt} %br diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 73fadc042b1..8a0c2bf4c5f 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -1,7 +1,7 @@ = content_for :title do %h3.project-title Runner ##{@runner.id} - .pull-right + .float-right - if @runner.shared? %span.runner-state.runner-state-shared Shared @@ -48,8 +48,8 @@ %strong = project.full_name %td - .pull-right - = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs' + .float-right + = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-sm' %table.table.unassigned-projects %thead @@ -70,10 +70,10 @@ %td = project.full_name %td - .pull-right + .float-right = form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f| = f.hidden_field :runner_id, value: @runner.id - = f.submit 'Enable', class: 'btn btn-xs' + = f.submit 'Enable', class: 'btn btn-sm' = paginate @projects, theme: "gitlab" .col-md-6 diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml index e5b8ebdf613..144dceacbdd 100644 --- a/app/views/admin/services/_form.html.haml +++ b/app/views/admin/services/_form.html.haml @@ -3,7 +3,7 @@ %p #{@service.description} template -= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |form| += form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'fieldset-form' } do |form| = render 'shared/service_settings', form: form, subject: @service .footer-block.row-content-block diff --git a/app/views/admin/spam_logs/_spam_log.html.haml b/app/views/admin/spam_logs/_spam_log.html.haml index ea6a0c4fb77..9d47dc1cce5 100644 --- a/app/views/admin/spam_logs/_spam_log.html.haml +++ b/app/views/admin/spam_logs/_spam_log.html.haml @@ -24,16 +24,16 @@ %td - if user = link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true), - data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove" + data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-sm btn-remove" %td - if spam_log.submitted_as_ham? - .btn.btn-xs.disabled + .btn.btn-sm.disabled Submitted as ham - else - = link_to 'Submit as ham', mark_as_ham_admin_spam_log_path(spam_log), method: :post, class: 'btn btn-xs btn-warning' + = link_to 'Submit as ham', mark_as_ham_admin_spam_log_path(spam_log), method: :post, class: 'btn btn-sm btn-warning' - if user && !user.blocked? - = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs" + = link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm" - else - .btn.btn-xs.disabled + .btn.btn-sm.disabled Already blocked - = link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr" + = link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-sm btn-close js-remove-tr" diff --git a/app/views/admin/system_info/show.html.haml b/app/views/admin/system_info/show.html.haml index 23f9927cfee..b19934e028d 100644 --- a/app/views/admin/system_info/show.html.haml +++ b/app/views/admin/system_info/show.html.haml @@ -5,7 +5,7 @@ .prepend-top-default .row .col-sm-4 - .light-well + .card.bg-light.light-well %h4 CPU .data - if @cpus @@ -14,7 +14,7 @@ = icon('warning', class: 'text-warning') Unable to collect CPU info .col-sm-4 - .light-well + .card.bg-light.light-well %h4 Memory Usage .data - if @memory @@ -23,7 +23,7 @@ = icon('warning', class: 'text-warning') Unable to collect memory info .col-sm-4 - .light-well + .card.bg-light.light-well %h4 Disk Usage .data - @disks.each do |disk| @@ -31,7 +31,7 @@ %p= disk[:disk_name] %p= disk[:mount_path] .col-sm-4 - .light-well + .card.bg-light.light-well %h4 Uptime .data %h1= distance_of_time_in_words_to_now(Rails.application.config.booted_at) diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml index 794aaec89bd..35a331283ab 100644 --- a/app/views/admin/users/_access_levels.html.haml +++ b/app/views/admin/users/_access_levels.html.haml @@ -1,15 +1,15 @@ %fieldset %legend Access .form-group - = f.label :projects_limit, class: 'control-label' + = f.label :projects_limit, class: 'col-form-label' .col-sm-10= f.number_field :projects_limit, min: 0, max: Gitlab::Database::MAX_INT_VALUE, class: 'form-control' .form-group - = f.label :can_create_group, class: 'control-label' + = f.label :can_create_group, class: 'col-form-label' .col-sm-10= f.check_box :can_create_group .form-group - = f.label :access_level, class: 'control-label' + = f.label :access_level, class: 'col-form-label' .col-sm-10 - editing_current_user = (current_user == @user) @@ -29,7 +29,7 @@ You cannot remove your own admin rights. .form-group - = f.label :external, class: 'control-label' + = f.label :external, class: 'col-form-label' .col-sm-10 = f.check_box :external do External diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml index e911af3f6f9..010cb2ac354 100644 --- a/app/views/admin/users/_form.html.haml +++ b/app/views/admin/users/_form.html.haml @@ -1,21 +1,21 @@ .user_new - = form_for [:admin, @user], html: { class: 'form-horizontal fieldset-form' } do |f| + = form_for [:admin, @user], html: { class: 'fieldset-form' } do |f| = form_errors(@user) %fieldset %legend Account - .form-group - = f.label :name, class: 'control-label' + .form-group.row + = f.label :name, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :name, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required - .form-group - = f.label :username, class: 'control-label' + .form-group.row + = f.label :username, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :username, required: true, autocomplete: 'off', autocorrect: 'off', autocapitalize: 'off', spellcheck: false, class: 'form-control' %span.help-inline * required - .form-group - = f.label :email, class: 'control-label' + .form-group.row + = f.label :email, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :email, required: true, autocomplete: 'off', class: 'form-control' %span.help-inline * required @@ -23,8 +23,8 @@ - if @user.new_record? %fieldset %legend Password - .form-group - = f.label :password, class: 'control-label' + .form-group.row + = f.label :password, class: 'col-form-label col-sm-2' .col-sm-10 %strong Reset link will be generated and sent to the user. @@ -33,33 +33,33 @@ - else %fieldset %legend Password - .form-group - = f.label :password, class: 'control-label' + .form-group.row + = f.label :password, class: 'col-form-label col-sm-2' .col-sm-10= f.password_field :password, disabled: f.object.force_random_password, class: 'form-control' - .form-group - = f.label :password_confirmation, class: 'control-label' + .form-group.row + = f.label :password_confirmation, class: 'col-form-label col-sm-2' .col-sm-10= f.password_field :password_confirmation, disabled: f.object.force_random_password, class: 'form-control' = render partial: 'access_levels', locals: { f: f } %fieldset %legend Profile - .form-group - = f.label :avatar, class: 'control-label' + .form-group.row + = f.label :avatar, class: 'col-form-label col-sm-2' .col-sm-10 = f.file_field :avatar - .form-group - = f.label :skype, class: 'control-label' + .form-group.row + = f.label :skype, class: 'col-form-label col-sm-2' .col-sm-10= f.text_field :skype, class: 'form-control' - .form-group - = f.label :linkedin, class: 'control-label' + .form-group.row + = f.label :linkedin, class: 'col-form-label col-sm-2' .col-sm-10= f.text_field :linkedin, class: 'form-control' - .form-group - = f.label :twitter, class: 'control-label' + .form-group.row + = f.label :twitter, class: 'col-form-label' .col-sm-10= f.text_field :twitter, class: 'form-control' - .form-group - = f.label :website_url, 'Website', class: 'control-label' + .form-group.row + = f.label :website_url, 'Website', class: 'col-form-label col-sm-2' .col-sm-10= f.text_field :website_url, class: 'form-control' .form-actions diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index be41c33b853..bfbc16d37a0 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -7,14 +7,14 @@ - if @user.admin %span.cred (Admin) - .pull-right + .float-right - if @user != current_user && @user.can?(:log_in) = link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-nr btn-grouped btn-info" = link_to edit_admin_user_path(@user), class: "btn btn-nr btn-grouped" do %i.fa.fa-pencil-square-o Edit %hr -%ul.nav-links +%ul.nav-links.nav.nav-tabs = nav_link(path: 'users#show') do = link_to "Account", admin_user_path(@user) = nav_link(path: 'users#projects') do diff --git a/app/views/admin/users/_profile.html.haml b/app/views/admin/users/_profile.html.haml index 6bc217f84cc..af22652e07c 100644 --- a/app/views/admin/users/_profile.html.haml +++ b/app/views/admin/users/_profile.html.haml @@ -1,5 +1,5 @@ -.panel.panel-default - .panel-heading +.card + .card-header Profile %ul.well-list %li diff --git a/app/views/admin/users/_projects.html.haml b/app/views/admin/users/_projects.html.haml index a126a858ea8..81cfb71af16 100644 --- a/app/views/admin/users/_projects.html.haml +++ b/app/views/admin/users/_projects.html.haml @@ -1,13 +1,13 @@ - if local_assigns.has_key?(:contributed_projects) && contributed_projects.present? - .panel.panel-default.contributed-projects - .panel-heading Projects contributed to + .card.contributed-projects + .card-header Projects contributed to = render 'shared/projects/list', projects: contributed_projects.sort_by(&:star_count).reverse, projects_limit: 5, stars: true, avatar: false - if local_assigns.has_key?(:projects) && projects.present? - .panel.panel-default - .panel-heading Personal projects + .card + .card-header Personal projects = render 'shared/projects/list', projects: projects.sort_by(&:star_count).reverse, projects_limit: 10, stars: true, avatar: false diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index badf3dd74b3..5e176b61d68 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -5,11 +5,11 @@ .user-name.row-title.str-truncated-100 = link_to user.name, [:admin, user] - if user.blocked? - %span.label.label-danger blocked + %span.badge.badge-danger blocked - if user.admin? - %span.label.label-success Admin + %span.badge.badge-success Admin - if user.external? - %span.label.label-default External + %span.badge.badge-secondary External - if user == current_user %span It's you! .row-second-line.str-truncated-100 @@ -21,7 +21,7 @@ %a.dropdown-new.btn.btn-default#project-settings-button{ href: '#', data: { toggle: 'dropdown' } } = icon('cog') = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right + %ul.dropdown-menu.dropdown-menu-right %li.dropdown-header Settings %li @@ -38,7 +38,7 @@ %li.divider - if user.can_be_removed? %li - %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal', + %button.delete-user-button.btn.btn-danger{ data: { toggle: 'modal', target: '#delete-user-modal', delete_user_url: admin_user_path(user), block_user_url: block_admin_user_path(user), @@ -47,7 +47,7 @@ = s_('AdminUsers|Delete user') %li - %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal', + %button.delete-user-button.btn.btn-danger{ data: { toggle: 'modal', target: '#delete-user-modal', delete_user_url: admin_user_path(user, hard_delete: true), block_user_url: block_admin_user_path(user), diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index 10b8bf5d565..faeb82656ba 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -13,7 +13,7 @@ .dropdown - toggle_text = if @sort.present? then sort_options_hash[@sort] else sort_title_name end = dropdown_toggle(toggle_text, { toggle: 'dropdown' }) - %ul.dropdown-menu.dropdown-menu-align-right + %ul.dropdown-menu.dropdown-menu-right %li.dropdown-header Sort by %li @@ -38,35 +38,35 @@ = icon('angle-left') .fade-right = icon('angle-right') - %ul.nav-links.scrolling-tabs + %ul.nav-links.nav.nav-tabs.scrolling-tabs = nav_link(html_options: { class: active_when(params[:filter].nil?) }) do = link_to admin_users_path do Active - %small.badge= limited_counter_with_delimiter(User.active) + %small.badge.badge-pill= limited_counter_with_delimiter(User.active) = nav_link(html_options: { class: active_when(params[:filter] == 'admins') }) do = link_to admin_users_path(filter: "admins") do Admins - %small.badge= limited_counter_with_delimiter(User.admins) + %small.badge.badge-pill= limited_counter_with_delimiter(User.admins) = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_enabled')} filter-two-factor-enabled" }) do = link_to admin_users_path(filter: 'two_factor_enabled') do 2FA Enabled - %small.badge= limited_counter_with_delimiter(User.with_two_factor) + %small.badge.badge-pill= limited_counter_with_delimiter(User.with_two_factor) = nav_link(html_options: { class: "#{active_when(params[:filter] == 'two_factor_disabled')} filter-two-factor-disabled" }) do = link_to admin_users_path(filter: 'two_factor_disabled') do 2FA Disabled - %small.badge= limited_counter_with_delimiter(User.without_two_factor) + %small.badge.badge-pill= limited_counter_with_delimiter(User.without_two_factor) = nav_link(html_options: { class: active_when(params[:filter] == 'external') }) do = link_to admin_users_path(filter: 'external') do External - %small.badge= limited_counter_with_delimiter(User.external) + %small.badge.badge-pill= limited_counter_with_delimiter(User.external) = nav_link(html_options: { class: active_when(params[:filter] == 'blocked') }) do = link_to admin_users_path(filter: "blocked") do Blocked - %small.badge= limited_counter_with_delimiter(User.blocked) + %small.badge.badge-pill= limited_counter_with_delimiter(User.blocked) = nav_link(html_options: { class: active_when(params[:filter] == 'wop') }) do = link_to admin_users_path(filter: "wop") do Without projects - %small.badge= limited_counter_with_delimiter(User.without_projects) + %small.badge.badge-pill= limited_counter_with_delimiter(User.without_projects) %ul.flex-list.content-list - if @users.empty? diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 96835ee9af5..469a7bd9715 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -2,19 +2,19 @@ = render 'admin/users/head' - if @user.groups.any? - .panel.panel-default - .panel-heading Group projects - %ul.well-list + .card + .card-header Group projects + %ul.card-body-list - @user.group_members.includes(:source).each do |group_member| - group = group_member.group %li.group_member %strong= link_to group.name, admin_group_path(group) – access to #{pluralize(group.projects.count, 'project')} - .pull-right + .float-right %span.light.vertical-align-middle= group_member.human_access - unless group_member.owner? - = link_to group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from group' do + = link_to group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member) }, method: :delete, remote: true, class: "btn-sm btn btn-remove prepend-left-10", title: 'Remove user from group' do %i.fa.fa-times.fa-inverse .row @@ -26,9 +26,9 @@ .col-md-6 - .panel.panel-default - .panel-heading Joined projects (#{@joined_projects.count}) - %ul.well-list + .card + .card-header Joined projects (#{@joined_projects.count}) + %ul.card-body-list - @joined_projects.sort_by(&:full_name).each do |project| - member = project.team.find_member(@user.id) %li.project_member @@ -37,12 +37,12 @@ = project.full_name - if member - .pull-right + .float-right - if member.owner? %span.light Owner - else %span.light.vertical-align-middle= member.human_access - if member.respond_to? :project - = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do + = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-sm btn btn-remove prepend-left-10", title: 'Remove user from project' do %i.fa.fa-times diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 814ccdae8f3..a74fcea65d8 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -5,8 +5,8 @@ .row .col-md-6 - .panel.panel-default - .panel-heading + .card + .card-header = @user.name %ul.well-list %li @@ -18,8 +18,8 @@ = @user.username = render 'admin/users/profile', user: @user - .panel.panel-default - .panel-heading + .card + .card-header Account: %ul.well-list %li @@ -37,7 +37,7 @@ %li %span.light Secondary email: %strong= email.email - = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do + = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-sm btn btn-remove float-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do %i.fa.fa-times %li.two-factor-status @@ -45,7 +45,7 @@ %strong{ class: @user.two_factor_enabled? ? 'cgreen' : 'cred' } - if @user.two_factor_enabled? Enabled - = link_to 'Disable', disable_two_factor_admin_user_path(@user), data: {confirm: 'Are you sure?'}, method: :patch, class: 'btn btn-xs btn-remove pull-right', title: 'Disable Two-factor Authentication' + = link_to 'Disable', disable_two_factor_admin_user_path(@user), data: {confirm: 'Are you sure?'}, method: :patch, class: 'btn btn-sm btn-remove float-right', title: 'Disable Two-factor Authentication' - else Disabled @@ -128,20 +128,20 @@ .col-md-6 - unless @user == current_user - unless @user.confirmed? - .panel.panel-info - .panel-heading + .card.bg-info + .card-header Confirm user - .panel-body + .card-body - if @user.unconfirmed_email.present? - email = " (#{@user.unconfirmed_email})" %p This user has an unconfirmed email address#{email}. You may force a confirmation. %br = link_to 'Confirm user', confirm_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - if @user.blocked? - .panel.panel-info - .panel-heading + .card.bg-info + .card-header This user is blocked - .panel-body + .card-body %p A blocked user cannot: %ul %li Log in @@ -149,10 +149,10 @@ %br = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - else - .panel.panel-warning - .panel-heading + .card.bg-warning + .card-header Block this user - .panel-body + .card-body %p Blocking user has the following effects: %ul %li User will not be able to login @@ -162,23 +162,23 @@ %br = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning" - if @user.access_locked? - .panel.panel-info - .panel-heading + .card.bg-info + .card-header This account has been locked - .panel-body + .card-body %p This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account. %br = link_to 'Unlock user', unlock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - .panel.panel-danger - .panel-heading + .card.bg-danger + .card-header = s_('AdminUsers|Delete user') - .panel-body + .card-body - if @user.can_be_removed? && can?(current_user, :destroy_user, @user) %p Deleting a user has the following effects: = render 'users/deletion_guidance', user: @user %br - %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal', + %button.delete-user-button.btn.btn-danger{ data: { toggle: 'modal', target: '#delete-user-modal', delete_user_url: admin_user_path(@user), block_user_url: block_admin_user_path(@user), @@ -196,10 +196,10 @@ %p You don't have access to delete this user. - .panel.panel-danger - .panel-heading + .card.bg-danger + .card-header = s_('AdminUsers|Delete user and contributions') - .panel-body + .card-body - if can?(current_user, :destroy_user, @user) %p This option deletes the user and any contributions that @@ -210,7 +210,7 @@ the user, and projects in them, will also be removed. Commits to other projects are unaffected. %br - %button.delete-user-button.btn.text-danger{ data: { toggle: 'modal', + %button.delete-user-button.btn.btn-danger{ data: { toggle: 'modal', target: '#delete-user-modal', delete_user_url: admin_user_path(@user, hard_delete: true), block_user_url: block_admin_user_path(@user), diff --git a/app/views/ci/lints/show.html.haml b/app/views/ci/lints/show.html.haml index 22f149d1caa..d4455749803 100644 --- a/app/views/ci/lints/show.html.haml +++ b/app/views/ci/lints/show.html.haml @@ -1,8 +1,8 @@ .row.empty-state - .col-xs-12 + .col-12 .svg-content = image_tag 'illustrations/feature_moved.svg' - .col-xs-12 + .col-12 .text-content.text-center %h4= _("GitLab CI Linter has been moved") %p diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml index d828f6f971d..8b0463db000 100644 --- a/app/views/ci/status/_dropdown_graph_badge.html.haml +++ b/app/views/ci/status/_dropdown_graph_badge.html.haml @@ -6,12 +6,12 @@ - tooltip = "#{subject.name} - #{status.status_tooltip}" - if status.has_details? - = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, html: true, container: 'body' } do + = link_to status.details_path, class: 'mini-pipeline-graph-dropdown-item', data: { toggle: 'tooltip', title: tooltip, html: 'true', container: 'body' } do %span{ class: klass }= sprite_icon(status.icon) %span.ci-build-text= subject.name - else - .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', html: true, title: tooltip, container: 'body' } } + .menu-item.mini-pipeline-graph-dropdown-item{ data: { toggle: 'tooltip', html: 'true', title: tooltip, container: 'body' } } %span{ class: klass }= sprite_icon(status.icon) %span.ci-build-text= subject.name diff --git a/app/views/dashboard/_activity_head.html.haml b/app/views/dashboard/_activity_head.html.haml index 7a3f3667ac1..7503548fa3d 100644 --- a/app/views/dashboard/_activity_head.html.haml +++ b/app/views/dashboard/_activity_head.html.haml @@ -1,5 +1,5 @@ .top-area - %ul.nav-links + %ul.nav-links.nav.nav-tabs %li{ class: active_when(params[:filter].nil?) }> = link_to activity_dashboard_path, class: 'shortcuts-activity', data: {placement: 'right'} do Your projects diff --git a/app/views/dashboard/_groups_head.html.haml b/app/views/dashboard/_groups_head.html.haml index 617c20b9635..d8f1e50544c 100644 --- a/app/views/dashboard/_groups_head.html.haml +++ b/app/views/dashboard/_groups_head.html.haml @@ -1,5 +1,5 @@ .top-area - %ul.nav-links.mobile-separator + %ul.nav-links.mobile-separator.nav.nav-tabs = nav_link(page: dashboard_groups_path) do = link_to dashboard_groups_path, title: _("Your groups") do Your groups diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index 449a2ce625e..9b1d9b659f9 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -4,7 +4,7 @@ .top-area.scrolling-tabs-container.inner-page-scroll-tabs .fade-left= icon('angle-left') .fade-right= icon('angle-right') - %ul.nav-links.scrolling-tabs.mobile-separator + %ul.nav-links.scrolling-tabs.mobile-separator.nav.nav-tabs = nav_link(page: [dashboard_projects_path, root_path]) do = link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do Your projects diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index a9488df07bd..e7e323a8683 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,5 +1,5 @@ .top-area - %ul.nav-links + %ul.nav-links.nav.nav-tabs = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do Your snippets @@ -8,5 +8,5 @@ Explore snippets - if current_user - .nav-controls.hidden-xs + .nav-controls.d-none.d-sm-block = link_to "New snippet", new_snippet_path, class: "btn btn-new", title: "New snippet" diff --git a/app/views/dashboard/projects/_nav.html.haml b/app/views/dashboard/projects/_nav.html.haml index 97f854cc5f0..da3cf5807b0 100644 --- a/app/views/dashboard/projects/_nav.html.haml +++ b/app/views/dashboard/projects/_nav.html.haml @@ -1,5 +1,5 @@ .nav-block - %ul.nav-links.mobile-separator + %ul.nav-links.mobile-separator.nav.nav-tabs = nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do = link_to s_('DashboardProjects|All'), dashboard_projects_path = nav_link(html_options: { class: ("active" if params[:personal].present?) }) do diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index b1efe59aadc..8933d9e31ff 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -11,5 +11,5 @@ - if params[:filter_projects] || any_projects?(@projects) = render 'projects' - else - %h3 You don't have starred projects yet + %h3.page-title You don't have starred projects yet %p.slead Visit project page and press on star icon and it will appear on this page. diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index e86b1ab3116..4391624196b 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -5,7 +5,7 @@ = render 'dashboard/snippets_head' = render partial: 'snippets/snippets_scope_menu', locals: { include_private: true } -.visible-xs +.d-block.d-sm-none = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do New snippet diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 664966989db..d5a9cc646a6 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -4,18 +4,18 @@ - if current_user.todos.any? .top-area - %ul.nav-links.mobile-separator + %ul.nav-links.mobile-separator.nav.nav-tabs %li.todos-pending{ class: active_when(params[:state].blank? || params[:state] == 'pending') }> = link_to todos_filter_path(state: 'pending') do %span Todos - %span.badge + %span.badge.badge-pill = number_with_delimiter(todos_pending_count) %li.todos-done{ class: active_when(params[:state] == 'done') }> = link_to todos_filter_path(state: 'done') do %span Done - %span.badge + %span.badge.badge-pill = number_with_delimiter(todos_done_count) .nav-controls @@ -35,7 +35,7 @@ - if params[:project_id].present? = hidden_field_tag(:project_id, params[:project_id]) = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', - placeholder: 'Search projects', data: { data: todo_projects_options, default_label: 'Project' } }) + placeholder: 'Search projects', data: { data: todo_projects_options, default_label: 'Project', display: 'static' } }) .filter-item.inline - if params[:author_id].present? = hidden_field_tag(:author_id, params[:author_id]) @@ -60,7 +60,7 @@ - else = sort_title_recently_created = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-sort + %ul.dropdown-menu.dropdown-menu-sort.dropdown-menu-right %li = link_to todos_filter_path(sort: sort_value_label_priority) do = sort_title_label_priority @@ -73,7 +73,7 @@ - if @todos.any? .js-todos-list-container .js-todos-options{ data: { per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages } } - .panel.panel-default.panel-without-border.panel-without-margin + .card.card-without-border.card-without-margin %ul.content-list.todos-list = render @todos = paginate @todos, theme: "gitlab" diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 41462f503cb..c45d2214592 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -6,11 +6,11 @@ = f.label :password = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." - if devise_mapping.rememberable? - .remember-me.checkbox + .remember-me %label{ for: "user_remember_me" } = f.check_box :remember_me, class: 'remember-me-checkbox' %span Remember me - .pull-right.forgot-password + .float-right.forgot-password = link_to "Forgot your password?", new_password_path(:user) .submit-container.move-submit-down = f.submit "Sign in", class: "btn btn-save" diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index 2556cb6f59b..36ff42090be 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -6,7 +6,7 @@ = label_tag :password = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? - .remember-me.checkbox + .remember-me %label{ for: "remember_me" } = check_box_tag :remember_me, '1', false, id: 'remember_me' %span Remember me diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 3159d21598a..6bf7349f602 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -6,7 +6,7 @@ = label_tag :password = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? - .remember-me.checkbox + .remember-me %label{ for: "remember_me" } = check_box_tag :remember_me, '1', false, id: 'remember_me' %span Remember me diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 6e54b9b5645..ba168c4eab8 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -9,7 +9,7 @@ %div = f.label 'Two-Factor Authentication code', name: :otp_attempt = f.text_field :otp_attempt, class: 'form-control', required: true, autofocus: true, autocomplete: 'off', title: 'This field is required.' - %p.help-block.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. + %p.form-text.text-muted.hint Enter the code from the two-factor app on your mobile device. If you've lost your device, you may enter one of your recovery codes. .prepend-top-20 = f.submit "Verify code", class: "btn btn-save" diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 546cec4d565..3723814debe 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -7,7 +7,7 @@ %span.light - has_icon = provider_has_icon?(provider) = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: 'oauth-login' + (has_icon ? ' oauth-image-link' : ' btn'), id: "oauth-login-#{provider}" - %fieldset.prepend-top-10.checkbox.remember-me + %fieldset.prepend-top-10.remember-me %label = check_box_tag :remember_me, nil, false, class: 'remember-me-checkbox' %span diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml index 7bd414d64c3..5683b4207b4 100644 --- a/app/views/devise/shared/_tab_single.html.haml +++ b/app/views/devise/shared/_tab_single.html.haml @@ -1,3 +1,3 @@ -%ul.nav-links.new-session-tabs.single-tab - %li.active - %a= tab_title +%ul.nav-links.new-session-tabs.single-tab.nav-tabs.nav + %li.nav-item + %a.nav-link.active= tab_title diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index f50e0724e09..087af61235b 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -1,13 +1,13 @@ -%ul.nav-links.new-session-tabs{ class: ('custom-provider-tabs' if form_based_providers.any?) } +%ul.nav-links.new-session-tabs.nav-tabs.nav{ class: ('custom-provider-tabs' if form_based_providers.any?) } - if crowd_enabled? - %li.active - = link_to "Crowd", "#crowd", 'data-toggle' => 'tab' + %li.nav-item + = link_to "Crowd", "#crowd", class: 'nav-link active', 'data-toggle' => 'tab' - @ldap_servers.each_with_index do |server, i| - %li{ class: active_when(i.zero? && !crowd_enabled?) } - = link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab' + %li.nav-item{ class: active_when(i.zero? && !crowd_enabled?) } + = link_to server['label'], "##{server['provider_name']}", class: 'nav-link', 'data-toggle' => 'tab' - if password_authentication_enabled_for_web? - %li - = link_to 'Standard', '#login-pane', 'data-toggle' => 'tab' + %li.nav-item + = link_to 'Standard', '#login-pane', class: 'nav-link', 'data-toggle' => 'tab' - if allow_signup? - %li - = link_to 'Register', '#register-pane', 'data-toggle' => 'tab' + %li.nav-item + = link_to 'Register', '#register-pane', class: 'nav-link', 'data-toggle' => 'tab' diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index fa3c3df7f60..284d4fa1b89 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,6 +1,6 @@ -%ul.nav-links.new-session-tabs{ role: 'tablist' } - %li.active{ role: 'presentation' } - %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in +%ul.nav-links.new-session-tabs.nav-tabs.nav{ role: 'tablist' } + %li.nav-item{ role: 'presentation' } + %a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in - if allow_signup? - %li{ role: 'presentation' } - %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register + %li.nav-item{ role: 'presentation' } + %a.nav-link{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml index ebe8c327079..1765251c93d 100644 --- a/app/views/discussions/_discussion.html.haml +++ b/app/views/discussions/_discussion.html.haml @@ -51,5 +51,5 @@ - if discussion.diff_discussion? && discussion.diff_file = render "discussions/diff_with_notes", discussion: discussion - else - .panel.panel-default + .card = render partial: "discussions/notes", locals: { discussion: discussion, disable_collapse_class: true } diff --git a/app/views/discussions/_notes.html.haml b/app/views/discussions/_notes.html.haml index 1cc227428e9..30b00ca86b3 100644 --- a/app/views/discussions/_notes.html.haml +++ b/app/views/discussions/_notes.html.haml @@ -11,7 +11,7 @@ - if discussion.try(:on_image?) && show_toggle %button.diff-notes-collapse.js-diff-notes-toggle{ type: 'button' } = sprite_icon('collapse', css_class: 'collapse-icon') - %button.btn-transparent.badge.js-diff-notes-toggle{ type: 'button' } + %button.btn-transparent.badge.badge-pill.js-diff-notes-toggle{ type: 'button' } = badge_counter = render partial: "shared/notes/note", collection: discussion.notes, as: :note, locals: { badge_counter: badge_counter, show_image_comment_badge: show_image_comment_badge } @@ -22,7 +22,7 @@ - if discussion.potentially_resolvable? - line_type = local_assigns.fetch(:line_type, nil) - .btn-group-justified.discussion-with-resolve-btn{ role: "group" } + .btn-group.discussion-with-resolve-btn{ role: "group" } .btn-group{ role: "group" } = link_to_reply_discussion(discussion, line_type) diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml index cf0e0de1ca4..be0935b8313 100644 --- a/app/views/doorkeeper/applications/_form.html.haml +++ b/app/views/doorkeeper/applications/_form.html.haml @@ -9,10 +9,10 @@ = f.label :redirect_uri, class: 'label-light' = f.text_area :redirect_uri, class: 'form-control', required: true - %span.help-block + %span.form-text.text-muted Use one line per URI - if Doorkeeper.configuration.native_redirect_uri - %span.help-block + %span.form-text.text-muted Use %code= Doorkeeper.configuration.native_redirect_uri for local tests diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index d1237d7bf6f..cdf3ff81bd9 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -73,7 +73,7 @@ %tr %td Anonymous - .help-block + .form-text.text-muted %em Authorization was granted by entering your username and password in the application. %td= token.created_at %td= token.scopes diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml index 6364f0be4a3..89ad626f73f 100644 --- a/app/views/doorkeeper/applications/show.html.haml +++ b/app/views/doorkeeper/applications/show.html.haml @@ -30,5 +30,5 @@ = render "shared/tokens/scopes_list", token: @application .form-actions - = link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left' + = link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide float-left' = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10' diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml index c8a585560a2..30c9d02b72e 100644 --- a/app/views/doorkeeper/authorized_applications/index.html.haml +++ b/app/views/doorkeeper/authorized_applications/index.html.haml @@ -1,4 +1,4 @@ -%header.page-header +%header %h1 Your authorized applications %main{ :role => "main" } .table-holder diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index e3c5fd55f08..bc1d32607e4 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -6,7 +6,7 @@ at = event.created_at.to_s(:short) - unless event.rm_ref? - %blockquote= markdown(escape_once(event.commit_title), pipeline: :atom, project: event.project, author: event.author) + .blockquote= markdown(escape_once(event.commit_title), pipeline: :atom, project: event.project, author: event.author) - if event.commits_count > 1 %p %i diff --git a/app/views/explore/groups/_nav.html.haml b/app/views/explore/groups/_nav.html.haml index c8d95b52156..ab4787c6d05 100644 --- a/app/views/explore/groups/_nav.html.haml +++ b/app/views/explore/groups/_nav.html.haml @@ -1,5 +1,5 @@ .top-area - %ul.nav-links + %ul.nav-links.nav.nav-tabs = nav_link(page: explore_groups_path) do = link_to explore_groups_path do Explore Groups diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index efa8b2706da..0643b9cfbc5 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -9,7 +9,7 @@ = render 'nav' - if cookies[:explore_groups_landing_dismissed] != 'true' - .explore-groups.landing.content-block.js-explore-groups-landing.hidden + .explore-groups.landing.content-block.js-explore-groups-landing.hide %button.dismiss-button{ type: 'button', 'aria-label' => 'Dismiss' }= icon('times') .svg-container = custom_icon('icon_explore_groups_splash') diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index f630f1effdc..845f4046d0d 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -8,7 +8,7 @@ - else Any = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right + %ul.dropdown-menu.dropdown-menu-right %li = link_to filter_projects_path(visibility_level: nil) do Any diff --git a/app/views/explore/projects/_nav.html.haml b/app/views/explore/projects/_nav.html.haml index e0a2a1e9c96..558cd26f1e0 100644 --- a/app/views/explore/projects/_nav.html.haml +++ b/app/views/explore/projects/_nav.html.haml @@ -1,5 +1,5 @@ .top-area - %ul.nav-links + %ul.nav-links.nav.nav-tabs = nav_link(page: [trending_explore_projects_path, explore_root_path]) do = link_to trending_explore_projects_path do Trending diff --git a/app/views/groups/_create_chat_team.html.haml b/app/views/groups/_create_chat_team.html.haml index 20de1b4c973..9a3ff0313b5 100644 --- a/app/views/groups/_create_chat_team.html.haml +++ b/app/views/groups/_create_chat_team.html.haml @@ -1,10 +1,10 @@ .form-group - = f.label :create_chat_team, class: 'control-label' do + = f.label :create_chat_team, class: 'col-form-label' do %span.mattermost-icon = custom_icon('icon_mattermost') Mattermost .col-sm-10 - .checkbox.js-toggle-container + .form-check.js-toggle-container = f.label :create_chat_team do .js-toggle-button= f.check_box(:create_chat_team, { checked: true }, true, false) Create a Mattermost team for this group diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml index 65e95f3aeef..3cd3fb32b9c 100644 --- a/app/views/groups/_group_admin_settings.html.haml +++ b/app/views/groups/_group_admin_settings.html.haml @@ -1,7 +1,7 @@ -.form-group - = f.label :lfs_enabled, 'Large File Storage', class: 'control-label' +.form-group.row + = f.label :lfs_enabled, 'Large File Storage', class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.label :lfs_enabled do = f.check_box :lfs_enabled, checked: @group.lfs_enabled? %strong @@ -10,17 +10,17 @@ %br/ %span.descr This setting can be overridden in each project. -.form-group - = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' +.form-group.row + = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.label :require_two_factor_authentication do = f.check_box :require_two_factor_authentication %strong Require all users in this group to setup Two-factor authentication = link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group') -.form-group - .col-sm-offset-2.col-sm-10 - .checkbox +.form-group.row + .offset-sm-2.col-sm-10 + .form-check = f.text_field :two_factor_grace_period, class: 'form-control' - .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication + .form-text.text-muted Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 3375e01b3a1..96ed63937fa 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,16 +1,16 @@ - breadcrumb_title "General Settings" - @content_class = "limit-container-width" unless fluid_layout -.panel.panel-default.prepend-top-default - .panel-heading +.card.prepend-top-default + .card-header Group settings - .panel-body - = form_for @group, html: { multipart: true, class: "form-horizontal gl-show-field-errors" }, authenticity_token: true do |f| + .card-body + = form_for @group, html: { multipart: true, class: "gl-show-field-errors" }, authenticity_token: true do |f| = form_errors(@group) = render 'shared/group_form', f: f - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 .avatar-container.s160 = group_icon(@group, alt: '', class: 'avatar group-avatar s160') %p.light @@ -25,15 +25,15 @@ = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 = render 'shared/allow_request_access', form: f - .form-group - %label.control-label + .form-group.row + %label.col-form-label.col-sm-2 = s_("GroupSettings|Share with group lock") .col-sm-10 - .checkbox + .form-check = f.label :share_with_group_lock do = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group) %strong @@ -47,9 +47,9 @@ .form-actions = f.submit 'Save group', class: "btn btn-save" -.panel.panel-danger - .panel-heading Remove group - .panel-body +.card.bg-danger + .card-header Remove group + .card-body = form_tag(@group, method: :delete) do %p Removing group will cause all child projects and resources to be removed. @@ -60,9 +60,9 @@ = button_to 'Remove group', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_group_message(@group) } - if supports_nested_groups? - .panel.panel-warning - .panel-heading Transfer group - .panel-body + .card.bg-warning + .card-header Transfer group + .card-body = form_for @group, url: transfer_group_path(@group), method: :put do |f| .form-group = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: "Search groups", data: { data: parent_group_options(@group) } }) diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml index 5b1a4630c56..aa03f8365f9 100644 --- a/app/views/groups/group_members/_new_group_member.html.haml +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -2,12 +2,12 @@ .row .col-md-4.col-lg-6 = users_select_tag(:user_ids, multiple: true, class: 'input-clamp', scope: :all, email_user: true) - .help-block.append-bottom-10 + .form-text.text-muted.append-bottom-10 Search for members by name, username, or email, or invite new ones using their email address. .col-md-3.col-lg-2 = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "form-control project-access-select" - .help-block.append-bottom-10 + .form-text.text-muted.append-bottom-10 = link_to "Read more", help_page_path("user/permissions"), class: "vlink" about role permissions @@ -15,7 +15,7 @@ .clearable-input = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' %i.clear-icon.js-clear-input - .help-block.append-bottom-10 + .form-text.text-muted.append-bottom-10 On this date, the member(s) will automatically lose access to this group and all of its projects. .col-md-2 diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index c8addc49117..6a0321bcd2b 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -17,8 +17,8 @@ .clearfix %h5.member.existing-title Existing members - .panel.panel-default - .panel-heading.flex-project-members-panel + .card + .card-header.flex-project-members-panel %span.flex-project-title Members with access to %strong= @group.name diff --git a/app/views/groups/milestones/_form.html.haml b/app/views/groups/milestones/_form.html.haml index a1be0d3220a..6d35457a0ec 100644 --- a/app/views/groups/milestones/_form.html.haml +++ b/app/views/groups/milestones/_form.html.haml @@ -1,14 +1,14 @@ -= form_for [@group, @milestone], html: { class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input' } do |f| += form_for [@group, @milestone], html: { class: 'milestone-form common-note-form js-quick-submit js-requires-input' } do |f| .row = form_errors(@milestone) .col-md-6 - .form-group - = f.label :title, "Title", class: "control-label" + .form-group.row + = f.label :title, "Title", class: "col-form-label col-sm-2" .col-sm-10 = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true - .form-group.milestone-description - = f.label :description, "Description", class: "control-label" + .form-group.row.milestone-description + = f.label :description, "Description", class: "col-form-label col-sm-2" .col-sm-10 = render layout: 'projects/md_preview', locals: { url: group_preview_markdown_path } do = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...', supports_autocomplete: false diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index e9daac95ca1..1e72d88db1e 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -8,12 +8,12 @@ New Group %hr -= form_for @group, html: { class: 'group-form form-horizontal gl-show-field-errors' } do |f| += form_for @group, html: { class: 'group-form gl-show-field-errors' } do |f| = form_errors(@group) = render 'shared/group_form', f: f, autofocus: true - .form-group.group-description-holder - = f.label :avatar, "Group avatar", class: 'control-label' + .form-group.row.group-description-holder + = f.label :avatar, "Group avatar", class: 'col-form-label col-sm-2' .col-sm-10 = render 'shared/choose_group_avatar_button', f: f @@ -21,8 +21,8 @@ = render 'create_chat_team', f: f if Gitlab.config.mattermost.enabled - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 = render 'shared/group_tips' .form-actions diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index ef181b425bc..cd07b95155c 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,7 +1,7 @@ - breadcrumb_title "Projects" -.panel.panel-default.prepend-top-default - .panel-heading +.card.prepend-top-default + .card-header %strong= @group.name projects: - if can? current_user, :admin_group, @group @@ -15,10 +15,10 @@ %span{ class: visibility_level_color(project.visibility_level) } = visibility_level_icon(project.visibility_level) %strong= link_to project.full_name, project - .pull-right + .float-right - if project.archived - %span.label.label-warning archived - %span.badge + %span.badge.badge-warning archived + %span.badge.badge-pill = storage_counter(project.statistics.storage_size) = link_to 'Members', project_project_members_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 8e1dea4afc1..8b0ef3cd87a 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -20,7 +20,7 @@ %input.btn.btn-success.dropdown-primary.js-new-group-child{ type: "button", value: new_project_label, data: { action: "new-project" } } %button.btn.btn-success.dropdown-toggle.js-dropdown-toggle{ type: "button", data: { "dropdown-trigger" => "#new-project-or-subgroup-dropdown" } } = icon("caret-down", class: "dropdown-btn-icon") - %ul#new-project-or-subgroup-dropdown.dropdown-menu.dropdown-menu-align-right{ data: { dropdown: true } } + %ul#new-project-or-subgroup-dropdown.dropdown-menu.dropdown-menu-right{ data: { dropdown: true } } %li.droplab-item-selected{ role: "button", data: { value: "new-project", text: new_project_label } } .menu-item .icon-container diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index bf2725dc328..6391a13dd25 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -8,7 +8,7 @@ Community Edition - if user_signed_in? %span= Gitlab::VERSION - %small= link_to Gitlab::REVISION, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab::REVISION) + %small= link_to Gitlab.revision, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab.revision) = version_status_badge %hr @@ -33,8 +33,8 @@ .documentation-index.wiki = markdown(@help_index) .col-md-4 - .panel.panel-default - .panel-heading + .card + .card-header Quick help %ul.well-list %li= link_to 'See our website for getting help', support_url diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 7908a04c2eb..94b012d39a3 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -129,8 +129,8 @@ .lead List inside panel .example - .panel.panel-default - .panel-heading Your list + .card + .card-header Your list %ul.well-list %li One item @@ -174,7 +174,7 @@ .example .top-area - %ul.nav-links + %ul.nav-links.nav.nav-tabs %li.active %a Open %li @@ -205,7 +205,7 @@ %h2#buttons Buttons .example - %button.btn.btn-default{ :type => "button" } Default + %button.btn.btn-default{ :type => "button" } Secondary %button.btn.btn-primary{ :type => "button" } Primary %button.btn.btn-success{ :type => "button" } Success %button.btn.btn-info{ :type => "button" } Info @@ -217,7 +217,7 @@ .example .clearfix - .dropdown.inline.pull-left + .dropdown.inline.float-left %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown = icon('chevron-down') @@ -225,11 +225,11 @@ %li %a{ href: "#" } Dropdown option - .dropdown.inline.pull-right + .dropdown.inline.float-right %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right + %ul.dropdown-menu.dropdown-menu-right %li %a{ href: "#" } Dropdown option @@ -415,26 +415,26 @@ .row .col-md-6 - .panel.panel-success - .panel-heading Success - .panel-body + .card.bg-success + .card-header Success + .card-body = lorem - .panel.panel-primary - .panel-heading Primary - .panel-body + .card.bg-primary + .card-header Primary + .card-body = lorem - .panel.panel-info - .panel-heading Info - .panel-body + .card.bg-info + .card-header Info + .card-body = lorem .col-md-6 - .panel.panel-warning - .panel-heading Warning - .panel-body + .card.bg-warning + .card-header Warning + .card-body = lorem - .panel.panel-danger - .panel-heading Danger - .panel-body + .card.bg-danger + .card-header Danger + .card-body = lorem %h2#alerts Alerts @@ -460,23 +460,23 @@ %code form.horizontal-form .example - %form.form-horizontal - .form-group - %label.col-sm-2.control-label{ :for => "inputEmail3" } Email + %form + .form-group.row + %label.col-sm-2.col-form-label{ :for => "inputEmail3" } Email .col-sm-10 %input#inputEmail3.form-control{ :placeholder => "Email", :type => "email" }/ - .form-group - %label.col-sm-2.control-label{ :for => "inputPassword3" } Password + .form-group.row + %label.col-sm-2.col-form-label{ :for => "inputPassword3" } Password .col-sm-10 %input#inputPassword3.form-control{ :placeholder => "Password", :type => "password" }/ - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check %label %input{ :type => "checkbox" }/ Remember me - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 %button.btn.btn-default{ :type => "submit" } Sign in .lead @@ -491,7 +491,7 @@ .form-group %label{ :for => "exampleInputPassword1" } Password %input#exampleInputPassword1.form-control{ :placeholder => "Password", :type => "password" }/ - .checkbox + .form-check %label %input{ :type => "checkbox" }/ Remember me diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml index 638c8b5a672..f7094375023 100644 --- a/app/views/import/_githubish_status.html.haml +++ b/app/views/import/_githubish_status.html.haml @@ -46,14 +46,15 @@ %td.import-target %fieldset.row .input-group - .project-path.input-group-btn + .project-path.input-group-prepend - if current_user.can_select_namespace? - selected = params[:namespace_id] || :current_user - opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner.login, path: repo.owner.login) } : {} - = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 } + = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'input-group-text select2 js-select-namespace', tabindex: 1 } - else - = text_field_tag :path, current_user.namespace_path, class: "input-large form-control", tabindex: 1, disabled: true - %span.input-group-addon / + = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true + %span.input-group-prepend + .input-group-text / = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true %td.import-actions.job-status = button_tag class: "btn btn-import js-add-to-import" do diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 9589e0956f4..4e8f715db4f 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -54,14 +54,15 @@ %td.import-target %fieldset.row .input-group - .project-path.input-group-btn + .project-path.input-group-prepend - if current_user.can_select_namespace? - selected = params[:namespace_id] || :current_user - opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner, path: repo.owner) } : {} - = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 } + = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'input-group-text select2 js-select-namespace', tabindex: 1 } - else - = text_field_tag :path, current_user.namespace_path, class: "input-large form-control", tabindex: 1, disabled: true - %span.input-group-addon / + = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true + %span.input-group-prepend + .input-group-text / = text_field_tag :path, repo.name, class: "input-mini form-control", tabindex: 2, autofocus: true, required: true %td.import-actions.job-status = button_tag class: 'btn btn-import js-add-to-import' do @@ -73,7 +74,7 @@ = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status - = label_tag 'Incompatible Project', nil, class: 'label label-danger' + = label_tag 'Incompatible Project', nil, class: 'label badge-danger' - if @incompatible_repos.any? %p diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml index 5515fad6f48..d5a37095365 100644 --- a/app/views/import/fogbugz/new.html.haml +++ b/app/views/import/fogbugz/new.html.haml @@ -5,21 +5,21 @@ Import projects from FogBugz %hr -= form_tag callback_import_fogbugz_path, class: 'form-horizontal' do += form_tag callback_import_fogbugz_path do %p To get started you enter your FogBugz URL and login information below. In the next steps, you'll be able to map users and select the projects you want to import. - .form-group - = label_tag :uri, 'FogBugz URL', class: 'control-label' + .form-group.row + = label_tag :uri, 'FogBugz URL', class: 'col-form-label col-sm-8' .col-sm-4 = text_field_tag :uri, nil, placeholder: 'https://mycompany.fogbugz.com', class: 'form-control' - .form-group - = label_tag :email, 'FogBugz Email', class: 'control-label' + .form-group.row + = label_tag :email, 'FogBugz Email', class: 'col-form-label col-sm-8' .col-sm-4 = text_field_tag :email, nil, class: 'form-control' - .form-group - = label_tag :password, 'FogBugz Password', class: 'control-label' + .form-group.row + = label_tag :password, 'FogBugz Password', class: 'col-form-label col-sm-8' .col-sm-4 = password_field_tag :password, nil, class: 'form-control' .form-actions diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml index 84e0009487f..d27c5d3c36d 100644 --- a/app/views/import/fogbugz/new_user_map.html.haml +++ b/app/views/import/fogbugz/new_user_map.html.haml @@ -5,7 +5,7 @@ Import projects from FogBugz %hr -= form_tag create_user_map_import_fogbugz_path, class: 'form-horizontal' do += form_tag create_user_map_import_fogbugz_path do %p Customize how FogBugz email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import. diff --git a/app/views/import/gitea/new.html.haml b/app/views/import/gitea/new.html.haml index 02a116f996b..eb9790c7903 100644 --- a/app/views/import/gitea/new.html.haml +++ b/app/views/import/gitea/new.html.haml @@ -10,13 +10,13 @@ = succeed '.' do = link_to 'Personal Access Token', 'https://github.com/gogits/go-gogs-client/wiki#access-token' -= form_tag personal_access_token_import_gitea_path, class: 'form-horizontal' do - .form-group - = label_tag :gitea_host_url, 'Gitea Host URL', class: 'control-label' += form_tag personal_access_token_import_gitea_path do + .form-group.row + = label_tag :gitea_host_url, 'Gitea Host URL', class: 'col-form-label col-sm-8' .col-sm-4 = text_field_tag :gitea_host_url, nil, placeholder: 'https://try.gitea.io', class: 'form-control' - .form-group - = label_tag :personal_access_token, 'Personal Access Token', class: 'control-label' + .form-group.row + = label_tag :personal_access_token, 'Personal Access Token', class: 'col-form-label col-sm-8' .col-sm-4 = text_field_tag :personal_access_token, nil, class: 'form-control' .form-actions diff --git a/app/views/import/gitlab_projects/new.html.haml b/app/views/import/gitlab_projects/new.html.haml index dec85368d10..2d059e78490 100644 --- a/app/views/import/gitlab_projects/new.html.haml +++ b/app/views/import/gitlab_projects/new.html.haml @@ -8,20 +8,22 @@ = form_tag import_gitlab_project_path, class: 'new_project', multipart: true do .row - .form-group.col-xs-12.col-sm-6 + .form-group.col-12.col-sm-6 = label_tag :namespace_id, 'Project path', class: 'label-light' .form-group .input-group - if current_user.can_select_namespace? - .input-group-addon.has-tooltip{ title: root_url } - = root_url + .input-group-prepend.has-tooltip{ title: root_url } + .input-group-text + = root_url = select_tag :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), class: 'select2 js-select-namespace', tabindex: 1 - else - .input-group-addon.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' } - #{user_url(current_user.username)}/ + .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' } + .input-group-text + #{user_url(current_user.username)}/ = hidden_field_tag :namespace_id, value: current_user.namespace_id - .form-group.col-xs-12.col-sm-6.project-path + .form-group.col-12.col-sm-6.project-path = label_tag :path, 'Project name', class: 'label-light' = text_field_tag :path, @path, placeholder: "my-awesome-project", class: "js-path-name form-control", tabindex: 2, autofocus: true, required: true diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml index c5800a1cca0..2f1fb8d9c56 100644 --- a/app/views/import/google_code/new.html.haml +++ b/app/views/import/google_code/new.html.haml @@ -5,7 +5,7 @@ Import projects from Google Code %hr -= form_tag callback_import_google_code_path, class: 'form-horizontal', multipart: true do += form_tag callback_import_google_code_path, multipart: true do %p Follow the steps below to export your Google Code project data. In the next step, you'll be able to select the projects you want to import. diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml index 0738b3db1eb..91c774f575c 100644 --- a/app/views/import/google_code/new_user_map.html.haml +++ b/app/views/import/google_code/new_user_map.html.haml @@ -5,7 +5,7 @@ Import projects from Google Code %hr -= form_tag create_user_map_import_google_code_path, class: 'form-horizontal' do += form_tag create_user_map_import_google_code_path do %p 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. @@ -36,7 +36,7 @@ will add "By <a href="#">johnsmith@example.com</a>" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address. - .form-group + .form-group.row .col-sm-12 = text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15 diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index bc61aeece72..acf7a108cb0 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -66,7 +66,7 @@ = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status - = label_tag "Incompatible Project", nil, class: "label label-danger" + = label_tag "Incompatible Project", nil, class: "label badge-danger" - if @incompatible_repos.any? %p diff --git a/app/views/kaminari/gitlab/_first_page.html.haml b/app/views/kaminari/gitlab/_first_page.html.haml index e7a70e3bb28..369165da02a 100644 --- a/app/views/kaminari/gitlab/_first_page.html.haml +++ b/app/views/kaminari/gitlab/_first_page.html.haml @@ -5,5 +5,5 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li.first - = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote +%li.first.page-item + = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link' diff --git a/app/views/kaminari/gitlab/_gap.html.haml b/app/views/kaminari/gitlab/_gap.html.haml index 889514c4755..6eec30212d1 100644 --- a/app/views/kaminari/gitlab/_gap.html.haml +++ b/app/views/kaminari/gitlab/_gap.html.haml @@ -4,6 +4,5 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li - %span.gap - = raw(t 'views.pagination.truncate') +%li.page-item.disabled + = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' diff --git a/app/views/kaminari/gitlab/_last_page.html.haml b/app/views/kaminari/gitlab/_last_page.html.haml index 53f780d1d1b..8b49db58281 100644 --- a/app/views/kaminari/gitlab/_last_page.html.haml +++ b/app/views/kaminari/gitlab/_last_page.html.haml @@ -5,5 +5,5 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li.last - = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote} +%li.last.page-item + = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, {remote: remote, class: 'page-link'} diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml index c93dc7a50e8..05f151555ad 100644 --- a/app/views/kaminari/gitlab/_next_page.html.haml +++ b/app/views/kaminari/gitlab/_next_page.html.haml @@ -5,9 +5,8 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -- if current_page.last? - %li.next.disabled - %span= raw(t 'views.pagination.next') -- else - %li.next - = link_to raw(t 'views.pagination.next'), url, rel: 'next', remote: remote + +- page_url = current_page.last? ? '#' : url + +%li.page-item{ class: ('disabled' if current_page.last?) } + = link_to raw(t 'views.pagination.next'), page_url, rel: 'next', remote: remote, class: 'page-link' diff --git a/app/views/kaminari/gitlab/_page.html.haml b/app/views/kaminari/gitlab/_page.html.haml index 5c5be03a7cd..8a40e13a537 100644 --- a/app/views/kaminari/gitlab/_page.html.haml +++ b/app/views/kaminari/gitlab/_page.html.haml @@ -6,5 +6,5 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li.page{ class: [active_when(page.current?), ('sibling' if page.next? || page.prev?)] } - = link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil } +%li.page-item.js-pagination-page{ class: [active_when(page.current?), ('sibling' if page.next? || page.prev?)] } + = link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil, class: 'page-link' } diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml index 8fe6bd653ae..a6435deb4bf 100644 --- a/app/views/kaminari/gitlab/_paginator.html.haml +++ b/app/views/kaminari/gitlab/_paginator.html.haml @@ -7,7 +7,7 @@ -# paginator: the paginator that renders the pagination tags inside = paginator.render do .gl-pagination - %ul.pagination.clearfix + %ul.pagination.justify-content-center - unless current_page.first? = first_page_tag unless total_pages < 5 # As kaminari will always show the first 5 pages = prev_page_tag diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml index b7c6caf7ff4..f4a11a449b7 100644 --- a/app/views/kaminari/gitlab/_prev_page.html.haml +++ b/app/views/kaminari/gitlab/_prev_page.html.haml @@ -5,9 +5,8 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -- if current_page.first? - %li.prev.disabled - %span= raw(t 'views.pagination.previous') -- else - %li.prev - = link_to raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote + +- page_url = current_page.first? ? '#' : url + +%li.page-item{ class: ('disabled' if current_page.first?) } + = link_to raw(t 'views.pagination.previous'), page_url, rel: 'prev', remote: remote, class: 'page-link' diff --git a/app/views/kaminari/gitlab/_without_count.html.haml b/app/views/kaminari/gitlab/_without_count.html.haml index 250029c4475..1425a809052 100644 --- a/app/views/kaminari/gitlab/_without_count.html.haml +++ b/app/views/kaminari/gitlab/_without_count.html.haml @@ -1,8 +1,8 @@ .gl-pagination %ul.pagination.clearfix - if previous_path - %li.prev - = link_to(t('views.pagination.previous'), previous_path, rel: 'prev') + %li.page-item.prev + = link_to(t('views.pagination.previous'), previous_path, rel: 'prev', class: 'page-link') - if next_path - %li.next - = link_to(t('views.pagination.next'), next_path, rel: 'next') + %li.page-item.next + = link_to(t('views.pagination.next'), next_path, rel: 'next', class: 'page-link') diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 52587760ba4..91f796adb2e 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -7,7 +7,7 @@ - if @project && @project.persisted? - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? } .search.search-form{ class: "#{'has-location-badge' if label.present?}" } - = form_tag search_path, method: :get, class: 'navbar-form' do |f| + = form_tag search_path, method: :get, class: 'form-inline' do |f| .search-input-container - if label.present? .location-badge= label diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 6513b719199..82ca7252424 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -10,9 +10,7 @@ .content = render "layouts/flash" .row - .col-sm-5.pull-right.new-session-forms-container - = yield - .col-sm-7.brand-holder.pull-left + .col-sm-7.brand-holder %h1 = brand_title = brand_image @@ -28,6 +26,8 @@ - if Gitlab::CurrentSettings.sign_in_text.present? = markdown_field(Gitlab::CurrentSettings.current_application_settings, :sign_in_text) + .col-sm-5.new-session-forms-container + = yield %hr.footer-fixed .container.footer-container diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index dc121812406..1bca837a311 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,4 +1,4 @@ -%header.navbar.navbar-gitlab.qa-navbar +%header.navbar.navbar-gitlab.qa-navbar.navbar-expand-sm %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content .container-fluid .header-content @@ -8,7 +8,7 @@ = brand_header_logo - logo_text = brand_header_logo_type - if logo_text.present? - %span.logo-text.hidden-xs + %span.logo-text.d-none.d-sm-block = logo_text - if current_user @@ -21,9 +21,9 @@ - if current_user = render 'layouts/header/new_dropdown' - if header_link?(:search) - %li.hidden-sm.hidden-xs + %li.nav-item.d-none.d-sm-none.d-md-block = render 'layouts/search' unless current_controller?(:search) - %li.visible-sm-inline-block.visible-xs-inline-block + %li.nav-item.d-inline-block.d-sm-none.d-md-none = link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = sprite_icon('search', size: 16) @@ -32,38 +32,38 @@ = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues', aria: { label: "Issues" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = sprite_icon('issues', size: 16) - issues_count = assigned_issuables_count(:issues) - %span.badge.issues-count{ class: ('hidden' if issues_count.zero?) } + %span.badge.badge-pill.issues-count{ class: ('hidden' if issues_count.zero?) } = number_with_delimiter(issues_count) - if header_link?(:merge_requests) = nav_link(path: 'dashboard#merge_requests', html_options: { class: "user-counter" }) do = link_to assigned_mrs_dashboard_path, title: 'Merge requests', class: 'dashboard-shortcuts-merge_requests', aria: { label: "Merge requests" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = sprite_icon('git-merge', size: 16) - merge_requests_count = assigned_issuables_count(:merge_requests) - %span.badge.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) } + %span.badge.badge-pill.merge-requests-count{ class: ('hidden' if merge_requests_count.zero?) } = number_with_delimiter(merge_requests_count) - if header_link?(:todos) = nav_link(controller: 'dashboard/todos', html_options: { class: "user-counter" }) do = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, class: 'shortcuts-todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = sprite_icon('todo-done', size: 16) - %span.badge.todos-count{ class: ('hidden' if todos_pending_count.zero?) } + %span.badge.badge-pill.todos-count{ class: ('hidden' if todos_pending_count.zero?) } = todos_count_format(todos_pending_count) - if header_link?(:user_dropdown) - %li.header-user.dropdown + %li.nav-item.header-user.dropdown = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar" = sprite_icon('angle-down', css_class: 'caret-down') - .dropdown-menu-nav.dropdown-menu-align-right + .dropdown-menu.dropdown-menu-right = render 'layouts/header/current_user_dropdown' - if header_link?(:admin_impersonation) - %li.impersonation - = link_to admin_impersonation_path, class: 'impersonation-btn', method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do + %li.nav-item.impersonation + = link_to admin_impersonation_path, class: 'nav-link impersonation-btn', method: :delete, title: "Stop impersonation", aria: { label: 'Stop impersonation' }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('user-secret') - if header_link?(:sign_in) - %li + %li.nav-item %div - = link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in' + = link_to "Sign in / Register", new_session_path(:user, redirect_to_referer: 'yes'), class: 'nav-link btn btn-sign-in' - %button.navbar-toggle.hidden-sm.hidden-md.hidden-lg{ type: 'button' } + %button.navbar-toggler.d-block.d-sm-none{ type: 'button' } %span.sr-only Toggle navigation = sprite_icon('more', size: 12, css_class: 'more-icon js-navbar-toggle-right') = sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left') diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml index 2ed4edb1136..2dfc787b7a8 100644 --- a/app/views/layouts/header/_empty.html.haml +++ b/app/views/layouts/header/_empty.html.haml @@ -1,4 +1,4 @@ -%header.navbar.navbar-fixed-top.navbar-empty +%header.navbar.fixed-top.navbar-empty .container - .center-logo + .mx-auto = brand_header_logo diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index 6f53f5ac1ae..db8137cc248 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -2,7 +2,7 @@ = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: "New...", ref: 'tooltip', aria: { label: "New..." }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body' } do = sprite_icon('plus-square', size: 16) = sprite_icon('angle-down', css_class: 'caret-down') - .dropdown-menu-nav.dropdown-menu-align-right + .dropdown-menu.dropdown-menu-right %ul - if @group&.persisted? - create_group_project = can?(current_user, :create_projects, @group) diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index f773bd0832d..7647e25e804 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -8,34 +8,34 @@ = render "layouts/nav/projects_dropdown/show" - if dashboard_nav_link?(:groups) - = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "hidden-xs" }) do + = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "d-none d-sm-block" }) do = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups qa-groups-link', title: 'Groups' do Groups - if dashboard_nav_link?(:activity) - = nav_link(path: 'dashboard#activity', html_options: { class: "visible-lg" }) do + = nav_link(path: 'dashboard#activity', html_options: { class: "d-none d-lg-block d-xl-block" }) do = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do Activity - if dashboard_nav_link?(:milestones) - = nav_link(controller: 'dashboard/milestones', html_options: { class: "visible-lg" }) do + = nav_link(controller: 'dashboard/milestones', html_options: { class: "d-none d-lg-block d-xl-block" }) do = link_to dashboard_milestones_path, class: 'dashboard-shortcuts-milestones', title: 'Milestones' do Milestones - if dashboard_nav_link?(:snippets) - = nav_link(controller: 'dashboard/snippets', html_options: { class: "visible-lg" }) do + = nav_link(controller: 'dashboard/snippets', html_options: { class: "d-none d-lg-block d-xl-block" }) do = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: 'Snippets' do Snippets - if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets]) - %li.header-more.dropdown.hidden-lg + %li.header-more.dropdown.d-lg-none.d-xl-none %a{ href: "#", data: { toggle: "dropdown" } } More = sprite_icon('angle-down', css_class: 'caret-down') .dropdown-menu %ul - if dashboard_nav_link?(:groups) - = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "visible-xs" }) do + = nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { class: "d-block d-sm-none" }) do = link_to dashboard_groups_path, class: 'dashboard-shortcuts-groups', title: 'Groups' do Groups @@ -61,13 +61,13 @@ Projects - if current_controller?('ide') - %li.line-separator.hidden-xs + %li.line-separator.d-none.d-sm-block = nav_link(controller: 'ide') do = link_to '#', class: 'dashboard-shortcuts-web-ide', title: 'Web IDE' do Web IDE - if current_user.admin? || Gitlab::Sherlock.enabled? - %li.line-separator.hidden-xs + %li.line-separator.d-none.d-sm-block - if current_user.admin? = nav_link(controller: 'admin/dashboard') do = link_to admin_root_path, class: 'admin-icon qa-admin-area-link', title: 'Admin area', aria: { label: "Admin area" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml index dd086f70641..62402c32e08 100644 --- a/app/views/layouts/nav/sidebar/_admin.html.haml +++ b/app/views/layouts/nav/sidebar/_admin.html.haml @@ -127,13 +127,13 @@ = sprite_icon('slight-frown') %span.nav-item-name Abuse Reports - %span.badge.count= number_with_delimiter(AbuseReport.count(:all)) + %span.badge.badge-pill.count= number_with_delimiter(AbuseReport.count(:all)) %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(controller: :abuse_reports, html_options: { class: "fly-out-top-item" } ) do = link_to admin_abuse_reports_path do %strong.fly-out-top-item-name #{ _('Abuse Reports') } - %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all)) + %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(AbuseReport.count(:all)) - if akismet_enabled? = nav_link(controller: :spam_logs) do diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml index 517d9aa3d99..39a033337ff 100644 --- a/app/views/layouts/nav/sidebar/_group.html.haml +++ b/app/views/layouts/nav/sidebar/_group.html.haml @@ -43,14 +43,14 @@ = sprite_icon('issues') %span.nav-item-name Issues - %span.badge.count= number_with_delimiter(issues_count) + %span.badge.badge-pill.count= number_with_delimiter(issues_count) %ul.sidebar-sub-level-items = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index'], html_options: { class: "fly-out-top-item" } ) do = link_to issues_group_path(@group) do %strong.fly-out-top-item-name #{ _('Issues') } - %span.badge.count.issue_counter.fly-out-badge= number_with_delimiter(issues_count) + %span.badge.badge-pill.count.issue_counter.fly-out-badge= number_with_delimiter(issues_count) %li.divider.fly-out-top-item = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do @@ -83,13 +83,13 @@ = sprite_icon('git-merge') %span.nav-item-name Merge Requests - %span.badge.count= number_with_delimiter(merge_requests_count) + %span.badge.badge-pill.count= number_with_delimiter(merge_requests_count) %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(path: 'groups#merge_requests', html_options: { class: "fly-out-top-item" } ) do = link_to merge_requests_group_path(@group) do %strong.fly-out-top-item-name #{ _('Merge Requests') } - %span.badge.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests_count) + %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests_count) - if group_sidebar_link?(:group_members) = nav_link(path: 'group_members#index') do diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index f3af15d771d..fdb07ce6fc5 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -88,8 +88,8 @@ %span.nav-item-name = _('Issues') - if @project.issues_enabled? - %span.badge.count.issue_counter - = number_with_delimiter(@project.open_issues_count) + %span.badge.badge-pill.count.issue_counter + = number_with_delimiter(@project.open_issues_count(current_user)) %ul.sidebar-sub-level-items = nav_link(controller: :issues, html_options: { class: "fly-out-top-item" } ) do @@ -97,8 +97,8 @@ %strong.fly-out-top-item-name = _('Issues') - if @project.issues_enabled? - %span.badge.count.issue_counter.fly-out-badge - = number_with_delimiter(@project.open_issues_count) + %span.badge.badge-pill.count.issue_counter.fly-out-badge + = number_with_delimiter(@project.open_issues_count(current_user)) %li.divider.fly-out-top-item = nav_link(controller: :issues, action: :index) do = link_to project_issues_path(@project), title: 'Issues' do @@ -140,14 +140,14 @@ = sprite_icon('git-merge') %span.nav-item-name = _('Merge Requests') - %span.badge.count.merge_counter.js-merge-counter + %span.badge.badge-pill.count.merge_counter.js-merge-counter = number_with_delimiter(@project.open_merge_requests_count) %ul.sidebar-sub-level-items.is-fly-out-only = nav_link(controller: :merge_requests, html_options: { class: "fly-out-top-item" } ) do = link_to project_merge_requests_path(@project) do %strong.fly-out-top-item-name = _('Merge Requests') - %span.badge.count.merge_counter.js-merge-counter.fly-out-badge + %span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge = number_with_delimiter(@project.open_merge_requests_count) - if project_nav_tab? :pipelines @@ -234,7 +234,7 @@ = link_to 'Auto DevOps', help_page_path('topics/autodevops/index.md') %span= _('uses Kubernetes clusters to deploy your code!') %hr - %button.btn.btn-create.btn-xs.dismiss-feature-highlight{ type: 'button' } + %button.btn.btn-create.btn-sm.dismiss-feature-highlight{ type: 'button' } %span= _("Got it!") = sprite_icon('thumb-up') diff --git a/app/views/layouts/terms.html.haml b/app/views/layouts/terms.html.haml index 87f4151f241..a8964b19ba1 100644 --- a/app/views/layouts/terms.html.haml +++ b/app/views/layouts/terms.html.haml @@ -29,6 +29,6 @@ = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do = image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar" = sprite_icon('angle-down', css_class: 'caret-down') - .dropdown-menu-nav.dropdown-menu-align-right + .dropdown-menu.dropdown-menu-right = render 'layouts/header/current_user_dropdown' = yield diff --git a/app/views/notify/merge_request_unmergeable_email.html.haml b/app/views/notify/merge_request_unmergeable_email.html.haml new file mode 100644 index 00000000000..e84905a5fad --- /dev/null +++ b/app/views/notify/merge_request_unmergeable_email.html.haml @@ -0,0 +1,6 @@ +%p + Merge Request #{link_to @merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request)} can no longer be merged due to the following #{pluralize(@reasons, 'reason')}: + + %ul + - @reasons.each do |reason| + %li= reason diff --git a/app/views/notify/merge_request_unmergeable_email.text.haml b/app/views/notify/merge_request_unmergeable_email.text.haml new file mode 100644 index 00000000000..4f0901c761a --- /dev/null +++ b/app/views/notify/merge_request_unmergeable_email.text.haml @@ -0,0 +1,11 @@ +Merge Request #{@merge_request.to_reference} can no longer be merged due to the following #{pluralize(@reasons, 'reason')}: + +- @reasons.each do |reason| + * #{reason} + +Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} + += merge_path_description(@merge_request, 'to') + +Author: #{@merge_request.author_name} +Assignee: #{@merge_request.assignee_name} diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 38dab104eb5..baafaa6e3a0 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -12,7 +12,7 @@ %tr.section %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } - %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } + %table.table-info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project @@ -114,7 +114,7 @@ = failed.size failed #{'build'.pluralize(failed.size)}. -%tr.warning +%tr.table-warning %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" } Logs may contain sensitive data. Please consider before forwarding this email. %tr.section diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 7b06e8afa0b..4fe3c4c8269 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -1,4 +1,4 @@ -%tr.success +%tr.table-success %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" } %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } %tbody @@ -12,7 +12,7 @@ %tr.section %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } - %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } + %table.table-info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } %tbody %tr %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml index d0ad90ac6cc..37466f7c821 100644 --- a/app/views/profiles/_event_table.html.haml +++ b/app/views/profiles/_event_table.html.haml @@ -9,6 +9,6 @@ Signed in with = event.details[:with] authentication - %span.pull-right= time_ago_with_tooltip(event.created_at) + %span.float-right= time_ago_with_tooltip(event.created_at) = paginate events, theme: "gitlab" diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml index d40b771f48b..d198bfc80db 100644 --- a/app/views/profiles/active_sessions/_active_session.html.haml +++ b/app/views/profiles/active_sessions/_active_session.html.haml @@ -1,6 +1,6 @@ - is_current_session = active_session.current?(session) -%li +%li.list-group-item .pull-left.append-right-10{ data: { toggle: 'tooltip' }, title: active_session.human_device_type } = active_session_device_type_icon(active_session) diff --git a/app/views/profiles/active_sessions/index.html.haml b/app/views/profiles/active_sessions/index.html.haml index d0250bb4eab..8688a52843d 100644 --- a/app/views/profiles/active_sessions/index.html.haml +++ b/app/views/profiles/active_sessions/index.html.haml @@ -10,5 +10,6 @@ .col-lg-8 .append-bottom-default - %ul.well-list - = render partial: 'profiles/active_sessions/active_session', collection: @sessions + .card.border-0 + %ul.list-group.list-group-flush + = render partial: 'profiles/active_sessions/active_session', collection: @sessions diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml index c7094800fb2..9e82e47c1e1 100644 --- a/app/views/profiles/chat_names/_chat_name.html.haml +++ b/app/views/profiles/chat_names/_chat_name.html.haml @@ -24,4 +24,4 @@ Never %td - = link_to 'Remove', profile_chat_name_path(chat_name), method: :delete, class: 'btn btn-danger pull-right', data: { confirm: 'Are you sure you want to revoke this nickname?' } + = link_to 'Remove', profile_chat_name_path(chat_name), method: :delete, class: 'btn btn-danger float-right', data: { confirm: 'Are you sure you want to revoke this nickname?' } diff --git a/app/views/profiles/chat_names/new.html.haml b/app/views/profiles/chat_names/new.html.haml index 5bf22aa94f1..d86941b7a29 100644 --- a/app/views/profiles/chat_names/new.html.haml +++ b/app/views/profiles/chat_names/new.html.haml @@ -9,7 +9,7 @@ .actions = form_tag profile_chat_names_path, method: :post do = hidden_field_tag :token, @chat_name_token.token - = submit_tag "Authorize", class: "btn btn-success wide pull-left" + = submit_tag "Authorize", class: "btn btn-success wide float-left" = form_tag deny_profile_chat_names_path, method: :delete do = hidden_field_tag :token, @chat_name_token.token = submit_tag "Deny", class: "btn btn-danger prepend-left-10" diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index e3c2bd1150e..914bd4eb57c 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -32,20 +32,20 @@ %ul.well-list %li = render partial: 'shared/email_with_badge', locals: { email: @primary_email, verified: current_user.confirmed? } - %span.pull-right - %span.label.label-success Primary email + %span.float-right + %span.badge.badge-success Primary email - if @primary_email === current_user.public_email - %span.label.label-info Public email + %span.badge.badge-info Public email - if @primary_email === current_user.notification_email - %span.label.label-info Notification email + %span.badge.badge-info Notification email - @emails.each do |email| %li = render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? } - %span.pull-right + %span.float-right - if email.email === current_user.public_email - %span.label.label-info Public email + %span.badge.badge-info Public email - if email.email === current_user.notification_email - %span.label.label-info Notification email + %span.badge.badge-info Notification email - unless email.confirmed? - confirm_title = "#{email.confirmation_sent_at ? 'Resend' : 'Send'} confirmation email" = link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'btn btn-sm btn-warning prepend-left-10' diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml index 5ed517c1ef6..d1fd7bc8e71 100644 --- a/app/views/profiles/gpg_keys/_key.html.haml +++ b/app/views/profiles/gpg_keys/_key.html.haml @@ -1,6 +1,6 @@ %li.key-list-item - .pull-left.append-right-10 - = icon 'key', class: "settings-list-icon hidden-xs" + .float-left.append-right-10 + = icon 'key', class: "settings-list-icon d-none d-sm-block" .key-list-item-info - key.emails_with_verified_status.map do |email, verified| = render partial: 'shared/email_with_badge', locals: { email: email, verified: verified } @@ -14,7 +14,7 @@ - key.subkeys.each do |subkey| %li %code= subkey.fingerprint - .pull-right + .float-right %span.key-created-at created #{time_ago_with_tooltip(key.created_at)} = link_to profile_gpg_key_path(key), data: { confirm: 'Are you sure? Removing this GPG key does not affect already signed commits.' }, method: :delete, class: "btn btn-danger prepend-left-10" do diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index 103446243e5..ce20994b0f4 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -1,9 +1,9 @@ %li.key-list-item - .pull-left.append-right-10 + .float-left.append-right-10 - if key.valid? - = icon 'key', class: 'settings-list-icon hidden-xs' + = icon 'key', class: 'settings-list-icon d-none d-sm-block' - else - = icon 'exclamation-triangle', class: 'settings-list-icon hidden-xs has-tooltip', + = icon 'exclamation-triangle', class: 'settings-list-icon d-none d-sm-block has-tooltip', title: key.errors.full_messages.join(', ') @@ -15,7 +15,7 @@ .last-used-at last used: = key.last_used_at ? time_ago_with_tooltip(key.last_used_at) : 'n/a' - .pull-right + .float-right %span.key-created-at created #{time_ago_with_tooltip(key.created_at)} = link_to path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-transparent prepend-left-10" do diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml index 77521417f47..02d5b08f7a3 100644 --- a/app/views/profiles/keys/_key_details.html.haml +++ b/app/views/profiles/keys/_key_details.html.haml @@ -1,8 +1,8 @@ - is_admin = defined?(admin) ? true : false .row.prepend-top-default .col-md-4 - .panel.panel-default - .panel-heading + .card + .card-header SSH Key %ul.well-list %li @@ -23,5 +23,5 @@ %pre.well-pre = @key.key .col-md-12 - .pull-right + .float-right = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml index 537bba21f4a..a12246bcdcc 100644 --- a/app/views/profiles/notifications/_group_settings.html.haml +++ b/app/views/profiles/notifications/_group_settings.html.haml @@ -8,5 +8,5 @@ %span.str-truncated = link_to group.name, group_path(group) - .pull-right + .float-right = render 'shared/notifications/button', notification_setting: setting diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml index 5b2a69b8891..823fec3fab4 100644 --- a/app/views/profiles/notifications/_project_settings.html.haml +++ b/app/views/profiles/notifications/_project_settings.html.haml @@ -8,5 +8,5 @@ %span.str-truncated = link_to_project(project) - .pull-right + .float-right = render 'shared/notifications/button', notification_setting: setting diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 8f099aa6dd7..71ea625e5d5 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -29,7 +29,7 @@ = label_tag :global_notification_level, "Global notification level", class: "label-light" %br .clearfix - .form-group.pull-left.global-notification-setting + .form-group.float-left.global-notification-setting = render 'shared/notifications/button', notification_setting: @global_notification_setting .clearfix diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index c606b5a1e6c..8a51a30191a 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -20,7 +20,7 @@ .form-group = f.label :current_password, class: 'label-light' = f.password_field :current_password, required: true, class: 'form-control' - %p.help-block + %p.form-text.text-muted You must provide your current password in order to change it. .form-group = f.label :password, 'New password', class: 'label-light' diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index 2eb9fac57c3..2176d7f8a31 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -2,7 +2,7 @@ - header_title "New Password" %h3.page-title Setup new password %hr -= form_for @user, url: profile_password_path, method: :post, html: { class: 'form-horizontal '} do |f| += form_for @user, url: profile_password_path, method: :post do |f| %p.slead Please set a new password before proceeding. %br @@ -11,14 +11,14 @@ = form_errors(@user) - unless @user.password_automatically_set? - .form-group - = f.label :current_password, class: 'control-label' + .form-group.row + = f.label :current_password, class: 'col-form-label col-sm-2' .col-sm-10= f.password_field :current_password, required: true, class: 'form-control' - .form-group - = f.label :password, class: 'control-label' + .form-group.row + = f.label :password, class: 'col-form-label col-sm-2' .col-sm-10= f.password_field :password, required: true, class: 'form-control' - .form-group - = f.label :password_confirmation, class: 'control-label' + .form-group.row + = f.label :password_confirmation, class: 'col-form-label col-sm-2' .col-sm-10 = f.password_field :password_confirmation, required: true, class: 'form-control' .form-actions diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index 9b87a7aaca8..d253e8e456e 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -20,9 +20,9 @@ .form-group .input-group = text_field_tag 'created-personal-access-token', @new_personal_access_token, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-personal-access-token-help-block" - %span.input-group-btn - = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left", class: "btn-default btn-clipboard") - %span#created-personal-access-token-help-block.help-block.text-danger Make sure you save it - you won't be able to access it again. + %span.input-group-append + = clipboard_button(text: @new_personal_access_token, title: "Copy personal access token to clipboard", placement: "left", class: "input-group-text btn-default btn-clipboard") + %span#created-personal-access-token-help-block.form-text.text-muted.text-danger Make sure you save it - you won't be able to access it again. %hr @@ -42,7 +42,7 @@ .col-lg-8.rss-token-reset = label_tag :rss_token, 'RSS token', class: "label-light" = text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()' - %p.help-block + %p.form-text.text-muted Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you. You should = link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' } @@ -61,7 +61,7 @@ .col-lg-8.incoming-email-token-reset = label_tag :incoming_email_token, 'Incoming email token', class: "label-light" = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control', readonly: true, onclick: 'this.select()' - %p.help-block + %p.form-text.text-muted Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should = link_to 'reset it', [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: 'Are you sure? Any issue email addresses currently in use will stop working.' } diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 6aefd97bb96..ab5565cfdaf 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -51,7 +51,7 @@ = f.label :layout, class: 'label-light' do Layout width = f.select :layout, layout_choices, {}, class: 'form-control' - .help-block + .form-text.text-muted Choose between fixed (max. 1200px) and fluid (100%) application layout. .form-group = f.label :dashboard, class: 'label-light' do @@ -61,7 +61,7 @@ = f.label :project_view, class: 'label-light' do Project overview content = f.select :project_view, project_view_choices, {}, class: 'form-control' - .help-block + .form-text.text-muted Choose what content you want to see on a project’s overview page .form-group = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index e497eab32e0..fbb29e7a0d9 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -26,7 +26,7 @@ %button.btn.js-choose-user-avatar-button{ type: 'button' }= _("Choose file...") %span.avatar-file-name.prepend-left-default.js-avatar-filename= _("No file chosen") = f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*' - .help-block= _("The maximum file size allowed is 200KB.") + .form-text.text-muted= _("The maximum file size allowed is 200KB.") - if @user.avatar? %hr = link_to _('Remove avatar'), profile_avatar_path, data: { confirm: _('Avatar will be removed. Are you sure?') }, method: :delete, class: 'btn btn-danger btn-inverted' diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml index 43b58be7f98..93722d7b034 100644 --- a/app/views/profiles/two_factor_auths/_codes.html.haml +++ b/app/views/profiles/two_factor_auths/_codes.html.haml @@ -4,7 +4,7 @@ %b will lose access to your account. -.codes.well +.codes.card %ul - @codes.each do |code| %li diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index d1eae05c46c..e35ebdea435 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -86,7 +86,7 @@ %tr %td= registration.name.presence || "<no name set>" %td= registration.created_at.to_date.to_s(:medium) - %td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." } + %td= link_to "Delete", profile_u2f_registration_path(registration), method: :delete, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to delete this device? This action cannot be undone." } - else .settings-message.text-center diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 043057e79ee..075badb9e56 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -37,7 +37,7 @@ = render 'projects/buttons/star' = render 'projects/buttons/fork' - %span.hidden-xs + %span.d-none.d-sm-inline - if can?(current_user, :download_code, @project) .project-clone-holder = render "shared/clone_panel" diff --git a/app/views/projects/_issuable_by_email.html.haml b/app/views/projects/_issuable_by_email.html.haml index c137e38ed50..e3dc0677bd6 100644 --- a/app/views/projects/_issuable_by_email.html.haml +++ b/app/views/projects/_issuable_by_email.html.haml @@ -17,8 +17,8 @@ You can create a new #{name} inside this project by sending an email to the following email address: .email-modal-input-group.input-group = text_field_tag :issuable_email, email, class: "monospace js-select-on-focus form-control", readonly: true - .input-group-btn - = clipboard_button(target: '#issuable_email', class: 'btn btn-clipboard btn-transparent hidden-xs') + .input-group-append + = clipboard_button(target: '#issuable_email', class: 'btn btn-clipboard input-group-text btn-transparent d-none d-sm-block') = mail_to email, class: 'btn btn-clipboard btn-transparent', subject: _("Enter the #{name} title"), body: _("Enter the #{name} description"), diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index f6d396c8127..3b66fdbdf1a 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -1,6 +1,6 @@ - event = last_push_event - if event && show_last_push_widget?(event) - .row-content-block.top-block.hidden-xs.white + .row-content-block.top-block.d-none.d-sm-block.white .event-last-push .event-last-push-text %span= s_("LastPushEvent|You pushed to") diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 8212ab9a31e..8fb6aa55436 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -9,7 +9,7 @@ .md-area .md-header - %ul.nav-links.clearfix + %ul.nav.nav-tabs.nav-links.clearfix %li.md-header-tab.active %a.js-md-write-button{ href: "#md-write-holder", tabindex: -1 } Write diff --git a/app/views/projects/_merge_request_fast_forward_settings.html.haml b/app/views/projects/_merge_request_fast_forward_settings.html.haml index f455522d17c..2f08a28e26e 100644 --- a/app/views/projects/_merge_request_fast_forward_settings.html.haml +++ b/app/views/projects/_merge_request_fast_forward_settings.html.haml @@ -1,7 +1,7 @@ - form = local_assigns.fetch(:form) - project = local_assigns.fetch(:project) -.radio +.form-check = label_tag :project_merge_method_ff do = form.radio_button :merge_method, :ff, class: "js-merge-method-radio qa-radio-button-merge-ff" %strong Fast-forward merge diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml index f6e5712ce81..762a263656d 100644 --- a/app/views/projects/_merge_request_merge_settings.html.haml +++ b/app/views/projects/_merge_request_merge_settings.html.haml @@ -1,7 +1,7 @@ - form = local_assigns.fetch(:form) .form-group - .checkbox.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) } + .form-check.builds-feature{ class: ("hidden" if @project && @project.project_feature.send(:builds_access_level) == 0) } = form.label :only_allow_merge_if_pipeline_succeeds do = form.check_box :only_allow_merge_if_pipeline_succeeds %strong Only allow merge requests to be merged if the pipeline succeeds @@ -9,15 +9,15 @@ %span.descr Pipelines need to be configured to enable this feature. = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds'), target: '_blank' - .checkbox + .form-check = form.label :only_allow_merge_if_all_discussions_are_resolved do = form.check_box :only_allow_merge_if_all_discussions_are_resolved %strong Only allow merge requests to be merged if all discussions are resolved - .checkbox + .form-check = form.label :resolve_outdated_diff_discussions do = form.check_box :resolve_outdated_diff_discussions %strong Automatically resolve merge request diff discussions when they become outdated - .checkbox + .form-check = form.label :printing_merge_request_link_enabled do = form.check_box :printing_merge_request_link_enabled %strong Show link to create/view merge request when pushing from the command line diff --git a/app/views/projects/_merge_request_rebase_settings.html.haml b/app/views/projects/_merge_request_rebase_settings.html.haml index 54e0b73d24c..93895a55435 100644 --- a/app/views/projects/_merge_request_rebase_settings.html.haml +++ b/app/views/projects/_merge_request_rebase_settings.html.haml @@ -1,6 +1,6 @@ - form = local_assigns.fetch(:form) -.radio +.form-check = label_tag :project_merge_method_rebase_merge do = form.radio_button :merge_method, :rebase_merge, class: "js-merge-method-radio" %strong Merge commit with semi-linear history diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index fd0c419cdac..a9ddcd94865 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -3,7 +3,7 @@ .form-group = label_tag :merge_method_merge, class: 'label-light' do Merge method - .radio + .form-check = label_tag :project_merge_method_merge do = form.radio_button :merge_method, :merge, class: "js-merge-method-radio" %strong Merge commit diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index 241bc3dbca0..6366a2f729a 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -9,13 +9,15 @@ Project path .input-group - if current_user.can_select_namespace? - .input-group-addon.has-tooltip{ title: root_url } - = root_url + .input-group-prepend.has-tooltip{ title: root_url } + .input-group-text + = root_url = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true, extra_group: namespace_id_from(params)), {}, { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1} - else - .input-group-addon.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' } - #{user_url(current_user.username)}/ + .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' } + .input-group-text + #{user_url(current_user.username)}/ = f.hidden_field :namespace_id, value: current_user.namespace_id .form-group.project-path.col-sm-6 = f.label :path, class: 'label-light' do @@ -23,7 +25,7 @@ Project name = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true - if current_user.can_create_group? - .help-block + .form-text.text-muted Want to house several dependent projects under the same namespace? = link_to "Create a group", new_group_path diff --git a/app/views/projects/_new_project_push_tip.html.haml b/app/views/projects/_new_project_push_tip.html.haml index 9bc69211d12..22e9522c0e7 100644 --- a/app/views/projects/_new_project_push_tip.html.haml +++ b/app/views/projects/_new_project_push_tip.html.haml @@ -3,9 +3,9 @@ = label_tag(:push_to_create_tip, _("Private projects can be created in your personal namespace with:"), class: "weight-normal") %p.input-group.project-tip-command - %span.input-group-btn + %span = text_field_tag :push_to_create_tip, push_to_create_project_command, class: "js-select-on-focus form-control monospace", readonly: true, aria: { label: _("Push project from command line") } - %span.input-group-btn - = clipboard_button(text: push_to_create_project_command, title: _("Copy command to clipboard"), placement: "right") + %span.input-group-append + = clipboard_button(text: push_to_create_project_command, title: _("Copy command to clipboard"), class: 'input-group-text', placement: "right") %p = link_to("What does this command do?", help_page_path("gitlab-basics/create-project", anchor: "push-to-create-a-new-project"), target: "_blank") diff --git a/app/views/projects/_project_templates.html.haml b/app/views/projects/_project_templates.html.haml index d50175727be..9d27f51926e 100644 --- a/app/views/projects/_project_templates.html.haml +++ b/app/views/projects/_project_templates.html.haml @@ -14,11 +14,12 @@ %label.label-light Template .input-group.template-input-group - .input-group-addon - .selected-icon - - Gitlab::ProjectTemplate.all.each do |template| - = custom_icon(template.logo) - .selected-template + .input-group-prepend + .input-group-text + .selected-icon + - Gitlab::ProjectTemplate.all.each do |template| + = custom_icon(template.logo) + .selected-template %button.btn.btn-default.change-template{ type: "button" } Change template = render 'new_project_fields', f: f, project_name_id: "template-project-name" diff --git a/app/views/projects/_stat_anchor_list.html.haml b/app/views/projects/_stat_anchor_list.html.haml index a115b65938b..8bffd1396ae 100644 --- a/app/views/projects/_stat_anchor_list.html.haml +++ b/app/views/projects/_stat_anchor_list.html.haml @@ -1,8 +1,8 @@ - anchors = local_assigns.fetch(:anchors, []) - return unless anchors.any? -%ul.nav +%ul.nav.justify-content-center - anchors.each do |anchor| - %li - = link_to_if anchor.link, anchor.label, anchor.link, class: anchor.enabled ? 'stat-link' : "btn btn-#{anchor.class_modifier || 'missing'}" do + %li.nav-item + = link_to_if anchor.link, anchor.label, anchor.link, class: anchor.enabled ? 'nav-link stat-link' : "nav-link btn btn-#{anchor.class_modifier || 'missing'}" do %span.stat-text= anchor.label diff --git a/app/views/projects/artifacts/file.html.haml b/app/views/projects/artifacts/file.html.haml index 2942d618a42..aac7a1870df 100644 --- a/app/views/projects/artifacts/file.html.haml +++ b/app/views/projects/artifacts/file.html.haml @@ -5,11 +5,11 @@ .tree-holder .nav-block %ul.breadcrumb.repo-breadcrumb - %li + %li.breadcrumb-item = link_to 'Artifacts', browse_project_job_artifacts_path(@project, @build) - path_breadcrumbs do |title, path| - title = truncate(title, length: 40) - %li + %li.breadcrumb-item - if path == @path = link_to file_project_job_artifacts_path(@project, @build, path) do %strong= title @@ -22,7 +22,7 @@ .js-file-title.file-title-flex-parent = render 'projects/blob/header_content', blob: blob - .file-actions.hidden-xs + .file-actions.d-none.d-sm-block = render 'projects/blob/viewer_switcher', blob: blob .btn-group{ role: "group" }< diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index e45861ac08d..e90916e340d 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -22,7 +22,7 @@ .commit-row-title %span.item-title.str-truncated-100 = link_to_markdown commit.title, project_commit_path(@project, commit.id), class: "cdark", title: commit.title - .pull-right + .float-right = link_to commit.short_id, project_commit_path(@project, commit), class: "commit-sha" .light diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 849716a679b..a4b1b496b69 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -1,6 +1,6 @@ = render "projects/blob/breadcrumb", blob: blob -.info-well.hidden-xs +.info-well.d-none.d-sm-block .well-segment %ul.blob-commit-info = render 'projects/commits/commit', commit: @last_commit, project: @project, ref: @ref diff --git a/app/views/projects/blob/_breadcrumb.html.haml b/app/views/projects/blob/_breadcrumb.html.haml index 1c148de9678..a4fb5f6ba88 100644 --- a/app/views/projects/blob/_breadcrumb.html.haml +++ b/app/views/projects/blob/_breadcrumb.html.haml @@ -5,12 +5,12 @@ = render 'shared/ref_switcher', destination: 'blob', path: @path %ul.breadcrumb.repo-breadcrumb - %li + %li.breadcrumb-item = link_to project_tree_path(@project, @ref) do = @project.path - path_breadcrumbs do |title, path| - title = truncate(title, length: 40) - %li + %li.breadcrumb-item - if path == @path = link_to project_blob_path(@project, tree_join(@ref, path)) do %strong= title diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index c9fa90acd11..8560b72fe85 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -16,7 +16,7 @@ = text_field_tag 'file_name', params[:file_name], placeholder: "File name", required: true, class: 'form-control new-file-name js-file-path-name-input' - .pull-right.file-buttons + .float-right.file-buttons = button_tag class: 'soft-wrap-toggle btn', type: 'button', tabindex: '-1' do %span.no-wrap = custom_icon('icon_no_wrap') diff --git a/app/views/projects/blob/_header_content.html.haml b/app/views/projects/blob/_header_content.html.haml index 5d457a50c49..4bef45932d0 100644 --- a/app/views/projects/blob/_header_content.html.haml +++ b/app/views/projects/blob/_header_content.html.haml @@ -10,4 +10,4 @@ = number_to_human_size(blob.raw_size) - if blob.stored_externally? && blob.external_storage == :lfs - %span.label.label-lfs.append-right-5 LFS + %span.badge.label-lfs.append-right-5 LFS diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index 48ff66900be..e7a4e3d67cb 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -5,9 +5,9 @@ %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title= _('Create New Directory') .modal-body - = form_tag project_create_dir_path(@project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do - .form-group - = label_tag :dir_name, _('Directory name'), class: 'control-label' + = form_tag project_create_dir_path(@project, @id), method: :post, remote: false, class: 'js-create-dir-form js-quick-submit js-requires-input' do + .form-group.row + = label_tag :dir_name, _('Directory name'), class: 'col-form-label col-sm-2' .col-sm-10 = text_field_tag :dir_name, params[:dir_name], required: true, class: 'form-control' diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index 750bdef3308..4628ecff3d6 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -6,10 +6,10 @@ %h3.page-title Delete #{@blob.name} .modal-body - = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal js-delete-blob-form js-quick-submit js-requires-input' do + = form_tag project_blob_path(@project, @id), method: :delete, class: 'js-delete-blob-form js-quick-submit js-requires-input' do = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}" - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-sm-2.col-sm-10 = button_tag 'Delete file', class: 'btn btn-remove btn-remove-file' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 182d02376bf..60a49441ce8 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -5,7 +5,7 @@ %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title= title .modal-body - = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal', data: { method: method } do + = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form', data: { method: method } do .dropzone .dropzone-previews.blob-upload-dropzone-previews %p.dz-message.light diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 9d90251ab66..27cf040da7c 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -15,7 +15,7 @@ Edit file = render 'template_selectors' .file-editor - %ul.nav-links.no-bottom.js-edit-mode + %ul.nav-links.no-bottom.js-edit-mode.nav.nav-tabs %li.active = link_to '#editor' do Write @@ -24,7 +24,7 @@ = link_to '#preview', 'data-preview-url' => project_preview_blob_path(@project, @id) do = editing_preview_title(@blob.name) - = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'form-horizontal js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do + = form_tag(project_update_blob_path(@project, @id), method: :put, class: 'js-quick-submit js-requires-input js-edit-blob-form', data: blob_editor_paths) do = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = hidden_field_tag 'last_commit_sha', @last_commit_sha diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index fa091d8f6ef..39442564a2b 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -7,7 +7,7 @@ New file = render 'template_selectors' .file-editor - = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'form-horizontal js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do + = form_tag(project_create_blob_path(@project, @id), method: :post, class: 'js-edit-blob-form js-new-blob-form js-quick-submit js-requires-input', data: blob_editor_paths) do = render 'projects/blob/editor', ref: @ref = render 'shared/new_commit_form', placeholder: "Add new file" diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 0e012b5a216..5b1f2d8953b 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -12,13 +12,13 @@ = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8' do = branch.name - if branch.name == @repository.root_ref - %span.label.label-primary.prepend-left-5 default + %span.badge.badge-primary.prepend-left-5 default - elsif merged - %span.label.label-info.has-tooltip.prepend-left-5{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } } + %span.badge.badge-info.has-tooltip.prepend-left-5{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } } = s_('Branches|merged') - if protected_branch?(@project, branch) - %span.label.label-success.prepend-left-5 + %span.badge.badge-success.prepend-left-5 = s_('Branches|protected') .block-truncated @@ -28,7 +28,7 @@ = s_('Branches|Cant find HEAD commit for this branch') - if branch.name != @repository.root_ref - .divergence-graph.hidden-xs{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind), + .divergence-graph.d-none.d-sm-block{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind), default_branch: @repository.root_ref, number_commits_ahead: diverging_count_label(number_commits_ahead) } } .graph-side @@ -39,7 +39,7 @@ .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" } %span.count.count-ahead= diverging_count_label(number_commits_ahead) - .controls.hidden-xs< + .controls.d-none.d-sm-block< - if merge_project && create_mr_button?(@repository.root_ref, branch.name) = link_to create_mr_path(@repository.root_ref, branch.name), class: 'btn btn-default' do = _('Merge request') diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml index 12e5a8e8d69..398f76d379a 100644 --- a/app/views/projects/branches/_panel.html.haml +++ b/app/views/projects/branches/_panel.html.haml @@ -7,13 +7,13 @@ - return unless branches.any? -.panel.panel-default.prepend-top-10 - .panel-heading - %h4.panel-title +.card.prepend-top-10 + .card-header + %h4.card-title = panel_title %ul.content-list.all-branches - branches.first(overview_max_branches).each do |branch| = render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch) - if branches.size > overview_max_branches - .panel-footer.text-center + .card-footer.text-center = link_to show_more_text, project_branches_filtered_path(project, state: state), id: "state-#{state}", data: { state: state } diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 5dcc72d8263..d6568c9f64a 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -3,7 +3,7 @@ %div{ class: container_class } .top-area.adjust - %ul.nav-links.issues-state-filters + %ul.nav-links.issues-state-filters.nav.nav-tabs %li{ class: active_when(@mode == 'overview') }> = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches') @@ -26,7 +26,7 @@ %span.light = branches_sort_options_hash[@sort] = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable %li.dropdown-header = s_('Branches|Sort by') - branches_sort_options_hash.each do |value, title| diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index c7fc5a98ca8..65b414c8af2 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -9,14 +9,14 @@ New Branch %hr -= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-create-branch-form js-requires-input" do - .form-group - = label_tag :branch_name, nil, class: 'control-label' += form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "js-create-branch-form js-requires-input" do + .form-group.row + = label_tag :branch_name, nil, class: 'col-form-label col-sm-2' .col-sm-10 = text_field_tag :branch_name, params[:branch_name], required: true, autofocus: true, class: 'form-control js-branch-name' - .help-block.text-danger.js-branch-name-error - .form-group - = label_tag :ref, 'Create from', class: 'control-label' + .form-text.text-muted.text-danger.js-branch-name-error + .form-group.row + = label_tag :ref, 'Create from', class: 'col-form-label col-sm-2' .col-sm-10.create-from .dropdown = hidden_field_tag :ref, default_ref @@ -24,7 +24,7 @@ .text-left.dropdown-toggle-text= default_ref = icon('chevron-down') = render 'shared/ref_dropdown', dropdown_class: 'wide' - .help-block Existing branch name, tag, or commit SHA + .form-text.text-muted Existing branch name, tag, or commit SHA .form-actions = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index f49f6e630d2..c75093c4c24 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -7,7 +7,7 @@ = sprite_icon('download') = icon("caret-down") %span.sr-only= _('Select Archive Format') - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } %li.dropdown-header #{ _('Source code') } %li diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 2e86a7d36d7..84245d72f4a 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -11,7 +11,7 @@ %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') } = icon('plus') = icon("caret-down") - %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown + %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown - if can_create_issue || merge_project || can_create_project_snippet %li.dropdown-header= _('This project') diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 9126476e79e..44c1453e239 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -41,14 +41,14 @@ .label-container - if job.tags.any? - job.tags.each do |tag| - %span.label.label-primary + %span.badge.badge-primary = tag - if job.try(:trigger_request) - %span.label.label-info triggered + %span.badge.badge-info triggered - if job.try(:allow_failure) - %span.label.label-danger allowed to fail + %span.badge.badge-danger allowed to fail - if job.action? - %span.label.label-info manual + %span.badge.badge-info manual - if pipeline_link %td @@ -93,7 +93,7 @@ #{job.coverage}% %td - .pull-right + .float-right - if can?(current_user, :read_build, job) && job.artifacts? = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: 'Download artifacts', class: 'btn btn-build' do = sprite_icon('download') diff --git a/app/views/projects/ci/lints/show.html.haml b/app/views/projects/ci/lints/show.html.haml index 6ca8152183d..cbda6bf2107 100644 --- a/app/views/projects/ci/lints/show.html.haml +++ b/app/views/projects/ci/lints/show.html.haml @@ -3,22 +3,21 @@ - content_for :library_javascripts do = page_specific_javascript_tag('lib/ace.js') -%h2 Check your .gitlab-ci.yml +%h2.pt-3.pb-3 Check your .gitlab-ci.yml .project-ci-linter - .row - = form_tag project_ci_lint_path(@project), method: :post do - .form-group - .col-sm-12 - .file-holder - .js-file-title.file-title.clearfix - Content of .gitlab-ci.yml - #ci-editor.ci-editor= @content - = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true) + = form_tag project_ci_lint_path(@project), method: :post do + .row .col-sm-12 - .pull-left.prepend-top-10 + .file-holder + .js-file-title.file-title.clearfix + Content of .gitlab-ci.yml + #ci-editor.ci-editor= @content + = text_area_tag(:content, @content, class: 'hidden form-control span1', rows: 7, require: true) + .col-sm-12 + .float-left.prepend-top-10 = submit_tag('Validate', class: 'btn btn-success submit-yml') - .pull-right.prepend-top-10 + .float-right.prepend-top-10 = button_tag('Clear', type: 'button', class: 'btn btn-default clear-yml') .row.prepend-top-20 diff --git a/app/views/projects/clusters/_advanced_settings.html.haml b/app/views/projects/clusters/_advanced_settings.html.haml index 14979bee714..e9bdc54364b 100644 --- a/app/views/projects/clusters/_advanced_settings.html.haml +++ b/app/views/projects/clusters/_advanced_settings.html.haml @@ -7,7 +7,7 @@ - link_gke = link_to(s_('ClusterIntegration|Google Kubernetes Engine'), @cluster.gke_cluster_url, target: '_blank', rel: 'noopener noreferrer') = s_('ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}').html_safe % { link_gke: link_gke } - .well.form-group + .card.form-group %label.text-danger = s_('ClusterIntegration|Remove Kubernetes cluster integration') %p diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml index 5f49d03b1bb..b8a3556a206 100644 --- a/app/views/projects/clusters/_empty_state.html.haml +++ b/app/views/projects/clusters/_empty_state.html.haml @@ -1,7 +1,7 @@ .row.empty-state - .col-xs-12 + .col-12 .svg-content= image_tag 'illustrations/clusters_empty.svg' - .col-xs-12 + .col-12 .text-content %h4.text-center= s_('ClusterIntegration|Integrate Kubernetes cluster automation') - link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml index 78cd687ef93..877e0cc876c 100644 --- a/app/views/projects/clusters/gcp/_show.html.haml +++ b/app/views/projects/clusters/gcp/_show.html.haml @@ -3,8 +3,8 @@ = s_('ClusterIntegration|Kubernetes cluster name') .input-group %input.form-control.cluster-name.js-select-on-focus{ value: @cluster.name, readonly: true } - %span.input-group-btn - = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'), class: 'btn-default') + %span.input-group-append + = clipboard_button(text: @cluster.name, title: s_('ClusterIntegration|Copy Kubernetes cluster name'), class: 'input-group-text btn-default') = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_errors(@cluster) @@ -14,22 +14,22 @@ = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') .input-group = platform_kubernetes_field.text_field :api_url, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|API URL'), readonly: true - %span.input-group-btn - = clipboard_button(text: @cluster.platform_kubernetes.api_url, title: s_('ClusterIntegration|Copy API URL'), class: 'btn-default') + %span.input-group-append + = clipboard_button(text: @cluster.platform_kubernetes.api_url, title: s_('ClusterIntegration|Copy API URL'), class: 'input-group-text btn-default') .form-group = platform_kubernetes_field.label :ca_cert, s_('ClusterIntegration|CA Certificate') .input-group = platform_kubernetes_field.text_area :ca_cert, class: 'form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Certificate Authority bundle (PEM format)'), readonly: true - %span.input-group-addon.clipboard-addon - = clipboard_button(text: @cluster.platform_kubernetes.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'), class: 'btn-blank') + %span.input-group-append.clipboard-addon + = clipboard_button(text: @cluster.platform_kubernetes.ca_cert, title: s_('ClusterIntegration|Copy CA Certificate'), class: 'input-group-text btn-blank') .form-group = platform_kubernetes_field.label :token, s_('ClusterIntegration|Token') .input-group = platform_kubernetes_field.text_field :token, class: 'form-control js-cluster-token js-select-on-focus', type: 'password', placeholder: s_('ClusterIntegration|Token'), readonly: true - %span.input-group-btn - %button.btn.btn-default.js-show-cluster-token{ type: 'button' } + %span.input-group-append + %button.btn.btn-default.input-group-text.js-show-cluster-token{ type: 'button' } = s_('ClusterIntegration|Show') = clipboard_button(text: @cluster.platform_kubernetes.token, title: s_('ClusterIntegration|Copy Token'), class: 'btn-default') diff --git a/app/views/projects/clusters/gcp/login.html.haml b/app/views/projects/clusters/gcp/login.html.haml index ff046c59a7a..f1771349a53 100644 --- a/app/views/projects/clusters/gcp/login.html.haml +++ b/app/views/projects/clusters/gcp/login.html.haml @@ -10,7 +10,7 @@ = render 'projects/clusters/dropdown', dropdown_text: s_('ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine') = render 'header' .row - .col-sm-8.col-sm-offset-4.signin-with-google + .col-sm-8.offset-sm-4.signin-with-google - if @authorize_url = link_to @authorize_url do = image_tag('auth_buttons/signin_with_google.png', width: '191px') diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml index ebbf7e775c7..77d7a055474 100644 --- a/app/views/projects/clusters/user/_show.html.haml +++ b/app/views/projects/clusters/user/_show.html.haml @@ -17,9 +17,10 @@ = platform_kubernetes_field.label :token, s_('ClusterIntegration|Token') .input-group = platform_kubernetes_field.text_field :token, class: 'form-control js-cluster-token', type: 'password', placeholder: s_('ClusterIntegration|Token'), autocomplete: 'off' - %span.input-group-addon.clipboard-addon - %button.js-show-cluster-token.btn-blank{ type: 'button' } - = s_('ClusterIntegration|Show') + %span.input-group-append.clipboard-addon + .input-group-text + %button.js-show-cluster-token.btn-blank{ type: 'button' } + = s_('ClusterIntegration|Show') .form-group = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') diff --git a/app/views/projects/commit/_ajax_signature.html.haml b/app/views/projects/commit/_ajax_signature.html.haml index 36b28c731a1..eb677cff5f0 100644 --- a/app/views/projects/commit/_ajax_signature.html.haml +++ b/app/views/projects/commit/_ajax_signature.html.haml @@ -1,2 +1,2 @@ - if commit.has_signature? - %a{ href: 'javascript:void(0)', tabindex: 0, class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'auto top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } } + %a{ href: 'javascript:void(0)', tabindex: 0, class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'top', title: 'GPG signature (loading...)', 'commit-sha' => commit.sha } } diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index 21e4664d4e4..430bc8f59f9 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -20,9 +20,9 @@ .modal-body - if description %p.append-bottom-20= description - = form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do - .form-group.branch - = label_tag 'start_branch', branch_label, class: 'control-label' + = form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "js-#{type}-form js-requires-input" do + .form-group.row.branch + = label_tag 'start_branch', branch_label, class: 'col-form-label col-sm-2' .col-sm-10 = hidden_field_tag :start_branch, @project.default_branch, id: 'start_branch' = dropdown_tag(@project.default_branch, options: { title: s_("BranchSwitcherTitle|Switch branch"), filter: true, placeholder: s_("BranchSwitcherPlaceholder|Search branches"), toggle_class: 'js-project-refs-dropdown dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "start_branch", selected: @project.default_branch, start_branch: @project.default_branch, refs_url: project_branches_path(@project), submit_form_on_click: false } }) diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index 7338468967f..f6666921a25 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -1,10 +1,10 @@ -%ul.nav-links.no-top.no-bottom.commit-ci-menu +%ul.nav-links.no-top.no-bottom.commit-ci-menu.nav.nav-tabs = nav_link(path: 'commit#show') do = link_to project_commit_path(@project, @commit.id) do Changes - %span.badge= @diffs.size + %span.badge.badge-pill= @diffs.size - if can?(current_user, :read_pipeline, @project) = nav_link(path: 'commit#pipelines') do = link_to pipelines_project_commit_path(@project, @commit.id) do Pipelines - %span.badge.js-pipelines-mr-count= @commit.pipelines.size + %span.badge.badge-pill.js-pipelines-mr-count= @commit.pipelines.size diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 1bffb3e8bf0..886dd73c33b 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -7,7 +7,7 @@ #{ s_('CommitBoxTitle|Commit') } %span.commit-sha= @commit.short_id = clipboard_button(text: @commit.id, title: _("Copy commit SHA to clipboard")) - %span.hidden-xs authored + %span.d-none.d-sm-inline authored #{time_ago_with_tooltip(@commit.authored_date)} %span= s_('ByAuthor|by') = author_avatar(@commit, size: 24) @@ -21,17 +21,17 @@ .header-action-buttons - if defined?(@notes_count) && @notes_count > 0 - %span.btn.disabled.btn-grouped.hidden-xs.append-right-10 + %span.btn.disabled.btn-grouped.d-none.d-sm-block.append-right-10 = icon('comment') = @notes_count - = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 hidden-xs hidden-sm" do + = link_to project_tree_path(@project, @commit), class: "btn btn-default append-right-10 d-none d-sm-none d-md-inline" do #{ _('Browse files') } .dropdown.inline %a.btn.btn-default.dropdown-toggle{ data: { toggle: "dropdown" } } %span= _('Options') = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right - %li.visible-xs-block.visible-sm-block + %ul.dropdown-menu.dropdown-menu-right + %li.d-block.d-sm-none.d-md-none = link_to project_tree_path(@project, @commit) do #{ _('Browse Files') } - if can_collaborate && !@commit.has_been_reverted?(current_user) diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml index aac020b42c5..c4d986ef742 100644 --- a/app/views/projects/commit/_signature_badge.html.haml +++ b/app/views/projects/commit/_signature_badge.html.haml @@ -24,5 +24,5 @@ = link_to('Learn more about signing commits', help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link') -%a{ href: 'javascript:void(0)', tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'auto top', title: title, content: content } } +%a{ href: 'javascript:void(0)', tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } } = label diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml index 8611129b356..a91e31afc2b 100644 --- a/app/views/projects/commit/branches.html.haml +++ b/app/views/projects/commit/branches.html.haml @@ -6,7 +6,8 @@ - if @branches.any? || @tags.any? || @tags_limit_exceeded %span - = link_to "…", "#", class: "js-details-expand label label-gray" + = link_to "#", class: "js-details-expand label label-gray ref-name" do + = sprite_icon('ellipsis_h', size: 12, css_class: 'vertical-align-middle') %span.js-details-content.hide = commit_branches_links(@project, @branches) - if @tags_limit_exceeded diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index c390c9c4469..12b27eb9b66 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -18,7 +18,7 @@ = cache(cache_key, expires_in: 1.day) do %li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" } - .avatar-cell.hidden-xs + .avatar-cell.d-none.d-sm-block = author_avatar(commit, size: 36) .commit-detail.flex-list @@ -27,14 +27,14 @@ = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title" - else = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title") - %span.commit-row-message.visible-xs-inline + %span.commit-row-message.d-block.d-sm-none · = commit.short_id - if commit.status(ref) - .visible-xs-inline + .d-block.d-sm-none = render_commit_status(commit, ref: ref) - if commit.description? - %button.text-expander.hidden-xs.js-toggle-button{ type: "button" } ... + %button.text-expander.d-none.d-sm-inline-block.js-toggle-button{ type: "button" } ... .commiter - commit_author_link = commit_author_link(commit, avatar: false, size: 24) @@ -46,7 +46,7 @@ %pre.commit-row-description.js-toggle-content.prepend-top-8.append-bottom-8 = preserve(markdown_field(commit, :description)) - .commit-actions.flex-row.hidden-xs + .commit-actions.flex-row.d-none.d-sm-flex - if request.xhr? = render partial: 'projects/commit/signature', object: commit.signature - else diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml index 6f5835cb9be..8f8eb2c3d5a 100644 --- a/app/views/projects/commits/_commit_list.html.haml +++ b/app/views/projects/commits/_commit_list.html.haml @@ -1,8 +1,8 @@ - commits, hidden = limited_commits(@commits) - commits = Commit.decorate(commits, @project) -.panel.panel-default - .panel-heading +.card + .card-header Commits (#{@commits.count}) - if hidden > 0 %ul.content-list diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml index 26385d2f534..caaff082cc3 100644 --- a/app/views/projects/commits/_inline_commit.html.haml +++ b/app/views/projects/commits/_inline_commit.html.haml @@ -4,5 +4,5 @@ %span.str-truncated = link_to_markdown_field(commit, :title, project_commit_path(project, commit.id), class: "commit-row-message") - .pull-right + .float-right #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 483cca11df9..9d254463fb6 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -15,7 +15,7 @@ %ul.breadcrumb.repo-breadcrumb = commits_breadcrumbs - .tree-controls.hidden-xs.hidden-sm + .tree-controls.d-none.d-sm-none.d-md-block - if @merge_request.present? .control = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn' diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index 40cdf96e76d..07112c98804 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,28 +1,29 @@ = form_tag project_compare_index_path(@project), method: :post, class: 'form-inline js-requires-input js-signature-container', data: { 'signatures-path' => signatures_namespace_project_compare_index_path } do - .clearfix - - if params[:to] && params[:from] - .compare-switch-container - = link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions' - .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown - .input-group.inline-input-group - %span.input-group-addon + - if params[:to] && params[:from] + .compare-switch-container + = link_to icon('exchange'), { from: params[:to], to: params[:from] }, class: 'commits-compare-switch has-tooltip btn btn-white', title: 'Swap revisions' + .form-group.dropdown.compare-form-group.to.js-compare-to-dropdown + .input-group.inline-input-group + %span.input-group-prepend + .input-group-text = s_("CompareBranches|Source") - = hidden_field_tag :to, params[:to] - = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do - .dropdown-toggle-text.str-truncated= params[:to] || _("Select branch/tag") - = render 'shared/ref_dropdown' - .compare-ellipsis.inline ... - .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown - .input-group.inline-input-group - %span.input-group-addon + = hidden_field_tag :to, params[:to] + = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do + .dropdown-toggle-text.str-truncated= params[:to] || _("Select branch/tag") + = render 'shared/ref_dropdown' + .compare-ellipsis.inline ... + .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown + .input-group.inline-input-group + %span.input-group-prepend + .input-group-text = s_("CompareBranches|Target") - = hidden_field_tag :from, params[:from] - = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do - .dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag") - = render 'shared/ref_dropdown' - - = button_tag s_("CompareBranches|Compare"), class: "btn btn-create commits-compare-btn" - - if @merge_request.present? - = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn' - - elsif create_mr_button? - = link_to _("Create merge request"), create_mr_path, class: 'prepend-left-10 btn' + = hidden_field_tag :from, params[:from] + = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do + .dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag") + = render 'shared/ref_dropdown' + + = button_tag s_("CompareBranches|Compare"), class: "btn btn-create commits-compare-btn" + - if @merge_request.present? + = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn' + - elsif create_mr_button? + = link_to _("Create merge request"), create_mr_path, class: 'prepend-left-10 btn' diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 8da55664878..b6bebbabed0 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -10,7 +10,7 @@ = render "projects/commits/commit_list" = render "projects/diffs/diffs", diffs: @diffs, environment: @environment - else - .light-well + .card.bg-light .center %h4 = s_("CompareBranches|There isn't anything to compare.") diff --git a/app/views/projects/cycle_analytics/_overview.html.haml b/app/views/projects/cycle_analytics/_overview.html.haml index 9007f2c24ba..5b0d73b8c68 100644 --- a/app/views/projects/cycle_analytics/_overview.html.haml +++ b/app/views/projects/cycle_analytics/_overview.html.haml @@ -1,7 +1,7 @@ .cycle-analytics-overview .container .row - .col-md-10.col-md-offset-1 + .col-md-10.offset-md-1 .row.overview-details .col-md-6.overview-text %h4 Introducing Cycle Analytics diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index 5041f322612..bdf021fd87f 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -8,21 +8,21 @@ "v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" } = icon("spinner spin", "v-show" => "isLoading") .wrapper{ "v-show" => "!isLoading && !hasError" } - .panel.panel-default - .panel-heading + .card + .card-header {{ __('Pipeline Health') }} .content-block .container-fluid .row - .col-sm-3.col-xs-12.column{ "v-for" => "item in state.summary" } + .col-sm-3.col-12.column{ "v-for" => "item in state.summary" } %h3.header {{ item.value }} %p.text {{ item.title }} - .col-sm-3.col-xs-12.column + .col-sm-3.col-12.column .dropdown.inline.js-ca-dropdown %button.dropdown-menu-toggle{ "data-toggle" => "dropdown", :type => "button" } %span.dropdown-label {{ n__('Last %d day', 'Last %d days', 30) }} %i.fa.fa-chevron-down - %ul.dropdown-menu.dropdown-menu-align-right + %ul.dropdown-menu.dropdown-menu-right %li %a{ "href" => "#", "data-value" => "7" } {{ n__('Last %d day', 'Last %d days', 7) }} @@ -33,8 +33,8 @@ %a{ "href" => "#", "data-value" => "90" } {{ n__('Last %d day', 'Last %d days', 90) }} .stage-panel-container - .panel.panel-default.stage-panel - .panel-heading + .card.stage-panel + .card-header %nav.col-headers %ul %li.stage-header diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index c363180d0db..5ad8091a02b 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -1,24 +1,24 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @deploy_keys.new_key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input" } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @deploy_keys.new_key], url: namespace_project_deploy_keys_path, html: { class: "js-requires-input container" } do |f| = form_errors(@deploy_keys.new_key) - .form-group + .form-group.row = f.label :title, class: "label-light" = f.text_field :title, class: 'form-control', required: true - .form-group + .form-group.row = f.label :key, class: "label-light" = f.text_area :key, class: "form-control", rows: 5, required: true - .form-group + .form-group.row %p.light.append-bottom-0 Paste a machine public key here. Read more about how to generate it = link_to "here", help_page_path("ssh/README") = f.fields_for :deploy_keys_projects do |deploy_keys_project_form| - .form-group - .checkbox - = deploy_keys_project_form.label :can_push do - = deploy_keys_project_form.check_box :can_push - %strong Write access allowed - .form-group + .form-group.row + = deploy_keys_project_form.label :can_push do + = deploy_keys_project_form.check_box :can_push + %strong Write access allowed + .form-group.row %p.light.append-bottom-0 Allow this key to push to repository as well? (Default only allows pull access.) - = f.submit "Add key", class: "btn-create btn" + .form-group.row + = f.submit "Add key", class: "btn-create btn" diff --git a/app/views/projects/deploy_keys/edit.html.haml b/app/views/projects/deploy_keys/edit.html.haml index cd910b82b57..e009b6fef0e 100644 --- a/app/views/projects/deploy_keys/edit.html.haml +++ b/app/views/projects/deploy_keys/edit.html.haml @@ -3,7 +3,7 @@ %hr %div - = form_for [@project.namespace.becomes(Namespace), @project, @deploy_key], html: { class: 'form-horizontal js-requires-input' } do |f| + = form_for [@project.namespace.becomes(Namespace), @project, @deploy_key], html: { class: 'js-requires-input' } do |f| = render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key } .form-actions = f.submit 'Save changes', class: 'btn-save btn' diff --git a/app/views/projects/deploy_tokens/_revoke_modal.html.haml b/app/views/projects/deploy_tokens/_revoke_modal.html.haml index 085964fe22e..ace3480c815 100644 --- a/app/views/projects/deploy_tokens/_revoke_modal.html.haml +++ b/app/views/projects/deploy_tokens/_revoke_modal.html.haml @@ -2,7 +2,7 @@ .modal-dialog .modal-content .modal-header - %h4.modal-title.pull-left + %h4.modal-title.float-left = s_('DeployTokens|Revoke') %b #{token.name}? %button.close{ 'aria-label' => _('Close'), 'data-dismiss' => 'modal', type: 'button' } diff --git a/app/views/projects/deploy_tokens/_table.html.haml b/app/views/projects/deploy_tokens/_table.html.haml index 5013a9b250d..91466a6736b 100644 --- a/app/views/projects/deploy_tokens/_table.html.haml +++ b/app/views/projects/deploy_tokens/_table.html.haml @@ -24,7 +24,7 @@ - else %span.token-never-expires-label Never %td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>" - %td= link_to s_('DeployTokens|Revoke'), "#", class: "btn btn-danger pull-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"} + %td= link_to s_('DeployTokens|Revoke'), "#", class: "btn btn-danger float-right", data: { toggle: "modal", target: "#revoke-modal-#{token.id}"} = render 'projects/deploy_tokens/revoke_modal', token: token, project: project - else .settings-message.text-center diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index e2baaa625ae..e0ecf56525a 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -6,7 +6,7 @@ %button.dropdown.dropdown-new.btn.btn-default{ type: 'button', 'data-toggle' => 'dropdown' } = custom_icon('icon_play') = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right + %ul.dropdown-menu.dropdown-menu-right - actions.each do |action| - next unless can?(current_user, :update_build, action) %li diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 9f420ee86f7..077c6c68f7e 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -6,16 +6,16 @@ .content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed{ class: ("diff-files-changed-merge-request" if merge_request) } .files-changed-inner - .inline-parallel-buttons.hidden-xs.hidden-sm + .inline-parallel-buttons.d-none.d-sm-none.d-md-block - if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? } = link_to 'Expand all', url_for(safe_params.merge(expanded: 1, format: nil)), class: 'btn btn-default' - if show_whitespace_toggle - if current_controller?(:commit) - = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs') + = commit_diff_whitespace_link(diffs.project, @commit, class: 'd-none d-sm-inline-block') - elsif current_controller?('projects/merge_requests/diffs') - = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs') + = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'd-none d-sm-inline-block') - elsif current_controller?(:compare) - = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs') + = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'd-none d-sm-inline-block') .btn-group = inline_diff_btn = parallel_diff_btn diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 47bfcb21cf4..b4df654c839 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -10,7 +10,7 @@ - unless diff_file.submodule? - blob = diff_file.blob - .file-actions.hidden-xs + .file-actions.d-none.d-sm-block - if blob&.readable_text? = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index dbeddf6689a..4cb04d744dc 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -37,4 +37,4 @@ #{diff_file.a_mode} → #{diff_file.b_mode} - if diff_file.stored_externally? && diff_file.external_storage == :lfs - %span.label.label-lfs.append-right-5 LFS + %span.badge.label-lfs.append-right-5 LFS diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index 6fd6018dea3..aa1112c3313 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -2,7 +2,7 @@ - sum_removed_lines = diff_files.sum(&:removed_lines) .commit-stat-summary.dropdown Showing - %button.diff-stats-summary-toggler.js-diff-stats-dropdown{ type: "button", data: { toggle: "dropdown" } }< + %button.diff-stats-summary-toggler.js-diff-stats-dropdown{ type: "button", data: { toggle: "dropdown", display: "static" } }< = pluralize(diff_files.size, "changed file") = icon("caret-down", class: "prepend-left-5") %span.diff-stats-additions-deletions-expanded#diff-stats @@ -10,7 +10,7 @@ %strong.cgreen= pluralize(sum_added_lines, 'addition') and %strong.cred= pluralize(sum_removed_lines, 'deletion') - .diff-stats-additions-deletions-collapsed.pull-right.hidden-xs.hidden-sm{ "aria-hidden": "true", "aria-describedby": "diff-stats" } + .diff-stats-additions-deletions-collapsed.float-right.d-none.d-sm-none{ "aria-hidden": "true", "aria-describedby": "diff-stats" } %strong.cgreen< +#{sum_added_lines} %strong.cred< diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index da34a83d8e0..abe494f2974 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -1,7 +1,7 @@ .alert.alert-warning %h4 Too many changes to show. - .pull-right + .float-right - if current_controller?(:commit) = link_to "Plain diff", project_commit_path(@project, @commit, format: :diff), class: "btn btn-sm" = link_to "Email patch", project_commit_path(@project, @commit, format: :patch), class: "btn btn-sm" diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 0994498c6be..79b22766bc7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -40,7 +40,7 @@ .form-group = f.label :tag_list, "Tags", class: 'label-light' = f.text_field :tag_list, value: @project.tag_list.sort.join(', '), maxlength: 2000, class: "form-control" - %p.help-block Separate tags with commas. + %p.form-text.text-muted Separate tags with commas. %fieldset.features %h5.prepend-top-0= _("Project avatar") .form-group @@ -54,7 +54,7 @@ %button.btn.js-choose-project-avatar-button{ type: 'button' }= _("Choose file...") %span.file_name.prepend-left-default.js-avatar-filename= _("No file chosen") = f.file_field :avatar, class: "js-project-avatar-input hidden" - .help-block= _("The maximum file size allowed is 200KB.") + .form-text.text-muted= _("The maximum file size allowed is 200KB.") - if @project.avatar? %hr = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted" @@ -142,8 +142,9 @@ %span Path .form-group .input-group - .input-group-addon - #{URI.join(root_url, @project.namespace.full_path)}/ + .input-group-prepend + .input-group-text + #{URI.join(root_url, @project.namespace.full_path)}/ = f.text_field :path, class: 'form-control' %ul %li Be careful. Renaming a project's repository can have unintended side effects. diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index a066f9f4cca..bf6fc8af12d 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -22,7 +22,7 @@ %hr %p - - link_to_auto_devops_settings = link_to(s_('AutoDevOps|enable Auto DevOps (Beta)'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings')) + - link_to_auto_devops_settings = link_to(s_('AutoDevOps|enable Auto DevOps'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings')) - link_to_add_kubernetes_cluster = link_to(s_('AutoDevOps|add a Kubernetes cluster'), new_project_cluster_path(@project)) = s_('AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}.').html_safe % { link_to_auto_devops_settings: link_to_auto_devops_settings, link_to_add_kubernetes_cluster: link_to_add_kubernetes_cluster } @@ -44,14 +44,14 @@ .git-empty %fieldset %h5 Git global setup - %pre.light-well + %pre.card.bg-light :preserve git config --global user.name "#{h git_user_name}" git config --global user.email "#{h git_user_email}" %fieldset %h5 Create a new repository - %pre.light-well + %pre.card.bg-light :preserve git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')} cd #{h @project.path} @@ -64,7 +64,7 @@ %fieldset %h5 Existing folder - %pre.light-well + %pre.card.bg-light :preserve cd existing_folder git init @@ -77,7 +77,7 @@ %fieldset %h5 Existing Git repository - %pre.light-well + %pre.card.bg-light :preserve cd existing_repo git remote rename origin old-origin @@ -89,4 +89,4 @@ - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove pull-right" + = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right" diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml index a3467eb6f05..a966bfb2dd9 100644 --- a/app/views/projects/find_file/show.html.haml +++ b/app/views/projects/find_file/show.html.haml @@ -5,10 +5,10 @@ .tree-ref-holder = render 'shared/ref_switcher', destination: 'find_file', path: @path %ul.breadcrumb.repo-breadcrumb - %li + %li.breadcrumb-item = link_to project_tree_path(@project, @ref) do = @project.path - %li.file-finder + %li.file-finder.breadcrumb-item %input#file_find.form-control.file-finder-input{ type: "text", placeholder: _('Find by path'), autocomplete: 'off' } .tree-content-holder diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index 21a4702a2a9..57afc7ac9c3 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -16,7 +16,7 @@ - else = sort_title_recently_created = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right + %ul.dropdown-menu.dropdown-menu-right %li - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id] = link_to page_filter_path(sort: sort_value_recently_created, without: excluded_filters) do diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 620fd1906ba..639efd34a74 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -35,10 +35,10 @@ .label-container - if generic_commit_status.tags.any? - generic_commit_status.tags.each do |tag| - %span.label.label-primary + %span.badge.badge-primary = tag - if retried - %span.label.label-warning retried + %span.badge.badge-warning retried - if pipeline_link %td diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 14c47a5d91c..983cb187c2f 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -14,7 +14,7 @@ = icon('circle') = language[:label] - .pull-right + .float-right = language[:value] \% .col-md-8 diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index c81ee6874e3..f1b14d4c4d1 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -24,4 +24,4 @@ .graphs.row #contributors-master #contributors.clearfix - %ol.contributors-list.clearfix + %ol.contributors-list.row diff --git a/app/views/projects/hook_logs/_index.html.haml b/app/views/projects/hook_logs/_index.html.haml index 8096d9530c3..3e54c3ca9f8 100644 --- a/app/views/projects/hook_logs/_index.html.haml +++ b/app/views/projects/hook_logs/_index.html.haml @@ -18,8 +18,8 @@ %tr %td = render partial: 'shared/hook_logs/status_label', locals: { hook_log: hook_log } - %td.hidden-xs - %span.label.label-gray.deploy-project-label + %td.d-none.d-sm-block + %span.badge.badge-gray.deploy-project-label = hook_log.trigger.singularize.titleize %td = truncate(hook_log.url, length: 50) diff --git a/app/views/projects/hook_logs/show.html.haml b/app/views/projects/hook_logs/show.html.haml index 1cf4105bd27..e51efa85df0 100644 --- a/app/views/projects/hook_logs/show.html.haml +++ b/app/views/projects/hook_logs/show.html.haml @@ -4,6 +4,6 @@ Request details .col-lg-9 - = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default pull-right prepend-left-10" + = link_to 'Resend Request', retry_project_hook_hook_log_path(@project, @hook, @hook_log), class: "btn btn-default float-right prepend-left-10" = render partial: 'shared/hook_logs/content', locals: { hook_log: @hook_log } diff --git a/app/views/projects/hooks/edit.html.haml b/app/views/projects/hooks/edit.html.haml index dcc1f0e3fbe..c31aef60453 100644 --- a/app/views/projects/hooks/edit.html.haml +++ b/app/views/projects/hooks/edit.html.haml @@ -13,7 +13,7 @@ = f.submit 'Save changes', class: 'btn btn-create' = render 'shared/web_hooks/test_button', triggers: ProjectHook.triggers, hook: @hook - = link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove pull-right', data: { confirm: 'Are you sure?' } + = link_to 'Remove', project_hook_path(@project, @hook), method: :delete, class: 'btn btn-remove float-right', data: { confirm: 'Are you sure?' } %hr diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 778ff91362d..16c4f21279d 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -5,14 +5,14 @@ %hr - if @project.import_failed? - .panel.panel-danger - .panel-heading The repository could not be imported. - .panel-body + .card.bg-danger + .card-header The repository could not be imported. + .card-body %pre :preserve #{h(sanitize_repo_path(@project, @project.import_error))} -= form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f| += form_for @project, url: project_import_path(@project), method: :post do |f| = render "shared/import_form", f: f .form-actions diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 8b011af78eb..2c5ffd85372 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,2 +1,2 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form common-note-form js-quick-submit js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'issue-form common-note-form js-quick-submit js-requires-input' } do |f| = render 'shared/issuable/form', f: f, issuable: @issue diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index e27f5658e87..8a14146cb87 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -12,25 +12,25 @@ = confidential_icon(issue) = link_to issue.title, issue_path(issue) - if issue.tasks? - %span.task-status.hidden-xs + %span.task-status.d-none.d-sm-inline-block = issue.task_status .issuable-info %span.issuable-reference #{issuable_reference(issue)} - %span.issuable-authored.hidden-xs + %span.issuable-authored.d-none.d-sm-inline-block · opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)} - if issue.milestone - %span.issuable-milestone.hidden-xs + %span.issuable-milestone.d-none.d-sm-inline-block - = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_due_date(issue.milestone) } do + = link_to project_issues_path(issue.project, milestone_title: issue.milestone.title), data: { html: 'true', toggle: 'tooltip', title: milestone_tooltip_due_date(issue.milestone) } do = icon('clock-o') = issue.milestone.title - if issue.due_date - %span.issuable-due-date.hidden-xs.has-tooltip{ class: "#{'cred' if issue.overdue?}", title: _('Due date') } + %span.issuable-due-date.d-none.d-sm-inline-block.has-tooltip{ class: "#{'cred' if issue.overdue?}", title: _('Due date') } = icon('calendar') = issue.due_date.to_s(:medium) @@ -50,5 +50,5 @@ = render 'shared/issuable_meta_data', issuable: issue - .pull-right.issuable-updated-at.hidden-xs + .float-right.issuable-updated-at.d-none.d-sm-inline-block %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')} diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 4b8bf578b28..76438fae663 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -14,7 +14,7 @@ = icon('spinner', class: 'fa-spin') %span.text Checking branch availability… - .btn-group.available.hide + .btn-group.available.hidden %button.btn.js-create-merge-request.btn-success.btn-inverted{ type: 'button', data: { action: data_action } } = value @@ -22,7 +22,7 @@ = icon('caret-down') .droplab-dropdown - %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-align-right.gl-show-field-errors{ data: { dropdown: true } } + %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-right.gl-show-field-errors{ data: { dropdown: true } } - if can_create_merge_request %li.droplab-item-selected{ role: 'button', data: { value: 'create-mr', text: _('Create merge request') } } .menu-item @@ -40,13 +40,13 @@ %label{ for: 'new-branch-name' } = _('Branch name') %input#new-branch-name.js-branch-name.form-control{ type: 'text', placeholder: "#{@issue.to_branch_name}", value: "#{@issue.to_branch_name}" } - %span.js-branch-message.help-block + %span.js-branch-message.form-text.text-muted .form-group %label{ for: 'source-name' } = _('Source (branch or tag)') %input#source-name.js-ref.ref.form-control{ type: 'text', placeholder: "#{@project.default_branch}", value: "#{@project.default_branch}", data: { value: "#{@project.default_branch}" } } - %span.js-ref-message.help-block + %span.js-ref-message.form-text.text-muted .form-group %button.btn.btn-success.js-create-target{ type: 'button', data: { action: 'create-mr' } } diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index f1fc1c2316d..2d036bd4e3e 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -12,12 +12,12 @@ .detail-page-header .detail-page-header-body .issuable-status-box.status-box.status-box-issue-closed{ class: issue_button_visibility(@issue, false) } - = sprite_icon('mobile-issue-close', size: 16, css_class: 'hidden-sm hidden-md hidden-lg') - %span.hidden-xs + = sprite_icon('mobile-issue-close', size: 16, css_class: 'd-block d-sm-none') + %span.d-none.d-sm-block Closed .issuable-status-box.status-box.status-box-open{ class: issue_button_visibility(@issue, true) } - = sprite_icon('issue-open-m', size: 16, css_class: 'hidden-sm hidden-md hidden-lg') - %span.hidden-xs Open + = sprite_icon('issue-open-m', size: 16, css_class: 'd-block d-sm-none') + %span.d-none.d-sm-block Open .issuable-meta - if @issue.confidential @@ -26,15 +26,15 @@ .issuable-warning-icon.inline= sprite_icon('lock', size: 16, css_class: 'icon') = issuable_meta(@issue, @project, "Issue") - %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } + %a.btn.btn-default.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } = icon('angle-double-left') .detail-page-header-actions.js-issuable-actions .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } + %button.btn.btn-default.float-left.d-md-none.d-lg-none.d-xl-none{ type: "button", data: { toggle: "dropdown" } } Options = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right.hidden-lg + .dropdown-menu.dropdown-menu-right.d-lg-none.d-xl-none %ul - unless current_user == @issue.author %li= link_to 'Report abuse', new_abuse_report_path(user_id: @issue.author.id, ref_url: issue_url(@issue)) @@ -51,9 +51,9 @@ = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue - if can_report_spam - = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' + = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'd-none d-sm-none d-md-block btn btn-grouped btn-spam', title: 'Submit as spam' - if can_create_issue - = link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do + = link_to new_project_issue_path(@project), class: 'd-none d-sm-none d-md-block btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do New issue .issue-details.issuable-details diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml index 311934d9c33..ea552c73c92 100644 --- a/app/views/projects/jobs/_empty_state.html.haml +++ b/app/views/projects/jobs/_empty_state.html.haml @@ -5,10 +5,10 @@ - action = local_assigns.fetch(:action, nil) .row.empty-state - .col-xs-12 + .col-12 .svg-content{ class: illustration_size } = image_tag illustration - .col-xs-12 + .col-12 .text-content %h4.text-center= title - if content diff --git a/app/views/projects/jobs/_header.html.haml b/app/views/projects/jobs/_header.html.haml index 83a2af1dc74..b83e8dddccb 100644 --- a/app/views/projects/jobs/_header.html.haml +++ b/app/views/projects/jobs/_header.html.haml @@ -27,5 +27,5 @@ = link_to "New issue", new_project_issue_path(@project, issue: build_failed_issue_options), class: 'btn btn-new btn-inverted' - if can?(current_user, :update_build, @build) && @build.retryable? = link_to "Retry job", retry_project_job_path(@project, @build), class: 'btn btn-inverted-secondary', method: :post - %button.btn.btn-default.pull-right.visible-xs-block.visible-sm-block.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" } + %button.btn.btn-default.float-right.d-block.d-sm-none.d-md-none.build-gutter-toggle.js-sidebar-build-toggle{ role: "button", type: "button" } = icon('angle-double-left') diff --git a/app/views/projects/jobs/_sidebar.html.haml b/app/views/projects/jobs/_sidebar.html.haml index 826404c2008..9c9e4ef8fce 100644 --- a/app/views/projects/jobs/_sidebar.html.haml +++ b/app/views/projects/jobs/_sidebar.html.haml @@ -18,7 +18,7 @@ %span= time_ago_with_tooltip @build.artifacts_expire_at - if @build.artifacts? - .btn-group.btn-group-justified{ role: :group } + .btn-group.btn-group.d-flex{ role: :group } - if @build.has_expiring_artifacts? && can?(current_user, :update_build, @build) = link_to keep_project_job_artifacts_path(@project, @build), class: 'btn btn-sm btn-default', method: :post do Keep @@ -42,7 +42,7 @@ - if @build.trigger_variables.any? %p - %button.btn.group.btn-group-justified.js-reveal-variables Reveal Variables + %button.btn.group.btn-group.js-reveal-variables Reveal Variables %dl.js-build-variables.trigger-build-variables.hide - @build.trigger_variables.each do |trigger_variable| @@ -83,7 +83,7 @@ - builds.select{|build| build.status == build_status}.each do |build| .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } - tooltip = build.tooltip_message - = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', html: true, title: tooltip, container: 'body' }) do + = link_to(project_job_path(@project, build), data: { toggle: 'tooltip', html: 'true', title: tooltip, container: 'body' }) do = sprite_icon('arrow-right', size:16, css_class: 'icon-arrow-right') %span{ class: "ci-status-icon-#{build.status}" } = ci_icon_for_status(build.status) diff --git a/app/views/projects/jobs/_user.html.haml b/app/views/projects/jobs/_user.html.haml index 461d503f95d..90ce581a903 100644 --- a/app/views/projects/jobs/_user.html.haml +++ b/app/views/projects/jobs/_user.html.haml @@ -1,7 +1,7 @@ by %a{ href: user_path(@build.user) } - %span.hidden-xs + %span.d-none.d-sm-inline = image_tag avatar_icon_for_user(@build.user, 24), class: "avatar s24" %strong{ data: { toggle: 'tooltip', placement: 'top', title: @build.user.to_reference } } = @build.user.name - %strong.visible-xs-inline= @build.user.to_reference + %strong.d-inline.d-sm-none= @build.user.to_reference diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index cbbcc8f1db5..ec9a04c0eab 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -15,7 +15,7 @@ - elsif @build.tags.any? This job is stuck, because you don't have any active runners online with any of these tags assigned to them: - @build.tags.each do |tag| - %span.label.label-primary + %span.badge.badge-primary = tag - else This job is stuck, because you don't have any active runners that can run this job. @@ -58,13 +58,13 @@ - if @build.running? || @build.has_trace? .build-trace-container.prepend-top-default .top-bar.js-top-bar - .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden< + .js-truncated-info.truncated-info.d-none.d-sm-block.float-left.hidden< Showing last %span.js-truncated-info-size.truncated-info-size>< of log - %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw - .controllers.pull-right + .controllers.float-right - if @build.has_trace? = link_to raw_project_job_path(@project, @build), title: 'Show complete raw', diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml index 243dcfdc187..0377cd6586e 100644 --- a/app/views/projects/mattermosts/_no_teams.html.haml +++ b/app/views/projects/mattermosts/_no_teams.html.haml @@ -13,4 +13,4 @@ and try again. %hr .clearfix - = link_to 'Go back', edit_project_service_path(@project, @service), class: 'btn btn-lg pull-right' + = link_to 'Go back', edit_project_service_path(@project, @service), class: 'btn btn-lg float-right' diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml index 20acd476f73..361d3c61d99 100644 --- a/app/views/projects/mattermosts/_team_selection.html.haml +++ b/app/views/projects/mattermosts/_team_selection.html.haml @@ -11,7 +11,7 @@ - options = options_for_select(mattermost_teams_options(@teams), selected_id) = f.select(:team_id, options, { include_blank: 'Select team...'}, { class: 'form-control', disabled: @teams.one?, selected: selected_id, required: true }) = f.hidden_field(:team_id, value: selected_id, required: true) if @teams.one? - .help-block + .form-text.text-muted - if @teams.one? This is the only available team. - else @@ -25,7 +25,7 @@ %h4 Command trigger word %p Choose the word that will trigger commands = f.text_field(:trigger, value: @project.path, class: 'form-control', required: true) - .help-block + .form-text.text-muted %p Trigger word must be unique, and can't begin with a slash or contain any spaces. Use the word that works best for your team. @@ -41,6 +41,6 @@ = icon('external-link') %hr .clearfix - .pull-right + .float-right = link_to 'Cancel', edit_project_service_path(@project, @service), class: 'btn btn-lg' = f.submit 'Install', class: 'btn btn-save btn-lg' diff --git a/app/views/projects/mattermosts/new.html.haml b/app/views/projects/mattermosts/new.html.haml index 15829a3f143..9e293d07cb7 100644 --- a/app/views/projects/mattermosts/new.html.haml +++ b/app/views/projects/mattermosts/new.html.haml @@ -1,7 +1,7 @@ - @body_class = 'card-content' .service-installation - .inline.pull-right + .inline.float-right = custom_icon('mattermost_logo', size: 48) %h3 Install Mattermost Command - if @teams.empty? diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 9607a7b5d06..179c1fcc684 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,2 +1,2 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request diff --git a/app/views/projects/merge_requests/_how_to_merge.html.haml b/app/views/projects/merge_requests/_how_to_merge.html.haml index 54a661040ea..5353fa8a88f 100644 --- a/app/views/projects/merge_requests/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/_how_to_merge.html.haml @@ -1,9 +1,9 @@ -#modal_merge_info.modal +#modal_merge_info.modal{ tabindex: '-1' } .modal-dialog .modal-content .modal-header - %a.close{ href: "#", "data-dismiss" => "modal" } × %h3 Check out, review, and merge locally + %a.close{ href: "#", "data-dismiss" => "modal" } × .modal-body %p %strong Step 1. diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 027a9ff1416..cd3d896fff2 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -9,21 +9,21 @@ %span.merge-request-title-text = link_to merge_request.title, merge_request_path(merge_request) - if merge_request.tasks? - %span.task-status.hidden-xs + %span.task-status.d-none.d-sm-inline-block = merge_request.task_status .issuable-info %span.issuable-reference #{issuable_reference(merge_request)} - %span.issuable-authored.hidden-xs + %span.issuable-authored.d-none.d-sm-inline-block · opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)} - if merge_request.milestone - %span.issuable-milestone.hidden-xs + %span.issuable-milestone.d-none.d-sm-inline-block - = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title), data: { html: 1, toggle: 'tooltip', title: milestone_tooltip_due_date(merge_request.milestone) } do + = link_to project_merge_requests_path(merge_request.project, milestone_title: merge_request.milestone.title), data: { html: 'true', toggle: 'tooltip', title: milestone_tooltip_due_date(merge_request.milestone) } do = icon('clock-o') = merge_request.milestone.title - if merge_request.target_project.default_branch != merge_request.target_branch @@ -40,17 +40,17 @@ .issuable-meta %ul.controls - if merge_request.merged? - %li.issuable-status.hidden-xs + %li.issuable-status.d-none.d-sm-inline-block MERGED - elsif merge_request.closed? - %li.issuable-status.hidden-xs + %li.issuable-status.d-none.d-sm-inline-block = icon('ban') CLOSED - if merge_request.head_pipeline - %li.issuable-pipeline-status.hidden-xs + %li.issuable-pipeline-status.d-none.d-sm-inline-block = render_pipeline_status(merge_request.head_pipeline) - if merge_request.open? && merge_request.broken? - %li.issuable-pipeline-broken.hidden-xs + %li.issuable-pipeline-broken.d-none.d-sm-inline-block = link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') do = icon('exclamation-triangle') - if merge_request.assignee @@ -59,5 +59,5 @@ = render 'shared/issuable_meta_data', issuable: merge_request - .pull-right.issuable-updated-at.hidden-xs + .float-right.issuable-updated-at.d-none.d-sm-inline-block %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')} diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml index 22c8b6b513d..a58179091ae 100644 --- a/app/views/projects/merge_requests/_mr_title.html.haml +++ b/app/views/projects/merge_requests/_mr_title.html.haml @@ -7,8 +7,8 @@ .detail-page-header .detail-page-header-body .issuable-status-box.status-box{ class: status_box_class(@merge_request) } - = sprite_icon(@merge_request.state_icon_name, size: 16, css_class: 'hidden-sm hidden-md hidden-lg') - %span.hidden-xs + = sprite_icon(@merge_request.state_icon_name, size: 16, css_class: 'd-block d-sm-none') + %span.d-none.d-sm-block = @merge_request.state_human_name .issuable-meta @@ -16,15 +16,15 @@ .issuable-warning-icon.inline= sprite_icon('lock', size: 16, css_class: 'icon') = issuable_meta(@merge_request, @project, "Merge request") - %a.btn.btn-default.pull-right.visible-xs-block.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } + %a.btn.btn-default.float-right.d-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } = icon('angle-double-left') .detail-page-header-actions.js-issuable-actions .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } + %button.btn.btn-default.float-left.d-md-none.d-lg-none.d-xl-none{ type: "button", data: { toggle: "dropdown" } } Options = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right.hidden-lg + .dropdown-menu.dropdown-menu-right.d-lg-none.d-xl-none %ul - if can_update_merge_request %li= link_to 'Edit', edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) @@ -37,6 +37,6 @@ = link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request' - if can_update_merge_request - = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "hidden-xs hidden-sm btn btn-grouped js-issuable-edit" + = link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "d-none d-sm-none d-md-block btn btn-grouped js-issuable-edit" = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index 964dc40a213..e6205f24ae6 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -11,6 +11,6 @@ Showing %strong.cred {{conflictsCountText}} between - %strong {{conflictsData.sourceBranch}} + %strong.ref-name {{conflictsData.sourceBranch}} and - %strong {{conflictsData.targetBranch}} + %strong.ref-name {{conflictsData.targetBranch}} diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 13026b7566a..4d84ee2488b 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -1,16 +1,24 @@ -.form-horizontal.resolve-conflicts-form - .form-group - %label.col-sm-2.control-label{ "for" => "commit-message" } - #{ _('Commit message') } - .col-sm-10 +- branch_name = link_to @merge_request.source_branch, project_tree_path(@merge_request.project, @merge_request.source_branch), class: "ref-name" +- translation =_('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}') % { use_ours: '<code>Use Ours</code>', use_theirs: '<code>Use Theirs</code>', branch_name: branch_name } + +%hr +.resolve-conflicts-form + .form-group.row + .col-md-4 + %h4= _('Resolve conflicts on source branch') + .resolve-info + = translation.html_safe + .col-md-8 + %label.label-light{ "for" => "commit-message" } + #{ _('Commit message') } .commit-message-container .max-width-marker %textarea.form-control.js-commit-message#commit-message{ "v-model" => "conflictsData.commitMessage", "rows" => "5" } - .form-group - .col-sm-offset-2.col-sm-10 + .form-group.row + .offset-md-4.col-md-8 .row - .col-xs-6 + .col-6 %button.btn.btn-success.js-submit-button{ type: "button", "@click" => "commit()", ":disabled" => "!readyToCommit" } %span {{commitButtonText}} - .col-xs-6.text-right + .col-6.text-right = link_to "Cancel", project_merge_request_path(@merge_request.project, @merge_request), class: "btn btn-cancel" diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml index 773b12b4536..ca0f7d6098f 100644 --- a/app/views/projects/merge_requests/creations/_new_compare.html.haml +++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml @@ -1,14 +1,14 @@ %h3.page-title New Merge Request -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline js-requires-input" } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form js-requires-input" } do |f| .hide.alert.alert-danger.mr-compare-errors .js-merge-request-new-compare.row{ 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) } .col-lg-6 - .panel.panel-default.panel-new-merge-request - .panel-heading + .card.card-new-merge-request + .card-header Source branch - .panel-body.clearfix + .card-body.clearfix .merge-request-select.dropdown = f.hidden_field :source_project_id = dropdown_toggle @merge_request.source_project_path, { toggle: "dropdown", 'field-name': "#{f.object_name}[source_project_id]", disabled: @merge_request.persisted? }, { toggle_class: "js-compare-dropdown js-source-project" } @@ -32,10 +32,10 @@ %ul.list-unstyled.mr_source_commit .col-lg-6 - .panel.panel-default.panel-new-merge-request - .panel-heading + .card.card-new-merge-request + .card-header Target branch - .panel-body.clearfix + .card-body.clearfix - projects = target_projects(@project) .merge-request-select.dropdown = f.hidden_field :target_project_id @@ -55,7 +55,7 @@ = dropdown_filter(_("Search branches")) = dropdown_content = dropdown_loading - .panel-footer + .card-footer .text-center= icon('spinner spin', class: "js-target-loading") %ul.list-unstyled.mr_target_commit diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml index 68780cedeb1..ebcd99f2a9b 100644 --- a/app/views/projects/merge_requests/creations/_new_submit.html.haml +++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml @@ -7,10 +7,10 @@ %span into %strong.ref-name= target_title - %span.pull-right + %span.float-right = link_to 'Change branches', mr_change_branches_path(@merge_request) %hr -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form common-note-form js-requires-input js-quick-submit' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request, commits: @commits = f.hidden_field :source_project_id = f.hidden_field :source_branch @@ -24,20 +24,20 @@ There are no commits yet. = custom_icon ('illustration_no_commits') - else - %ul.merge-request-tabs.nav-links.no-top.no-bottom + %ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom %li.commits-tab.active = link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do Commits - %span.badge= @commits.size + %span.badge.badge-pill= @commits.size - if @pipelines.any? %li.builds-tab = link_to url_for(safe_params.merge(action: 'pipelines')), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do Pipelines - %span.badge= @pipelines.size + %span.badge.badge-pill= @pipelines.size %li.diffs-tab = link_to url_for(safe_params.merge(action: 'diffs')), data: {target: 'div#diffs', action: 'diffs', toggle: 'tab'} do Changes - %span.badge= @merge_request.diff_size + %span.badge.badge-pill= @merge_request.diff_size .tab-content #commits.commits.tab-pane.active diff --git a/app/views/projects/merge_requests/diffs/_commit_widget.html.haml b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml index 2e5594f8cbe..dab95b97346 100644 --- a/app/views/projects/merge_requests/diffs/_commit_widget.html.haml +++ b/app/views/projects/merge_requests/diffs/_commit_widget.html.haml @@ -1,5 +1,5 @@ - if @commit - .info-well.hidden-xs.prepend-top-default + .info-well.d-none.d-sm-block.prepend-top-default .well-segment %ul.blob-commit-info = render 'projects/commits/commit', commit: @commit, merge_request: @merge_request, view_details: true diff --git a/app/views/projects/merge_requests/diffs/_diffs.html.haml b/app/views/projects/merge_requests/diffs/_diffs.html.haml index 986ba5ae02d..19659fe5140 100644 --- a/app/views/projects/merge_requests/diffs/_diffs.html.haml +++ b/app/views/projects/merge_requests/diffs/_diffs.html.haml @@ -5,9 +5,9 @@ - if @merge_request_diff&.empty? .row.empty-state.nothing-here-block - .col-xs-12 + .col-12 .svg-content= image_tag 'illustrations/merge_request_changes_empty.svg' - .col-xs-12 + .col-12 .text-content.text-center %p No changes between diff --git a/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml index 529fbb8547a..8d7138747fb 100644 --- a/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml +++ b/app/views/projects/merge_requests/diffs/_not_all_comments_displayed.html.haml @@ -11,7 +11,7 @@ comparing two versions of the diff - else viewing an old version of the diff - .pull-right + .float-right = link_to diffs_project_merge_request_path(@merge_request.project, @merge_request), class: 'btn btn-sm' do Show latest version = "of the diff" if @commit diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml index 6df19d6438b..749228a9664 100644 --- a/app/views/projects/merge_requests/invalid.html.haml +++ b/app/views/projects/merge_requests/invalid.html.haml @@ -10,13 +10,13 @@ - if @merge_request.for_fork? && !@merge_request.source_project fork project was removed - elsif !@merge_request.source_branch_exists? - %span.label.label-inverse= @merge_request.source_branch + %span.badge.badge-inverse= @merge_request.source_branch does not exist in - %span.label.label-info= @merge_request.source_project_path + %span.badge.badge-info= @merge_request.source_project_path - elsif !@merge_request.target_branch_exists? - %span.label.label-inverse= @merge_request.target_branch + %span.badge.badge-inverse= @merge_request.target_branch does not exist in - %span.label.label-info= @merge_request.target_project_path + %span.badge.badge-info= @merge_request.target_project_path - else of internal error diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index 15a0e4d7ef5..aa3fb623e58 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -30,26 +30,26 @@ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .fade-left= icon('angle-left') .fade-right= icon('angle-right') - .nav-links.scrolling-tabs - %ul.merge-request-tabs + .nav-links.scrolling-tabs.nav.nav-tabs + %ul.merge-request-tabs.nav-tabs.nav %li.notes-tab = tab_link_for @merge_request, :show, force_link: @commit.present? do Discussion - %span.badge= @merge_request.related_notes.user.count + %span.badge.badge-pill= @merge_request.related_notes.user.count - if @merge_request.source_project %li.commits-tab = tab_link_for @merge_request, :commits do Commits - %span.badge= @commits_count + %span.badge.badge-pill= @commits_count - if @pipelines.any? %li.pipelines-tab = tab_link_for @merge_request, :pipelines do Pipelines - %span.badge.js-pipelines-mr-count= @pipelines.size + %span.badge.badge-pill.js-pipelines-mr-count= @pipelines.size %li.diffs-tab = tab_link_for @merge_request, :diffs do Changes - %span.badge= @merge_request.diff_size + %span.badge.badge-pill= @merge_request.diff_size - if has_vue_discussions_cookie? #js-vue-discussion-counter diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 2e74b1b83cb..4cc59718715 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,13 +1,13 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form common-note-form js-quick-submit js-requires-input'} do |f| += form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'milestone-form common-note-form js-quick-submit js-requires-input'} do |f| = form_errors(@milestone) .row .col-md-6 - .form-group - = f.label :title, "Title", class: "control-label" + .form-group.row + = f.label :title, "Title", class: "col-form-label col-sm-2" .col-sm-10 = f.text_field :title, maxlength: 255, class: "form-control", required: true, autofocus: true - .form-group.milestone-description - = f.label :description, "Description", class: "control-label" + .form-group.row.milestone-description + = f.label :description, "Description", class: "col-form-label col-sm-2" .col-sm-10 = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project) } do = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: 'Write milestone description...' diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 5ec219fdf00..b478fbbb15e 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -56,7 +56,7 @@ #delete-milestone-modal - %a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" } + %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" } = icon('angle-double-left') .detail-page-description.milestone-detail diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 4b7be9a223f..75cb8245d36 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -9,7 +9,7 @@ = button_tag class: 'btn btn-success' do = icon('search') .inline.prepend-left-20 - .checkbox.light + .form-check.light = label_tag :filter_ref do = check_box_tag :filter_ref, 1, @options[:filter_ref] %span= _("Begin with the selected commit") diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 5beaa3c6d23..35a09f06bfa 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -28,19 +28,19 @@ %template.push-new-project-tip-template= render partial: "new_project_push_tip" .col-lg-9.js-toggle-container - %ul.nav-links.gitlab-tabs{ role: 'tablist' } + %ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' } %li{ class: active_when(active_tab == 'blank'), role: 'presentation' } %a{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' } - %span.hidden-xs Blank project - %span.visible-xs Blank + %span.d-none.d-sm-block Blank project + %span.d-block.d-sm-none Blank %li{ class: active_when(active_tab == 'template'), role: 'presentation' } %a{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' } - %span.hidden-xs Create from template - %span.visible-xs Template + %span.d-none.d-sm-block Create from template + %span.d-block.d-sm-none Template %li{ class: active_when(active_tab == 'import'), role: 'presentation' } %a{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' } - %span.hidden-xs Import project - %span.visible-xs Import + %span.d-none.d-sm-block Import project + %span.d-block.d-sm-none Import .tab-content.gitlab-tab-content .tab-pane{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' } @@ -63,7 +63,7 @@ %h4 No import options available %p Contact an administrator to enable options for importing your project. -.save-project-loader.hide +.save-project-loader.d-none .center %h2 %i.fa.fa-spinner.fa-spin diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml index 14d880028c7..08772a0188b 100644 --- a/app/views/projects/no_repo.html.haml +++ b/app/views/projects/no_repo.html.haml @@ -21,4 +21,4 @@ - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to _('Remove project'), project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove pull-right" + = link_to _('Remove project'), project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right" diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml index 82e20eeebb3..73ea30e1d3d 100644 --- a/app/views/projects/pages/_access.html.haml +++ b/app/views/projects/pages/_access.html.haml @@ -1,8 +1,8 @@ - if @project.pages_deployed? - .panel.panel-default - .panel-heading + .card + .card-header Access pages - .panel-body + .card-body %p %strong Congratulations! Your pages are served under: diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml index 7d6c30b7f8d..4ada19a1368 100644 --- a/app/views/projects/pages/_destroy.haml +++ b/app/views/projects/pages/_destroy.haml @@ -1,9 +1,9 @@ - if @project.pages_deployed? - if can?(current_user, :remove_pages, @project) - .panel.panel-default.panel.panel-danger - .panel-heading Remove pages + .card.bg-danger + .card-header Remove pages .errors-holder - .panel-body + .card-body %p Removing the pages will prevent from exposing them to outside world. .form-actions diff --git a/app/views/projects/pages/_https_only.html.haml b/app/views/projects/pages/_https_only.html.haml index 6a3ffce949f..57345edb90b 100644 --- a/app/views/projects/pages/_https_only.html.haml +++ b/app/views/projects/pages/_https_only.html.haml @@ -1,5 +1,5 @@ = form_for @project, url: namespace_project_pages_path(@project.namespace.becomes(Namespace), @project), html: { class: 'inline', title: pages_https_only_title } do |f| - = f.check_box :pages_https_only, class: 'pull-left', disabled: pages_https_only_disabled? + = f.check_box :pages_https_only, class: 'float-left', disabled: pages_https_only_disabled? .prepend-left-20 = f.label :pages_https_only, class: pages_https_only_label_class do diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml index 27bbe52a714..986ca852411 100644 --- a/app/views/projects/pages/_list.html.haml +++ b/app/views/projects/pages/_list.html.haml @@ -1,8 +1,8 @@ - verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled? - if can?(current_user, :update_pages, @project) && @domains.any? - .panel.panel-default - .panel-heading + .card + .card-header Domains (#{@domains.count}) %ul.well-list.pages-domain-list{ class: ("has-verification-status" if verification_enabled) } - @domains.each do |domain| @@ -17,9 +17,9 @@ = icon('external-link') - if domain.subject %p - %span.label.label-gray Certificate: #{domain.subject} + %span.badge.badge-gray Certificate: #{domain.subject} - if domain.expired? - %span.label.label-danger Expired + %span.badge.badge-danger Expired %div = link_to 'Details', project_pages_domain_path(@project, domain), class: "btn btn-sm btn-grouped" = link_to 'Remove', project_pages_domain_path(@project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" diff --git a/app/views/projects/pages/_no_domains.html.haml b/app/views/projects/pages/_no_domains.html.haml index 7cea5f3e70b..8c93cf7a8ad 100644 --- a/app/views/projects/pages/_no_domains.html.haml +++ b/app/views/projects/pages/_no_domains.html.haml @@ -1,6 +1,6 @@ - if can?(current_user, :update_pages, @project) - .panel.panel-default - .panel-heading + .card + .card-header Domains .nothing-here-block Support for domains and certificates is disabled. diff --git a/app/views/projects/pages/_use.html.haml b/app/views/projects/pages/_use.html.haml index e442e6e9a09..cd9177c0f9e 100644 --- a/app/views/projects/pages/_use.html.haml +++ b/app/views/projects/pages/_use.html.haml @@ -1,8 +1,8 @@ - unless @project.pages_deployed? - .panel.panel-info - .panel-heading + .card.bg-info + .card-header Configure pages - .panel-body + .card-body %p Learn how to upload your static site and have it served by GitLab by following the diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml index 6adaea799b2..7e1a3b9bea6 100644 --- a/app/views/projects/pages/show.html.haml +++ b/app/views/projects/pages/show.html.haml @@ -4,7 +4,7 @@ Pages - if can?(current_user, :update_pages, @project) && (Gitlab.config.pages.external_http || Gitlab.config.pages.external_https) - = link_to new_project_pages_domain_path(@project), class: 'btn btn-new pull-right', title: 'New Domain' do + = link_to new_project_pages_domain_path(@project), class: 'btn btn-new float-right', title: 'New Domain' do New Domain %p.light diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml index d81b07832bb..0d848f7899c 100644 --- a/app/views/projects/pages_domains/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -4,22 +4,22 @@ - @domain.errors.full_messages.each do |msg| %p= msg -.form-group - = f.label :domain, class: 'control-label' do +.form-group.row + = f.label :domain, class: 'col-form-label col-sm-2' do Domain .col-sm-10 = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control', disabled: @domain.persisted? - if Gitlab.config.pages.external_https - .form-group - = f.label :certificate, class: 'control-label' do + .form-group.row + = f.label :certificate, class: 'col-form-label col-sm-2' do Certificate (PEM) .col-sm-10 = f.text_area :certificate, rows: 5, class: 'form-control' %span.help-inline Upload a certificate for your domain with all intermediates - .form-group - = f.label :key, class: 'control-label' do + .form-group.row + = f.label :key, class: 'col-form-label col-sm-2' do Key (PEM) .col-sm-10 = f.text_area :key, rows: 5, class: 'form-control' diff --git a/app/views/projects/pages_domains/edit.html.haml b/app/views/projects/pages_domains/edit.html.haml index 6c404990492..ee70de22f13 100644 --- a/app/views/projects/pages_domains/edit.html.haml +++ b/app/views/projects/pages_domains/edit.html.haml @@ -5,7 +5,7 @@ = @domain.domain %hr.clearfix %div - = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f| + = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f| = render 'form', { f: f } .form-actions = f.submit 'Save Changes', class: "btn btn-save" diff --git a/app/views/projects/pages_domains/new.html.haml b/app/views/projects/pages_domains/new.html.haml index 269df803a2b..376ce3f68aa 100644 --- a/app/views/projects/pages_domains/new.html.haml +++ b/app/views/projects/pages_domains/new.html.haml @@ -4,9 +4,9 @@ New Pages Domain %hr.clearfix %div - = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'form-horizontal fieldset-form' } do |f| + = form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f| = render 'form', { f: f } .form-actions = f.submit 'Create New Domain', class: "btn btn-save" - .pull-right + .float-right = link_to _('Cancel'), project_pages_path(@project), class: 'btn btn-cancel' diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml index 44d66f3b2d0..a8484187493 100644 --- a/app/views/projects/pages_domains/show.html.haml +++ b/app/views/projects/pages_domains/show.html.haml @@ -12,7 +12,7 @@ This domain is not verified. You will need to verify ownership before access is enabled. %h3.page-title.with-button - = link_to 'Edit', edit_project_pages_domain_path(@project, @domain), class: 'btn btn-success pull-right' + = link_to 'Edit', edit_project_pages_domain_path(@project, @domain), class: 'btn btn-success float-right' Pages Domain .table-holder @@ -30,9 +30,9 @@ %td .input-group = text_field_tag :domain_dns, dns_record , class: "monospace js-select-on-focus form-control", readonly: true - .input-group-btn - = clipboard_button(target: '#domain_dns', class: 'btn-default hidden-xs') - %p.help-block + .input-group-append + = clipboard_button(target: '#domain_dns', class: 'btn-default input-group-text d-none d-sm-block') + %p.form-text.text-muted To access this domain create a new DNS record - if verification_enabled @@ -43,16 +43,16 @@ %td = form_tag verify_project_pages_domain_path(@project, @domain) do .status-badge - - text, status = @domain.unverified? ? [_('Unverified'), 'label-danger'] : [_('Verified'), 'label-success'] - .label{ class: status } + - text, status = @domain.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success'] + .badge{ class: status } = text %button.btn.has-tooltip{ type: "submit", data: { container: 'body' }, title: _("Retry verification") } = sprite_icon('redo') .input-group = text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true - .input-group-btn - = clipboard_button(target: '#domain_verification', class: 'btn-default hidden-xs') - %p.help-block + .input-group-append + = clipboard_button(target: '#domain_verification', class: 'btn-default d-none d-sm-block') + %p.form-text.text-muted - help_link = help_page_path('user/project/pages/getting_started_part_three.md', anchor: 'dns-txt-record') To #{link_to 'verify ownership', help_link} of your domain, add the above key to a TXT record within to your DNS configuration. diff --git a/app/views/projects/pipeline_schedules/_form.html.haml b/app/views/projects/pipeline_schedules/_form.html.haml index 160e325996a..1cdf981fcb4 100644 --- a/app/views/projects/pipeline_schedules/_form.html.haml +++ b/app/views/projects/pipeline_schedules/_form.html.haml @@ -1,24 +1,24 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "form-horizontal js-pipeline-schedule-form" } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @schedule], as: :schedule, html: { id: "new-pipeline-schedule-form", class: "js-pipeline-schedule-form" } do |f| = form_errors(@schedule) - .form-group + .form-group.row .col-md-9 = f.label :description, _('Description'), class: 'label-light' = f.text_field :description, class: 'form-control', required: true, autofocus: true, placeholder: s_('PipelineSchedules|Provide a short description for this pipeline') - .form-group + .form-group.row .col-md-9 = f.label :cron, _('Interval Pattern'), class: 'label-light' #interval-pattern-input{ data: { initial_interval: @schedule.cron } } - .form-group + .form-group.row .col-md-9 = f.label :cron_timezone, _('Cron Timezone'), class: 'label-light' = dropdown_tag(_("Select a timezone"), options: { toggle_class: 'btn js-timezone-dropdown', title: _("Select a timezone"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: timezone_data } } ) = f.text_field :cron_timezone, value: @schedule.cron_timezone, id: 'schedule_cron_timezone', class: 'hidden', name: 'schedule[cron_timezone]', required: true - .form-group + .form-group.row .col-md-9 = f.label :ref, _('Target Branch'), class: 'label-light' = dropdown_tag(_("Select target branch"), options: { toggle_class: 'btn js-target-branch-dropdown', dropdown_class: 'git-revision-dropdown', title: _("Select target branch"), filter: true, placeholder: s_("OfSearchInADropdown|Filter"), data: { data: @project.repository.branch_names, default_branch: @project.default_branch } } ) = f.text_field :ref, value: @schedule.ref, id: 'schedule_ref', class: 'hidden', name: 'schedule[ref]', required: true - .form-group.js-ci-variable-list-section + .form-group.row.js-ci-variable-list-section .col-md-9 %label.label-light #{ s_('PipelineSchedules|Variables') } @@ -32,7 +32,7 @@ = n_('Hide value', 'Hide values', @schedule.variables.size) - else = n_('Reveal value', 'Reveal values', @schedule.variables.size) - .form-group + .form-group.row .col-md-9 = f.label :active, s_('PipelineSchedules|Activated'), class: 'label-light' %div diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml index 55d0e8bb7f9..8d88f0be083 100644 --- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml +++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml @@ -25,7 +25,7 @@ = link_to user_path(pipeline_schedule.owner) do = pipeline_schedule.owner&.name %td - .pull-right.btn-group + .float-right.btn-group - if can?(current_user, :play_pipeline_schedule, pipeline_schedule) = link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: s_('Play'), class: 'btn' do = icon('play') diff --git a/app/views/projects/pipeline_schedules/_tabs.html.haml b/app/views/projects/pipeline_schedules/_tabs.html.haml index 8996c1b3e38..61f6ad34052 100644 --- a/app/views/projects/pipeline_schedules/_tabs.html.haml +++ b/app/views/projects/pipeline_schedules/_tabs.html.haml @@ -1,18 +1,18 @@ -%ul.nav-links.mobile-separator +%ul.nav-links.mobile-separator.nav.nav-tabs %li{ class: active_when(scope.nil?) }> = link_to schedule_path_proc.call(nil) do = s_("PipelineSchedules|All") - %span.badge.js-totalbuilds-count + %span.badge.badge-pill.js-totalbuilds-count = number_with_delimiter(all_schedules.count(:id)) %li{ class: active_when(scope == 'active') }> = link_to schedule_path_proc.call('active') do = s_("PipelineSchedules|Active") - %span.badge + %span.badge.badge-pill = number_with_delimiter(all_schedules.active.count(:id)) %li{ class: active_when(scope == 'inactive') }> = link_to schedule_path_proc.call('inactive') do = s_("PipelineSchedules|Inactive") - %span.badge + %span.badge.badge-pill = number_with_delimiter(all_schedules.inactive.count(:id)) diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index bcb6dddba1a..3677666070e 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -18,5 +18,5 @@ %ul.content-list = render partial: "table" - else - .light-well + .card.bg-light .nothing-here-block= _("No schedules") diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 9db30042bf4..aa53fc3ea28 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -27,7 +27,7 @@ .icon-container.commit-icon = custom_icon("icon_commit") = link_to @commit.short_id, project_commit_path(@project, @pipeline.sha), class: "commit-sha js-details-short" - = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do + = link_to("#", class: "js-details-expand d-none d-sm-none d-md-inline") do %span.text-expander \... %span.js-details-content.hide diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 4dbf95be357..118391aac64 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -1,17 +1,17 @@ .tabs-holder - %ul.pipelines-tabs.nav-links.no-top.no-bottom.mobile-separator + %ul.pipelines-tabs.nav-links.no-top.no-bottom.mobile-separator.nav.nav-tabs %li.js-pipeline-tab-link = link_to project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do = _("Pipeline") %li.js-builds-tab-link = link_to builds_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do = _("Jobs") - %span.badge.js-builds-counter= pipeline.total_size + %span.badge.badge-pill.js-builds-counter= pipeline.total_size - if @pipeline.failed_builds.present? %li.js-failures-tab-link = link_to failures_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do = _("Failed Jobs") - %span.badge.js-failures-counter= @pipeline.failed_builds.count + %span.badge.badge-pill.js-failures-counter= @pipeline.failed_builds.count .tab-content #js-tab-pipeline.tab-pane diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index 81984ee94b0..d1e8e9d0d60 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -6,9 +6,9 @@ = s_("Pipeline|Run Pipeline") %hr -= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "form-horizontal js-new-pipeline-form js-requires-input" } do |f| += form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "js-new-pipeline-form js-requires-input" } do |f| = form_errors(@pipeline) - .form-group + .form-group.row .col-sm-12 = f.label :ref, s_('Pipeline|Create for') = hidden_field_tag 'pipeline[ref]', params[:ref] || @project.default_branch @@ -16,7 +16,7 @@ options: { toggle_class: 'js-branch-select wide git-revision-dropdown-toggle', filter: true, dropdown_class: "dropdown-menu-selectable git-revision-dropdown", placeholder: s_("Pipeline|Search branches"), data: { selected: params[:ref] || @project.default_branch, field_name: 'pipeline[ref]' } }) - .help-block + .form-text.text-muted = s_("Pipeline|Existing branch name or tag") .col-sm-12.prepend-top-10.js-ci-variable-list-section @@ -24,7 +24,7 @@ = s_('Pipeline|Variables') %ul.ci-variable-list = render 'ci/variables/variable_row', form_field: 'pipeline', only_key_value: true - .help-block + .form-text.text-muted = (s_("Pipeline|Specify variable values to be used in this run. The values specified in %{settings_link} will be used by default.") % {settings_link: settings_link}).html_safe .form-actions diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml index fdeb5f21fbe..128f52ff648 100644 --- a/app/views/projects/project_members/_groups.html.haml +++ b/app/views/projects/project_members/_groups.html.haml @@ -1,7 +1,7 @@ -.panel.panel-default.project-members-groups - .panel-heading +.card.project-members-groups + .card-header Groups with access to %strong= @project.name - %span.badge= group_links.size + %span.badge.badge-pill= group_links.size %ul.content-list = render partial: 'shared/members/group', collection: group_links, as: :group_link diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml index bf5b11ea30c..5064db8d5cd 100644 --- a/app/views/projects/project_members/_new_project_member.html.haml +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -9,7 +9,7 @@ .select-wrapper = select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control" = icon('chevron-down') - .help-block.append-bottom-10 + .form-text.text-muted.append-bottom-10 = link_to "Read more", help_page_path("user/permissions"), class: "vlink" about role permissions .form-group diff --git a/app/views/projects/project_members/_new_shared_group.html.haml b/app/views/projects/project_members/_new_shared_group.html.haml index c10ef648a8f..684219735e2 100644 --- a/app/views/projects/project_members/_new_shared_group.html.haml +++ b/app/views/projects/project_members/_new_shared_group.html.haml @@ -9,7 +9,7 @@ .select-wrapper = select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control" = icon('chevron-down') - .help-block.append-bottom-10 + .form-text.text-muted.append-bottom-10 = link_to "Read more", help_page_path("user/permissions"), class: "vlink" about role permissions .form-group diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 16bcf671c25..a30870a241c 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,12 +1,12 @@ - project = local_assigns.fetch(:project) - members = local_assigns.fetch(:members) -.panel.panel-default - .panel-heading.flex-project-members-panel +.card + .card-header.flex-project-members-panel %span.flex-project-title Members of %strong= project.name - %span.badge= members.total_count + %span.badge.badge-pill= members.total_count = form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false } diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml index 755128af565..6a52e72bfd8 100644 --- a/app/views/projects/project_members/import.html.haml +++ b/app/views/projects/project_members/import.html.haml @@ -5,9 +5,9 @@ %p.light Only project members will be imported. Group members will be skipped. %hr -= form_tag apply_import_project_project_members_path(@project), method: 'post', class: 'form-horizontal' do - .form-group - = label_tag :source_project_id, "Project", class: 'control-label' += form_tag apply_import_project_project_members_path(@project), method: 'post' do + .form-group.row + = label_tag :source_project_id, "Project", class: 'col-form-label col-sm-2' .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(@projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true) .form-actions diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index d81103c3a92..a56023e98cd 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -17,7 +17,7 @@ %i Owners .light - if can?(current_user, :admin_project_member, @project) - %ul.nav-links.gitlab-tabs{ role: 'tablist' } + %ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' } %li.active{ role: 'presentation' } %a{ href: '#add-member-pane', id: 'add-member-tab', data: { toggle: 'tab' }, role: 'tab' } Add member - if @project.allowed_to_share_with_group? diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml index d1ed438eb21..a2cd7752fc4 100644 --- a/app/views/projects/protected_branches/shared/_branches_list.html.haml +++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml @@ -1,7 +1,7 @@ .protected-branches-list.js-protected-branches-list.qa-protected-branches-list - if @protected_branches.empty? - .panel-heading - %h3.panel-title + .card-header + %h3.card-title Protected branch (#{@protected_branches_count}) %p.settings-message.text-center There are currently no protected branches, protect a branch with the form above. diff --git a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml index 9f0c4f3b3a8..c2d6c034e35 100644 --- a/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_create_protected_branch.html.haml @@ -1,33 +1,32 @@ = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'new-protected-branch js-new-protected-branch' } do |f| - .panel.panel-default - .panel-heading - %h3.panel-title + .card + .card-header + %h3.card-title Protect a branch - .panel-body - .form-horizontal - = form_errors(@protected_branch) - .form-group - = f.label :name, class: 'col-md-2 text-right' do - Branch: - .col-md-10 - = render partial: "projects/protected_branches/shared/dropdown", locals: { f: f } - .help-block - = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches') - such as - %code *-stable - or - %code production/* - are supported - .form-group - %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } - Allowed to merge: - .col-md-10 - = yield :merge_access_levels - .form-group - %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } - Allowed to push: - .col-md-10 - = yield :push_access_levels + .card-body + = form_errors(@protected_branch) + .form-group.row + = f.label :name, class: 'col-md-2 text-right' do + Branch: + .col-md-10 + = render partial: "projects/protected_branches/shared/dropdown", locals: { f: f } + .form-text.text-muted + = link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches') + such as + %code *-stable + or + %code production/* + are supported + .form-group.row + %label.col-md-2.text-right{ for: 'merge_access_levels_attributes' } + Allowed to merge: + .col-md-10 + = yield :merge_access_levels + .form-group.row + %label.col-md-2.text-right{ for: 'push_access_levels_attributes' } + Allowed to push: + .col-md-10 + = yield :push_access_levels - .panel-footer + .card-footer = f.submit 'Protect', class: 'btn-create btn', disabled: true diff --git a/app/views/projects/protected_branches/shared/_matching_branch.html.haml b/app/views/projects/protected_branches/shared/_matching_branch.html.haml index 98793d632e6..2c76bf87945 100644 --- a/app/views/projects/protected_branches/shared/_matching_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_matching_branch.html.haml @@ -3,7 +3,7 @@ = link_to matching_branch.name, project_ref_path(@project, matching_branch.name), class: 'ref-name' - if @project.root_ref?(matching_branch.name) - %span.label.label-info.prepend-left-5 default + %span.badge.badge-info.prepend-left-5 default %td - commit = @project.commit(matching_branch.name) = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml index 2d3b2af00c2..82ef08272d3 100644 --- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml @@ -5,7 +5,7 @@ %span.ref-name.qa-protected-branch-name= protected_branch.name - if @project.root_ref?(protected_branch.name) - %span.label.label-info.prepend-left-5 default + %span.badge.badge-info.prepend-left-5 default %td - if protected_branch.wildcard? - matching_branches = protected_branch.matching(repository.branches) diff --git a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml index 5a53c704fcb..0ae5ca3ff36 100644 --- a/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/shared/_create_protected_tag.html.haml @@ -1,29 +1,28 @@ = form_for [@project.namespace.becomes(Namespace), @project, @protected_tag], html: { class: 'new-protected-tag js-new-protected-tag' } do |f| - .panel.panel-default - .panel-heading - %h3.panel-title + .card + .card-header + %h3.card-title Protect a tag - .panel-body - .form-horizontal - = form_errors(@protected_tag) - .form-group - = f.label :name, class: 'col-md-2 text-right' do - Tag: - .col-md-10.protected-tags-dropdown - = render partial: "projects/protected_tags/shared/dropdown", locals: { f: f } - .help-block - = link_to 'Wildcards', help_page_path('user/project/protected_tags', anchor: 'wildcard-protected-tags') - such as - %code v* - or - %code *-release - are supported - .form-group - %label.col-md-2.text-right{ for: 'create_access_levels_attributes' } - Allowed to create: - .col-md-10 - .create_access_levels-container - = yield :create_access_levels + .card-body + = form_errors(@protected_tag) + .form-group.row + = f.label :name, class: 'col-md-2 text-right' do + Tag: + .col-md-10.protected-tags-dropdown + = render partial: "projects/protected_tags/shared/dropdown", locals: { f: f } + .form-text.text-muted + = link_to 'Wildcards', help_page_path('user/project/protected_tags', anchor: 'wildcard-protected-tags') + such as + %code v* + or + %code *-release + are supported + .form-group.row + %label.col-md-2.text-right{ for: 'create_access_levels_attributes' } + Allowed to create: + .col-md-10 + .create_access_levels-container + = yield :create_access_levels - .panel-footer + .card-footer = f.submit 'Protect', class: 'btn-create btn', disabled: true diff --git a/app/views/projects/protected_tags/shared/_matching_tag.html.haml b/app/views/projects/protected_tags/shared/_matching_tag.html.haml index 05f102d1ca3..133c76cd2ad 100644 --- a/app/views/projects/protected_tags/shared/_matching_tag.html.haml +++ b/app/views/projects/protected_tags/shared/_matching_tag.html.haml @@ -3,7 +3,7 @@ = link_to matching_tag.name, project_ref_path(@project, matching_tag.name), class: 'ref-name' - if @project.root_ref?(matching_tag.name) - %span.label.label-info.prepend-left-5 default + %span.badge.badge-info.prepend-left-5 default %td - commit = @project.commit(matching_tag.name) = link_to(commit.short_id, project_commit_path(@project, commit.id), class: 'commit-sha') diff --git a/app/views/projects/protected_tags/shared/_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_protected_tag.html.haml index c778f7b9781..1702e38df7e 100644 --- a/app/views/projects/protected_tags/shared/_protected_tag.html.haml +++ b/app/views/projects/protected_tags/shared/_protected_tag.html.haml @@ -3,7 +3,7 @@ %span.ref-name= protected_tag.name - if @project.root_ref?(protected_tag.name) - %span.label.label-info.prepend-left-5 default + %span.badge.badge-info.prepend-left-5 default %td - if protected_tag.wildcard? - matching_tags = protected_tag.matching(repository.tags) diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml index 3ed82e51dbe..c3081d75fb4 100644 --- a/app/views/projects/protected_tags/shared/_tags_list.html.haml +++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml @@ -1,7 +1,7 @@ .protected-tags-list.js-protected-tags-list - if @protected_tags.empty? - .panel-heading - %h3.panel-title + .card-header + %h3.card-title Protected tag (#{@protected_tags_count}) %p.settings-message.text-center There are currently no protected tags, protect a tag with the form above. diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml index 0223372bff8..a4cde53e8c6 100644 --- a/app/views/projects/registry/repositories/_tag.html.haml +++ b/app/views/projects/registry/repositories/_tag.html.haml @@ -24,7 +24,7 @@ \- - if can?(current_user, :update_container_image, @project) %td.content - .controls.hidden-xs.pull-right + .controls.d-none.d-sm-block.float-right = link_to project_registry_repository_tag_path(@project, tag.repository, tag.name), method: :delete, class: 'btn btn-remove has-tooltip', diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml index 2a683d5be0f..0426f2215ad 100644 --- a/app/views/projects/registry/repositories/index.html.haml +++ b/app/views/projects/registry/repositories/index.html.haml @@ -16,11 +16,11 @@ .row.prepend-top-10 .col-lg-12 - .panel.panel-default - .panel-heading - %h4.panel-title + .card + .card-header + %h4.card-title = s_('ContainerRegistry|How to use the Container Registry') - .panel-body + .card-body %p - link_token = link_to(_('personal access token'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'personal-access-tokens'), target: '_blank') - link_2fa = link_to(_('2FA enabled'), help_page_path('user/profile/account/two_factor_authentication'), target: '_blank') diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index 4d962f9433f..b4787032966 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -11,7 +11,7 @@ %strong= @tag.name - = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), html: { class: 'form-horizontal common-note-form release-form js-quick-submit' }) do |f| + = form_for(@release, method: :put, url: project_tag_release_path(@project, @tag.name), html: { class: 'common-note-form release-form js-quick-submit' }) do |f| = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." = render 'shared/notes/hints' diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml index 87895a15239..ae0d9ab9908 100644 --- a/app/views/projects/repositories/_feed.html.haml +++ b/app/views/projects/repositories/_feed.html.haml @@ -14,5 +14,5 @@ = image_tag avatar_icon_for_email(commit.author_email), class: "", width: 16, alt: '' = markdown(truncate(commit.title, length: 40), pipeline: :single_line, author: commit.author) %td - %span.pull-right.cgray + %span.float-right.cgray = time_ago_with_tooltip(commit.committed_date) diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 69218f344f7..a23f5d6f0c3 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -15,7 +15,7 @@ %span.commit-sha = runner.short_sha - .pull-right + .float-right - if @project_runners.include?(runner) - if runner.active? = link_to _('Pause'), pause_project_runner_path(@project, runner), method: :post, class: 'btn btn-sm btn-danger', data: { confirm: _("Are you sure?") } @@ -30,7 +30,7 @@ = form_for [@project.namespace.becomes(Namespace), @project, @project.runner_projects.new] do |f| = f.hidden_field :runner_id, value: runner.id = f.submit _('Enable for this project'), class: 'btn btn-sm' - .pull-right + .float-right %small.light \##{runner.id} - if runner.description.present? @@ -39,5 +39,5 @@ - if runner.tag_list.present? %p - runner.tag_list.sort.each do |tag| - %span.label.label-primary + %span.badge.badge-primary = tag diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index 684b082efbb..aa30ebdc3b8 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -9,7 +9,7 @@ - if @service.respond_to?(:detailed_description) %p= @service.detailed_description .col-lg-9 - = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| + = form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form| = render 'shared/service_settings', form: form, subject: @service - if @service.editable? .footer-block.row-content-block diff --git a/app/views/projects/services/_index.html.haml b/app/views/projects/services/_index.html.haml index dac7d4d1bbb..acbab8b85c9 100644 --- a/app/views/projects/services/_index.html.haml +++ b/app/views/projects/services/_index.html.haml @@ -8,13 +8,13 @@ %colgroup %col %col - %col.hidden-xs + %col.d-none.d-sm-block %col{ width: "120" } %thead %tr %th %th Service - %th.hidden-xs Description + %th.d-none.d-sm-block Description %th Last edit - @services.sort_by(&:title).each do |service| %tr @@ -23,7 +23,7 @@ %td = link_to edit_project_service_path(@project, service.to_param) do %strong= service.title - %td.hidden-xs + %td.d-none.d-sm-block = service.description %td.light - if service.updated_at.present? diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml index 2ab0227126a..209b9c71390 100644 --- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml @@ -18,22 +18,22 @@ .help-form .form-group - = label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#display_name') + = label_tag :display_name, 'Display name', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#display_name', class: 'input-group-text') .form-group - = label_tag :description, 'Description', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#description') + = label_tag :description, 'Description', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#description', class: 'input-group-text') .form-group - = label_tag nil, 'Command trigger word', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block + = label_tag nil, 'Command trigger word', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.text-block %p Fill in the word that works best for your team. %p Suggestions: @@ -42,47 +42,47 @@ %code= @project.full_path .form-group - = label_tag :request_url, 'Request URL', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :request_url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#request_url') + = label_tag :request_url, 'Request URL', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :request_url, service_trigger_url(subject), class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#request_url', class: 'input-group-text') .form-group - = label_tag nil, 'Request method', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block POST + = label_tag nil, 'Request method', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.text-block POST .form-group - = label_tag :response_username, 'Response username', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :response_username, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#response_username') + = label_tag :response_username, 'Response username', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :response_username, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#response_username', class: 'input-group-text') .form-group - = label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#response_icon') + = label_tag :response_icon, 'Response icon', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :response_icon, asset_url('gitlab_logo.png'), class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#response_icon', class: 'input-group-text') .form-group - = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block Yes + = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.text-block Yes .form-group - = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :autocomplete_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#autocomplete_hint') + = label_tag :autocomplete_hint, 'Autocomplete hint', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :autocomplete_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#autocomplete_hint', class: 'input-group-text') .form-group - = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#autocomplete_description') + = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#autocomplete_description', class: 'input-group-text') %hr diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml index 2a1b9d4c465..62bef77be97 100644 --- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml @@ -1,6 +1,6 @@ - enabled = Gitlab.config.mattermost.enabled -.well +.card %p This service allows users to perform common operations on this project by entering slash commands in Mattermost. diff --git a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml index 44c0b7a90dc..2da8e5428ec 100644 --- a/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_installation_info.html.haml @@ -1,7 +1,7 @@ .services-installation-info - unless @service.activated? .row - .col-sm-9.col-sm-offset-3 + .col-sm-9.offset-sm-3 = link_to new_project_mattermost_path(@project), class: 'btn btn-lg' do = custom_icon('mattermost_logo', size: 15) Add to Mattermost diff --git a/app/views/projects/services/prometheus/_help.html.haml b/app/views/projects/services/prometheus/_help.html.haml index 88acb824ba7..15e7362c2ba 100644 --- a/app/views/projects/services/prometheus/_help.html.haml +++ b/app/views/projects/services/prometheus/_help.html.haml @@ -5,5 +5,5 @@ = s_('PrometheusService|Manual configuration') - unless @service.editable? - .well + .card = s_('PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters') diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index 43e6a173108..bda597cc02b 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -7,12 +7,12 @@ = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus') .col-lg-9 - .panel.panel-default.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(@project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } } - .panel-heading - %h3.panel-title + .card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(@project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } } + .card-header + %h3.card-title = s_('PrometheusService|Common metrics') - %span.badge.js-monitored-count 0 - .panel-body + %span.badge.badge-pill.js-monitored-count 0 + .card-body .loading-metrics.js-loading-metrics %p.prepend-top-10.prepend-left-10 = icon('spinner spin', class: 'metrics-load-spinner') @@ -22,13 +22,13 @@ = s_('PrometheusService|Waiting for your first deployment to an environment to find common metrics') %ul.list-unstyled.metrics-list.hidden.js-metrics-list - .panel.panel-default.hidden.js-panel-missing-env-vars - .panel-heading - %h3.panel-title + .card.hidden.js-panel-missing-env-vars + .card-header + %h3.card-title = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel') = s_('PrometheusService|Missing environment variable') - %span.badge.js-env-var-count 0 - .panel-body.hidden + %span.badge.badge-pill.js-env-var-count 0 + .card-body.hidden .flash-container .flash-notice .flash-text diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml index d592a5e4663..44f58ad05e5 100644 --- a/app/views/projects/services/slack_slash_commands/_help.html.haml +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -1,7 +1,7 @@ - pretty_name = defined?(@project) ? @project.full_name : 'namespace / path' - run_actions_text = "Perform common operations on GitLab project: #{pretty_name}" -.well +.card %p This service allows users to perform common operations on this project by entering slash commands in Slack. @@ -26,8 +26,8 @@ .help-form .form-group - = label_tag nil, 'Command', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block + = label_tag nil, 'Command', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.text-block %p Fill in the word that works best for your team. %p Suggestions: @@ -36,53 +36,53 @@ %code= @project.full_path .form-group - = label_tag :url, 'URL', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#url') + = label_tag :url, 'URL', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :url, service_trigger_url(subject), class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#url', class: 'input-group-text') .form-group - = label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block POST + = label_tag nil, 'Method', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.text-block POST .form-group - = label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#customize_name') + = label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :customize_name, 'GitLab', class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#customize_name', class: 'input-group-text') .form-group - = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block + = label_tag nil, 'Customize icon', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.text-block = image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36) = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer') .form-group - = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.text-block Show this command in the autocomplete list + = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.text-block Show this command in the autocomplete list .form-group - = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#autocomplete_description') + = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#autocomplete_description', class: 'input-group-text') .form-group - = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#autocomplete_usage_hint') + = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#autocomplete_usage_hint', class: 'input-group-text') .form-group - = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label' - .col-sm-10.col-xs-12.input-group - = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly' - .input-group-btn - = clipboard_button(target: '#descriptive_label') + = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-12 col-form-label' + .col-sm-10.col-12.input-group + = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control form-control-sm', readonly: 'readonly' + .input-group-append + = clipboard_button(target: '#descriptive_label', class: 'input-group-text') %hr diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index 8cb6c446e18..988bcfb5265 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -10,21 +10,21 @@ %p.settings-message.text-center = message.html_safe = f.fields_for :auto_devops_attributes, @auto_devops do |form| - .radio + .form-check = form.label :enabled_true do = form.radio_button :enabled, 'true' %strong= s_('CICD|Enable Auto DevOps') %br = s_('CICD|The Auto DevOps pipeline configuration will be used when there is no %{ci_file} in the project.').html_safe % { ci_file: ci_file_formatted } - .radio + .form-check = form.label :enabled_false do = form.radio_button :enabled, 'false' %strong= s_('CICD|Disable Auto DevOps') %br = s_('CICD|An explicit %{ci_file} needs to be specified before you can begin using Continuous Integration and Delivery.').html_safe % { ci_file: ci_file_formatted } - .radio + .form-check = form.label :enabled_ do = form.radio_button :enabled, '' %strong= s_('CICD|Instance default (%{state})') % { state: "#{Gitlab::CurrentSettings.auto_devops_enabled? ? _('enabled') : _('disabled')}" } diff --git a/app/views/projects/settings/ci_cd/_badge.html.haml b/app/views/projects/settings/ci_cd/_badge.html.haml index e8028059487..d14360913a4 100644 --- a/app/views/projects/settings/ci_cd/_badge.html.haml +++ b/app/views/projects/settings/ci_cd/_badge.html.haml @@ -2,15 +2,15 @@ .col-lg-12 %h4 = badge.title.capitalize - .panel.panel-default - .panel-heading + .card + .card-header %b = badge.title.capitalize · = badge.to_html - .pull-right + .float-right = render 'shared/ref_switcher', destination: 'badges', align_right: true - .panel-body + .card-body .row .col-md-2.text-center Markdown diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index 80c226ad273..50175f5258c 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -7,8 +7,8 @@ = f.label :runners_token, "Runner token", class: 'label-light' .form-control.js-secret-value-placeholder = '*' * 20 - = f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89' - %p.help-block The secure token used by the Runner to checkout the project + = f.text_field :runners_token, class: "form-control hidden 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') @@ -19,14 +19,14 @@ %p Choose between <code>clone</code> or <code>fetch</code> to get the recent application code = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'git-strategy'), target: '_blank' - .radio + .form-check = f.label :build_allow_git_fetch_false do = f.radio_button :build_allow_git_fetch, 'false' %strong git clone %br %span.descr Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job - .radio + .form-check = f.label :build_allow_git_fetch_true do = f.radio_button :build_allow_git_fetch, 'true' %strong git fetch @@ -38,7 +38,7 @@ .form-group = f.label :build_timeout_human_readable, 'Timeout', class: 'label-light' = f.text_field :build_timeout_human_readable, class: 'form-control' - %p.help-block + %p.form-text.text-muted Per job. If a job passes this threshold, it will be marked as failed = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'timeout'), target: '_blank' @@ -46,17 +46,17 @@ .form-group = f.label :ci_config_path, 'Custom CI config path', class: 'label-light' = f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml' - %p.help-block + %p.form-text.text-muted The path to CI config file. Defaults to <code>.gitlab-ci.yml</code> = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'custom-ci-config-path'), target: '_blank' %hr .form-group - .checkbox + .form-check = f.label :public_builds do = f.check_box :public_builds %strong Public pipelines - .help-block + .form-text.text-muted Allow public access to pipelines and job details, including output logs and artifacts = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'visibility-of-pipelines'), target: '_blank' .bs-callout.bs-callout-info @@ -74,11 +74,11 @@ %hr .form-group - .checkbox + .form-check = f.label :auto_cancel_pending_pipelines do = f.check_box :auto_cancel_pending_pipelines, {}, 'enabled', 'disabled' %strong Auto-cancel redundant, pending pipelines - .help-block + .form-text.text-muted New pipelines will cancel older, pending pipelines on the same branch = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'auto-cancel-pending-pipelines'), target: '_blank' @@ -86,10 +86,12 @@ .form-group = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light' .input-group - %span.input-group-addon / + %span.input-group-prepend + .input-group-text / = f.text_field :build_coverage_regex, class: 'form-control', placeholder: 'Regular expression' - %span.input-group-addon / - %p.help-block + %span.input-group-append + .input-group-text / + %p.form-text.text-muted A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable = link_to icon('question-circle'), help_page_path('user/project/pipelines/settings', anchor: 'test-coverage-parsing'), target: '_blank' diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 5f596a019f7..7d8dd58e7e0 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -19,7 +19,7 @@ %section.settings#autodevops-settings.no-animate{ class: ('expanded' if expanded) } .settings-header %h4 - = s_('CICD|Auto DevOps (Beta)') + = s_('CICD|Auto DevOps') %button.btn.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') %p diff --git a/app/views/projects/settings/integrations/_project_hook.html.haml b/app/views/projects/settings/integrations/_project_hook.html.haml index cd003107d66..77d88aed883 100644 --- a/app/views/projects/settings/integrations/_project_hook.html.haml +++ b/app/views/projects/settings/integrations/_project_hook.html.haml @@ -5,7 +5,7 @@ %div - ProjectHook.triggers.each_value do |event| - if hook.public_send(event) - %span.label.label-gray.deploy-project-label= event.to_s.titleize + %span.badge.badge-gray.deploy-project-label= event.to_s.titleize .col-md-4.col-lg-5.text-right-lg.prepend-top-5 %span.append-right-10.inline SSL Verification: #{hook.enable_ssl_verification ? 'enabled' : 'disabled'} diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index f09871c7fcc..b3a9fa9dd91 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -1,6 +1,6 @@ - return unless current_user -.hidden-xs +.d-none.d-sm-block - if can?(current_user, :update_project_snippet, @snippet) = link_to edit_project_snippet_path(@project, @snippet), class: "btn btn-grouped" do Edit @@ -13,7 +13,7 @@ - if @snippet.submittable_as_spam_by?(current_user) = link_to 'Submit as spam', mark_as_spam_project_snippet_path(@project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) - .visible-xs-block.dropdown + .d-block.d-sm-none.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } Options = icon('caret-down') diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 98b4d6339da..f55202c2c5f 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -6,7 +6,7 @@ = link_to tag.name, project_tag_path(@project, tag.name), class: 'item-title ref-name prepend-left-4' - if protected_tag?(@project, tag) - %span.label.label-success.prepend-left-4 + %span.badge.badge-success.prepend-left-4 = s_('TagsPage|protected') - if tag.message.present? diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 10415d011d6..96ecac815c0 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -16,7 +16,7 @@ %span.light = tags_sort_options_hash[@sort] = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable %li.dropdown-header = s_('TagsPage|Sort by') - tags_sort_options_hash.each do |value, title| diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 1827a3d323c..b596748ca5f 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -10,35 +10,35 @@ = s_('TagsPage|New Tag') %hr -= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal common-note-form tag-form js-quick-submit js-requires-input" do - .form-group - = label_tag :tag_name, nil, class: 'control-label' += form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "common-note-form tag-form js-quick-submit js-requires-input" do + .form-group.row + = label_tag :tag_name, nil, class: 'col-form-label col-sm-2' .col-sm-10 = text_field_tag :tag_name, params[:tag_name], required: true, autofocus: true, class: 'form-control' - .form-group - = label_tag :ref, 'Create from', class: 'control-label' + .form-group.row + = label_tag :ref, 'Create from', class: 'col-form-label col-sm-2' .col-sm-10.create-from .dropdown = hidden_field_tag :ref, default_ref = button_tag type: 'button', title: default_ref, class: 'dropdown-menu-toggle wide js-branch-select', required: true, data: { toggle: 'dropdown', selected: default_ref, field_name: 'ref' } do .text-left.dropdown-toggle-text= default_ref = render 'shared/ref_dropdown', dropdown_class: 'wide' - .help-block + .form-text.text-muted = s_('TagsPage|Existing branch name, tag, or commit SHA') - .form-group - = label_tag :message, nil, class: 'control-label' + .form-group.row + = label_tag :message, nil, class: 'col-form-label col-sm-2' .col-sm-10 = text_area_tag :message, @message, required: false, class: 'form-control', rows: 5 - .help-block + .form-text.text-muted = s_('TagsPage|Optionally, add a message to the tag.') %hr - .form-group - = label_tag :release_description, s_('TagsPage|Release notes'), class: 'control-label' + .form-group.row + = label_tag :release_description, s_('TagsPage|Release notes'), class: 'col-form-label col-sm-2' .col-sm-10 = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do = render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: s_('TagsPage|Write your release notes or drag files here...'), current_text: @release_description = render 'shared/notes/hints' - .help-block + .form-text.text-muted = s_('TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page.') .form-actions = button_tag s_('TagsPage|Create tag'), class: 'btn btn-create' diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 7a3469cdd26..15a960f81b8 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -11,7 +11,7 @@ = icon('tag') = @tag.name - if protected_tag?(@project, @tag) - %span.label.label-success + %span.badge.badge-success = s_('TagsPage|protected') - if @commit = render 'projects/branches/commit', commit: @commit, project: @project diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index 8c1c532cb3e..f79f3af36d4 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -6,7 +6,7 @@ = link_to project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)), class: 'str-truncated', title: file_name do %span= file_name - if is_lfs_blob - %span.label.label-lfs.prepend-left-5 LFS - %td.hidden-xs.tree-commit + %span.badge.label-lfs.prepend-left-5 LFS + %td.d-none.d-sm-table-cell.tree-commit %td.tree-time-ago.cgray.text-right = render 'projects/tree/spinner' diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index 04d52361db0..e563c8c4036 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -3,4 +3,4 @@ %i.fa.fa-archive.fa-fw = submodule_link(submodule_item, @ref) %td - %td.hidden-xs + %td.d-none.d-sm-table-cell diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 6ea78851b8d..587aeafa82f 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -4,15 +4,15 @@ %thead %tr %th= s_('ProjectFileTree|Name') - %th.hidden-xs - .pull-left= _('Last commit') + %th.d-none.d-sm-table-cell + .float-left= _('Last commit') %th.text-right= _('Last update') - if @path.present? %tr.tree-item %td.tree-item-file-name = link_to "..", project_tree_path(@project, up_dir_path), class: 'prepend-left-10' %td - %td.hidden-xs + %td.d-none.d-sm-table-cell = render_tree(tree) diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 8587d3b0c0d..4fc1a284693 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -11,18 +11,18 @@ - addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' } %ul.breadcrumb.repo-breadcrumb - %li + %li.breadcrumb-item = link_to project_tree_path(@project, @ref) do = @project.path - path_breadcrumbs do |title, path| - %li + %li.breadcrumb-item = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path)) - if can_collaborate || can_create_mr_from_fork - %li + %li.breadcrumb-item %a.btn.add-to-tree{ addtotree_toggle_attributes } - = sprite_icon('plus', size: 16, css_class: 'pull-left') - = sprite_icon('arrow-down', size: 16, css_class: 'pull-left') + = sprite_icon('plus', size: 16, css_class: 'float-left') + = sprite_icon('arrow-down', size: 16, css_class: 'float-left') - if on_top_of_branch? .add-to-tree-dropdown %ul.dropdown-menu @@ -82,7 +82,7 @@ - if can_collaborate = succeed " " do - = link_to ide_edit_path(@project, @id, ""), class: 'btn btn-default' do + = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default' do = _('Web IDE') = render 'projects/buttons/download', project: @project, ref: @ref diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index af3816fc9f4..ce0cd95b468 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -4,6 +4,6 @@ - path = flatten_tree(@path, tree_item) = link_to project_tree_path(@project, tree_join(@id || @commit.id, path)), class: 'str-truncated', title: path do %span= path - %td.hidden-xs.tree-commit + %td.d-none.d-sm-table-cell.tree-commit %td.tree-time-ago.text-right = render 'projects/tree/spinner' diff --git a/app/views/projects/triggers/_content.html.haml b/app/views/projects/triggers/_content.html.haml index 6c2d603d95d..96a41aa066c 100644 --- a/app/views/projects/triggers/_content.html.haml +++ b/app/views/projects/triggers/_content.html.haml @@ -1,6 +1,6 @@ %p.append-bottom-default Triggers with the - %span.label.label-primary legacy + %span.badge.badge-primary legacy label do not have an associated user and only have access to the current project. %br = succeed '.' do diff --git a/app/views/projects/triggers/_form.html.haml b/app/views/projects/triggers/_form.html.haml index 5f708b3a2ed..3539aea3580 100644 --- a/app/views/projects/triggers/_form.html.haml +++ b/app/views/projects/triggers/_form.html.haml @@ -4,7 +4,7 @@ - if @trigger.token .form-group %label.label-light Token - %p.form-control-static= @trigger.token + %p.form-control-plaintext= @trigger.token .form-group = f.label :key, "Description", class: "label-light" = f.text_field :description, class: "form-control", required: true, title: 'Trigger description is required.', placeholder: "Trigger description" diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml index 0f655e4ed83..a15bb4c4f3f 100644 --- a/app/views/projects/triggers/_index.html.haml +++ b/app/views/projects/triggers/_index.html.haml @@ -1,11 +1,11 @@ .row.prepend-top-default.append-bottom-default.triggers-container .col-lg-12 = render "projects/triggers/content" - .panel.panel-default - .panel-heading - %h4.panel-title + .card + .card-header + %h4.card-title Manage your project's triggers - .panel-body + .card-body = render "projects/triggers/form", btn_text: "Add trigger" %hr - if @triggers.any? @@ -26,7 +26,7 @@ %p.settings-message.text-center.append-bottom-default No triggers have been created yet. Add one using the form above. - .panel-footer + .card-footer %p In the following examples, you can see the exact API call you need to diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml index 9201680f119..7e4618e1a88 100644 --- a/app/views/projects/triggers/_trigger.html.haml +++ b/app/views/projects/triggers/_trigger.html.haml @@ -8,9 +8,9 @@ .label-container - if trigger.legacy? - %span.label.label-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy + %span.badge.badge-primary.has-tooltip{ title: "Trigger makes use of deprecated functionality" } legacy - if !trigger.can_access_project? - %span.label.label-danger.has-tooltip{ title: "Trigger user has insufficient permissions to project" } invalid + %span.badge.badge-danger.has-tooltip{ title: "Trigger user has insufficient permissions to project" } invalid %td - if trigger.description? && trigger.description.length > 15 diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index d285251d06f..bcceb69954a 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,13 +1,13 @@ - commit_message = @page.persisted? ? s_("WikiPageEdit|Update %{page_title}") : s_("WikiPageCreate|Create %{page_title}") - commit_message = commit_message % { page_title: @page.title } -= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form common-note-form prepend-top-default js-quick-submit' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'wiki-form common-note-form prepend-top-default js-quick-submit' } do |f| = form_errors(@page) - if @page.persisted? = f.hidden_field :last_commit_sha, value: @page.last_commit_sha - .form-group + .form-group.row .col-sm-12= f.label :title, class: 'control-label-full-width' .col-sm-12 = f.text_field :title, class: 'form-control', value: @page.title @@ -16,12 +16,12 @@ = icon('lightbulb-o') = s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.") = link_to icon('question-circle'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'), target: '_blank' - .form-group + .form-group.row .col-sm-12= f.label :format, class: 'control-label-full-width' .col-sm-12 = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: 'form-control' - .form-group + .form-group.row .col-sm-12= f.label :content, class: 'control-label-full-width' .col-sm-12 = render layout: 'projects/md_preview', locals: { url: project_wiki_preview_markdown_path(@project, @page.slug) } do @@ -31,7 +31,7 @@ .clearfix .error-alert - .help-block + .form-text.text-muted = succeed '.' do = (s_("WikiMarkdownTip|To link to a (new) page, simply type %{link_example}") % { link_example: '<code>[Link Title](page-slug)</code>' }).html_safe @@ -39,16 +39,16 @@ - markdown_link = link_to s_("WikiMarkdownDocs|documentation"), help_page_path('user/markdown', anchor: 'wiki-specific-markdown') = (s_("WikiMarkdownDocs|More examples are in the %{docs_link}") % { docs_link: markdown_link }).html_safe - .form-group + .form-group.row .col-sm-12= f.label :commit_message, class: 'control-label-full-width' .col-sm-12= f.text_field :message, class: 'form-control', rows: 18, value: commit_message .form-actions - if @page && @page.persisted? = f.submit _("Save changes"), class: 'btn-save btn' - .pull-right + .float-right = link_to _("Cancel"), project_wiki_path(@project, @page), class: 'btn btn-cancel btn-grouped' - else = f.submit s_("Wiki|Create page"), class: 'btn-create btn' - .pull-right + .float-right = link_to _("Cancel"), project_wiki_path(@project, :home), class: 'btn btn-cancel' diff --git a/app/views/projects/wikis/_pages_wiki_page.html.haml b/app/views/projects/wikis/_pages_wiki_page.html.haml index efa16d38f84..cbb441d7509 100644 --- a/app/views/projects/wikis/_pages_wiki_page.html.haml +++ b/app/views/projects/wikis/_pages_wiki_page.html.haml @@ -1,5 +1,5 @@ %li = link_to wiki_page.title, project_wiki_path(@project, wiki_page) %small (#{wiki_page.format}) - .pull-right + .float-right %small= (s_("Last edited %{date}") % { date: time_ago_with_tooltip(wiki_page.last_version.authored_date) }).html_safe diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml index 2c7551c6f8c..a23396dc0d8 100644 --- a/app/views/projects/wikis/_sidebar.html.haml +++ b/app/views/projects/wikis/_sidebar.html.haml @@ -1,7 +1,7 @@ %aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } } .sidebar-container .block.wiki-sidebar-header.append-bottom-default - %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } + %a.gutter-toggle.float-right.d-block.d-sm-block.d-md-none.js-sidebar-wiki-toggle{ href: "#" } = icon('angle-double-right') - git_access_url = project_wikis_git_access_path(@project) diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index 10dbbc0e42c..909987d8090 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -2,7 +2,7 @@ - page_title s_("WikiClone|Git Access"), _("Wiki") .wiki-page-header.has-sidebar-toggle - %button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + %button.btn.btn-default.d-block.d-sm-block.d-md-none.float-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } = icon('angle-double-left') .git-access-header diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml index 7d43fd61081..ff9a7b53a86 100644 --- a/app/views/search/_category.html.haml +++ b/app/views/search/_category.html.haml @@ -1,80 +1,80 @@ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .fade-left= icon('angle-left') .fade-right= icon('angle-right') - %ul.nav-links.search-filter.scrolling-tabs + %ul.nav-links.search-filter.scrolling-tabs.nav.nav-tabs - if @project - if project_search_tabs?(:blobs) %li{ class: active_when(@scope == 'blobs') } = link_to search_filter_path(scope: 'blobs') do Code - %span.badge + %span.badge.badge-pill = @search_results.blobs_count - if project_search_tabs?(:issues) %li{ class: active_when(@scope == 'issues') } = link_to search_filter_path(scope: 'issues') do Issues - %span.badge + %span.badge.badge-pill = limited_count(@search_results.limited_issues_count) - if project_search_tabs?(:merge_requests) %li{ class: active_when(@scope == 'merge_requests') } = link_to search_filter_path(scope: 'merge_requests') do Merge requests - %span.badge + %span.badge.badge-pill = limited_count(@search_results.limited_merge_requests_count) - if project_search_tabs?(:milestones) %li{ class: active_when(@scope == 'milestones') } = link_to search_filter_path(scope: 'milestones') do Milestones - %span.badge + %span.badge.badge-pill = limited_count(@search_results.limited_milestones_count) - if project_search_tabs?(:notes) %li{ class: active_when(@scope == 'notes') } = link_to search_filter_path(scope: 'notes') do Comments - %span.badge + %span.badge.badge-pill = limited_count(@search_results.limited_notes_count) - if project_search_tabs?(:wiki) %li{ class: active_when(@scope == 'wiki_blobs') } = link_to search_filter_path(scope: 'wiki_blobs') do Wiki - %span.badge + %span.badge.badge-pill = @search_results.wiki_blobs_count - if project_search_tabs?(:commits) %li{ class: active_when(@scope == 'commits') } = link_to search_filter_path(scope: 'commits') do Commits - %span.badge + %span.badge.badge-pill = @search_results.commits_count - elsif @show_snippets %li{ class: active_when(@scope == 'snippet_blobs') } = link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do Snippet Contents - %span.badge + %span.badge.badge-pill = @search_results.snippet_blobs_count %li{ class: active_when(@scope == 'snippet_titles') } = link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do Titles and Filenames - %span.badge + %span.badge.badge-pill = @search_results.snippet_titles_count - else %li{ class: active_when(@scope == 'projects') } = link_to search_filter_path(scope: 'projects') do Projects - %span.badge + %span.badge.badge-pill = limited_count(@search_results.limited_projects_count) %li{ class: active_when(@scope == 'issues') } = link_to search_filter_path(scope: 'issues') do Issues - %span.badge + %span.badge.badge-pill = limited_count(@search_results.limited_issues_count) %li{ class: active_when(@scope == 'merge_requests') } = link_to search_filter_path(scope: 'merge_requests') do Merge requests - %span.badge + %span.badge.badge-pill = limited_count(@search_results.limited_merge_requests_count) %li{ class: active_when(@scope == 'milestones') } = link_to search_filter_path(scope: 'milestones') do Milestones - %span.badge + %span.badge.badge-pill = limited_count(@search_results.limited_milestones_count) diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index e4902d368e7..6837f64f132 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -11,7 +11,7 @@ - else Any = icon("chevron-down") - .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right + .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right = dropdown_title("Filter results by group") = dropdown_filter("Search groups") = dropdown_content @@ -26,7 +26,7 @@ - else Any = icon("chevron-down") - .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-align-right + .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right = dropdown_title("Filter results by project") = dropdown_filter("Search projects") = dropdown_content diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index b7a27ef6be2..c413c3d4abb 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -4,8 +4,8 @@ = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do %span.term.str-truncated= issue.title - if issue.closed? - %span.label.label-danger.prepend-left-5 Closed - .pull-right ##{issue.iid} + %span.badge.badge-danger.prepend-left-5 Closed + .float-right ##{issue.iid} - if issue.description.present? .description.term = search_md_sanitize(issue, :description) diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 8b0fd74f680..519176af108 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -3,10 +3,10 @@ = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do %span.term.str-truncated= merge_request.title - if merge_request.merged? - %span.label.label-primary.prepend-left-5 Merged + %span.badge.badge-primary.prepend-left-5 Merged - elsif merge_request.closed? - %span.label.label-danger.prepend-left-5 Closed - .pull-right= merge_request.to_reference + %span.badge.badge-danger.prepend-left-5 Closed + .float-right= merge_request.to_reference - if merge_request.description.present? .description.term = search_md_sanitize(merge_request, :description) diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index d46c4d11e51..9c8afb2165b 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -3,13 +3,13 @@ = link_to reliable_snippet_path(snippet_title) do = truncate(snippet_title.title, length: 60) - if snippet_title.private? - %span.label.label-gray + %span.badge.badge-gray %i.fa.fa-lock private - %span.cgray.monospace.tiny.pull-right.term + %span.cgray.monospace.tiny.float-right.term = snippet_title.file_name - %small.pull-right.cgray + %small.float-right.cgray - if snippet_title.project_id? = link_to snippet_title.project.full_name, project_path(snippet_title.project) diff --git a/app/views/shared/_allow_request_access.html.haml b/app/views/shared/_allow_request_access.html.haml index 53a99a736c0..0e698570d7d 100644 --- a/app/views/shared/_allow_request_access.html.haml +++ b/app/views/shared/_allow_request_access.html.haml @@ -1,4 +1,4 @@ -.checkbox +.form-check = form.label :request_access_enabled do = form.check_box :request_access_enabled %strong Allow users to request access diff --git a/app/views/shared/_auto_devops_callout.html.haml b/app/views/shared/_auto_devops_callout.html.haml index d3fa324e460..084d295f2c1 100644 --- a/app/views/shared/_auto_devops_callout.html.haml +++ b/app/views/shared/_auto_devops_callout.html.haml @@ -3,7 +3,7 @@ = custom_icon('icon_autodevops') .banner-body.prepend-left-10.append-bottom-10 - %h5.banner-title= s_('AutoDevOps|Auto DevOps (Beta)') + %h5.banner-title= s_('AutoDevOps|Auto DevOps') %p= s_('AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration.') %p - link = link_to(s_('AutoDevOps|Auto DevOps documentation'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer') diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml index 75c65520350..0552fe62090 100644 --- a/app/views/shared/_choose_group_avatar_button.html.haml +++ b/app/views/shared/_choose_group_avatar_button.html.haml @@ -1,4 +1,4 @@ %button.btn.js-choose-group-avatar-button{ type: 'button' }= _("Choose File ...") %span.file_name.js-avatar-filename= _("No file chosen") = f.file_field :avatar, class: "js-group-avatar-input hidden" -.help-block= _("The maximum file size allowed is 200KB.") +.form-text.text-muted= _("The maximum file size allowed is 200KB.") diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 687cd4d1532..5fc02ba3160 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,13 +1,13 @@ - project = project || @project .git-clone-holder.input-group - .input-group-btn + .input-group-prepend - if allowed_protocols_present? - .clone-dropdown-btn.btn + .input-group-text.clone-dropdown-btn.btn %span = enabled_project_button(project, enabled_protocol) - else - %a#clone-dropdown.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } + %a#clone-dropdown.input-group-text.btn.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } } %span = default_clone_protocol.upcase = icon('caret-down') @@ -18,5 +18,5 @@ = http_clone_button(project) = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true, aria: { label: 'Project clone URL' } - .input-group-btn - = clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard"), class: "btn-default btn-clipboard") + .input-group-append + = clipboard_button(target: '#project_clone', title: _("Copy URL to clipboard"), class: "input-group-text btn-default btn-clipboard") diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index 2329de9e11f..68c14c307ac 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -1,7 +1,7 @@ -.form-group.commit_message-group +.form-group.row.commit_message-group - nonce = SecureRandom.hex - descriptions = local_assigns.slice(:message_with_description, :message_without_description) - = label_tag "commit_message-#{nonce}", class: 'control-label' do + = label_tag "commit_message-#{nonce}", class: 'col-form-label col-sm-2' do #{ _('Commit message') } .col-sm-10 .commit-message-container diff --git a/app/views/shared/_commit_well.html.haml b/app/views/shared/_commit_well.html.haml index 50e3d80a84d..6f1fe9bfdc5 100644 --- a/app/views/shared/_commit_well.html.haml +++ b/app/views/shared/_commit_well.html.haml @@ -1,4 +1,4 @@ -.info-well.hidden-xs.project-last-commit.append-bottom-default +.info-well.d-none.d-sm-block.project-last-commit.append-bottom-default .well-segment %ul.blob-commit-info = render 'projects/commits/commit', commit: commit, ref: ref, project: project diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml index f94f8ffc604..7c326d36d99 100644 --- a/app/views/shared/_confirm_modal.html.haml +++ b/app/views/shared/_confirm_modal.html.haml @@ -2,9 +2,9 @@ .modal-dialog .modal-content .modal-header - %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title Confirmation required + %a.close{ href: "#", "data-dismiss" => "modal" } × .modal-body %p.text-danger.js-confirm-text diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index e7fa7477e0c..ecb5b1c6ebc 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,7 +1,7 @@ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .fade-left= icon('angle-left') .fade-right= icon('angle-right') - %ul.nav-links.event-filter.scrolling-tabs + %ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs = event_filter_link EventFilter.all, _('All'), s_('EventFilterBy|Filter by all') - if event_filter_visible(:repository) = event_filter_link EventFilter.push, _('Push events'), s_('EventFilterBy|Filter by push events') diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml index aea0a8fd8e0..0c8d90d92f5 100644 --- a/app/views/shared/_field.html.haml +++ b/app/views/shared/_field.html.haml @@ -11,9 +11,9 @@ .form-group - if type == "password" && value.present? - = form.label name, "Enter new #{title.downcase}", class: "control-label" + = form.label name, "Enter new #{title.downcase}", class: "col-form-label" - else - = form.label name, title, class: "control-label" + = form.label name, title, class: "col-form-label" .col-sm-10 - if type == 'text' = form.text_field name, class: "form-control", placeholder: placeholder, required: required, disabled: disabled @@ -26,4 +26,4 @@ - elsif type == 'password' = form.password_field name, autocomplete: "new-password", class: "form-control", required: value.blank? && required, disabled: disabled - if help - %span.help-block= help + %span.form-text.text-muted= help diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index 403d22c79f8..dbed4b94d61 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -2,15 +2,16 @@ - group_path = root_url - group_path << parent.full_path + '/' if parent -.form-group - = f.label :path, class: 'control-label' do +.form-group.row + = f.label :path, class: 'col-form-label col-sm-2' do Group path .col-sm-10 .input-group.gl-field-error-anchor - .group-root-path.input-group-addon.has-tooltip{ title: group_path, :'data-placement' => 'bottom' } - %span>= root_url - - if parent - %strong= parent.full_path + '/' + .group-root-path.input-group-prepend.has-tooltip{ title: group_path, :'data-placement' => 'bottom' } + .input-group-text + %span>= root_url + - if parent + %strong= parent.full_path + '/' = f.hidden_field :parent_id = f.text_field :path, placeholder: 'open-source', class: 'form-control', autofocus: local_assigns[:autofocus] || false, required: true, @@ -24,8 +25,8 @@ = succeed '.' do = link_to 'Learn more', help_page_path('user/group/index', anchor: 'changing-a-groups-path'), target: '_blank' -.form-group.group-name-holder - = f.label :name, class: 'control-label' do +.form-group.row.group-name-holder + = f.label :name, class: 'col-form-label col-sm-2' do Group name .col-sm-10 = f.text_field :name, class: 'form-control', @@ -33,14 +34,14 @@ title: 'You can choose a descriptive name different from the path.' - if @group.persisted? - .form-group.group-name-holder - = f.label :id, class: 'control-label' do + .form-group.row.group-name-holder + = f.label :id, class: 'col-form-label col-sm-2' do = _("Group ID") .col-sm-10 = f.text_field :id, class: 'form-control', readonly: true -.form-group.group-description-holder - = f.label :description, class: 'control-label' +.form-group.row.group-description-holder + = f.label :description, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_area :description, maxlength: 250, class: 'form-control js-gfm-input', rows: 4 diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 3806ead6c87..35673303b85 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -1,13 +1,13 @@ - ci_cd_only = local_assigns.fetch(:ci_cd_only, false) -.form-group.import-url-data +.form-group.row.import-url-data = f.label :import_url, class: 'label-light' do %span = _('Git repository URL') = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', required: true - .well.prepend-top-20 + .card.prepend-top-20 %ul %li = _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml index 430d9a9dd76..6cc8c485666 100644 --- a/app/views/shared/_issuable_meta_data.html.haml +++ b/app/views/shared/_issuable_meta_data.html.haml @@ -5,21 +5,21 @@ - issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count - if issuable_mr > 0 - %li.issuable-mr.hidden-xs.has-tooltip{ title: _('Related merge requests') } + %li.issuable-mr.d-none.d-sm-block.has-tooltip{ title: _('Related merge requests') } = image_tag('icon-merge-request-unmerged.svg', class: 'icon-merge-request-unmerged') = issuable_mr - if upvotes > 0 - %li.issuable-upvotes.hidden-xs.has-tooltip{ title: _('Upvotes') } + %li.issuable-upvotes.d-none.d-sm-block.has-tooltip{ title: _('Upvotes') } = icon('thumbs-up') = upvotes - if downvotes > 0 - %li.issuable-downvotes.hidden-xs.has-tooltip{ title: _('Downvotes') } + %li.issuable-downvotes.d-none.d-sm-block.has-tooltip{ title: _('Downvotes') } = icon('thumbs-down') = downvotes -%li.issuable-comments.hidden-xs +%li.issuable-comments.d-none.d-sm-block = link_to issuable_url, class: ['has-tooltip', ('no-comments' if note_count.zero?)], title: _('Comments') do = icon('comments') = note_count diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 49555b6ff4e..987a5d4f13f 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,5 +1,5 @@ - if @issues.to_a.any? - .panel.panel-default.panel-small.panel-without-border + .card.card-small.card-without-border %ul.content-list.issues-list.issuable-list = render partial: 'projects/issues/issue', collection: @issues = paginate @issues, theme: "gitlab" diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index 836df57a3a2..ba5b65a209d 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -8,11 +8,11 @@ %li.label-list-item{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label - .visible-xs.visible-sm-inline-block.dropdown + .d-inline-block.d-sm-none.dropdown %button.btn.btn-default.label-options-toggle{ type: 'button', data: { toggle: "dropdown" } } Options = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right + .dropdown-menu.dropdown-menu-right %ul - if show_label_merge_requests_link %li @@ -46,7 +46,7 @@ data: {confirm: 'Remove this label? Are you sure?'}, class: 'text-danger' - .pull-right.hidden-xs.hidden-sm + .float-right.d-none.d-sm-none.d-md-block - if can?(current_user, :admin_label, label) - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group) %button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'), diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml index bd4f191502e..f1c1ca9b2c9 100644 --- a/app/views/shared/_label_row.html.haml +++ b/app/views/shared/_label_row.html.haml @@ -22,7 +22,7 @@ - if label.description.present? .description-text = markdown_field(label, :description) - .hidden-xs.hidden-sm + .d-none.d-sm-none.d-md-block - if show_label_issues_link = link_to_label(label, subject: subject) { 'Issues' } - if show_label_merge_requests_link diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index 0517896cfbd..700ec4b606f 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -1,5 +1,5 @@ - if @merge_requests.to_a.any? - .panel.panel-default.panel-small.panel-without-border + .card.card-small.card-without-border %ul.content-list.mr-list.issuable-list = render partial: 'projects/merge_requests/merge_request', collection: @merge_requests diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index 034b76b978f..6c1ac20d544 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -1,13 +1,13 @@ -%ul.nav-links.mobile-separator +%ul.nav-links.mobile-separator.nav.nav-tabs %li{ class: milestone_class_for_state(params[:state], 'opened', true) }> = link_to milestones_filter_path(state: 'opened') do Open - %span.badge= counts[:opened] + %span.badge.badge-pill= counts[:opened] %li{ class: milestone_class_for_state(params[:state], 'closed') }> = link_to milestones_filter_path(state: 'closed', sort: 'due_date_desc') do Closed - %span.badge= counts[:closed] + %span.badge.badge-pill= counts[:closed] %li{ class: milestone_class_for_state(params[:state], 'all') }> = link_to milestones_filter_path(state: 'all', sort: 'due_date_desc') do All - %span.badge= counts[:all] + %span.badge.badge-pill= counts[:all] diff --git a/app/views/shared/_milestones_sort_dropdown.html.haml b/app/views/shared/_milestones_sort_dropdown.html.haml index 9b2f2fdcc93..a6ba3b59365 100644 --- a/app/views/shared/_milestones_sort_dropdown.html.haml +++ b/app/views/shared/_milestones_sort_dropdown.html.haml @@ -6,7 +6,7 @@ - else = sort_title_due_date_soon = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort %li = link_to page_filter_path(sort: sort_value_due_date_soon, label: true) do = sort_title_due_date_soon diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml index 9221fd1e025..81c33eeea4f 100644 --- a/app/views/shared/_new_commit_form.html.haml +++ b/app/views/shared/_new_commit_form.html.haml @@ -7,8 +7,8 @@ = hidden_field_tag 'branch_name', @ref - else - if can?(current_user, :push_code, @project) - .form-group.branch - = label_tag 'branch_name', _('Target Branch'), class: 'control-label' + .form-group.row.branch + = label_tag 'branch_name', _('Target Branch'), class: 'col-form-label col-sm-2' .col-sm-10 = text_field_tag 'branch_name', branch_name, required: true, class: "form-control js-branch-name ref-name" diff --git a/app/views/shared/_new_merge_request_checkbox.html.haml b/app/views/shared/_new_merge_request_checkbox.html.haml index 133c31f09c4..8dbdf63f9f4 100644 --- a/app/views/shared/_new_merge_request_checkbox.html.haml +++ b/app/views/shared/_new_merge_request_checkbox.html.haml @@ -1,4 +1,4 @@ -.checkbox +.form-check - nonce = SecureRandom.hex = label_tag "create_merge_request-#{nonce}" do = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml index c5e4d6e2871..cadac1cc99d 100644 --- a/app/views/shared/_personal_access_tokens_table.html.haml +++ b/app/views/shared/_personal_access_tokens_table.html.haml @@ -35,7 +35,7 @@ = text_field_tag 'impersonation-token-token', token.token, readonly: true, class: "form-control" = clipboard_button(text: token.token) - path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token) - %td= link_to "Revoke", path, method: :put, class: "btn btn-danger pull-right", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." } + %td= link_to "Revoke", path, method: :put, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." } - else .settings-message.text-center This user has no active #{type} Tokens. diff --git a/app/views/shared/_project_limit.html.haml b/app/views/shared/_project_limit.html.haml index f4eb8e491b9..2c52eccccb6 100644 --- a/app/views/shared/_project_limit.html.haml +++ b/app/views/shared/_project_limit.html.haml @@ -1,8 +1,8 @@ - if cookies[:hide_project_limit_message].blank? && !current_user.hide_project_limit && !current_user.can_create_project? && current_user.projects_limit > 0 - .project-limit-message.alert.alert-warning.hidden-xs + .project-limit-message.alert.alert-warning.d-none.d-sm-block You won't be able to create new projects because you have reached your project limit. - .pull-right + .float-right = link_to "Don't show again", profile_path(user: {hide_project_limit: true}), method: :put, class: 'alert-link' | = link_to 'Remind later', '#', class: 'hide-project-limit-message alert-link' diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index f1c39b9e923..4e7061eef1c 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -9,7 +9,7 @@ = hidden_field_tag key, value, id: nil .dropdown = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" } - .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-align-right" if local_assigns[:align_right]) } + .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-right" if local_assigns[:align_right]) } .dropdown-page-one = dropdown_title _("Switch branch/tag") = dropdown_filter _("Search branches and tags") diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index a41aaed66a3..0ebf365c7bd 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -3,24 +3,24 @@ - if lookup_context.template_exists?('help', "projects/services/#{@service.to_param}", true) = render "projects/services/#{@service.to_param}/help", subject: subject - elsif @service.help.present? - .well + .card = markdown @service.help .service-settings - if @service.show_active_box? - .form-group - = form.label :active, "Active", class: "control-label" + .form-group.row + = form.label :active, "Active", class: "col-form-label col-sm-2" .col-sm-10 = form.check_box :active, disabled: disable_fields_service?(@service) - if @service.configurable_events.present? - .form-group - = form.label :url, "Trigger", class: 'control-label' + .form-group.row + = form.label :url, "Trigger", class: 'col-form-label col-sm-2' .col-sm-10 - @service.configurable_events.each do |event| %div - = form.check_box service_event_field_name(event), class: 'pull-left' + = form.check_box service_event_field_name(event), class: 'float-left' .prepend-left-20 = form.label service_event_field_name(event), class: 'list-label' do %strong diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 7ff5e679f17..be6d4f1c32b 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -2,10 +2,10 @@ - viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues' .dropdown.inline.prepend-left-10 - %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown' } } + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown', display: 'static' } } = sorted_by = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable.dropdown-menu-sort + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable.dropdown-menu-sort %li = sortable_item(sort_title_priority, page_filter_path(sort: sort_value_priority, label: true), sorted_by) = sortable_item(sort_title_created_date, page_filter_path(sort: sort_value_created_date, label: true), sorted_by) diff --git a/app/views/shared/_visibility_level.html.haml b/app/views/shared/_visibility_level.html.haml index 192d2502aaf..d67409ffe14 100644 --- a/app/views/shared/_visibility_level.html.haml +++ b/app/views/shared/_visibility_level.html.haml @@ -2,7 +2,7 @@ .form-group.visibility-level-setting - if with_label - = f.label :visibility_level, class: 'control-label' do + = f.label :visibility_level, class: 'col-form-label col-sm-2' do Visibility Level = link_to icon('question-circle'), help_page_path("public_access/public_access") %div{ :class => ("col-sm-10" if with_label) } diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml index 0ec7677a566..bdf550cd8b6 100644 --- a/app/views/shared/_visibility_radios.html.haml +++ b/app/views/shared/_visibility_radios.html.haml @@ -2,7 +2,7 @@ - disallowed = disallowed_visibility_level?(form_model, level) - restricted = restricted_visibility_levels.include?(level) - disabled = disallowed || restricted - .radio{ class: [('disabled' if disabled), ('restricted' if restricted)] } + .form-check.pl-0{ class: [('disabled' if disabled), ('restricted' if restricted)] } = form.label "#{model_method}_#{level}" do = form.radio_button model_method, level, checked: (selected_level == level), disabled: disabled = visibility_level_icon(level) diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml index dac60094686..496b94ec953 100644 --- a/app/views/shared/boards/_show.html.haml +++ b/app/views/shared/boards/_show.html.haml @@ -13,7 +13,7 @@ %script#js-board-modal-filter{ type: "text/x-template" }= render "shared/issuable/search_bar", type: :boards_modal #board-app.boards-app{ "v-cloak" => true, data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" } - .hidden-xs.hidden-sm + .d-none.d-sm-none.d-md-block = render 'shared/issuable/search_bar', type: :boards .boards-list diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml index aea40df41b0..67476a3f573 100644 --- a/app/views/shared/boards/components/_board.html.haml +++ b/app/views/shared/boards/components/_board.html.haml @@ -14,7 +14,7 @@ %span.has-tooltip{ "v-if": "list.type === \"label\"", ":title" => '(list.label ? list.label.description : "")', data: { container: "body", placement: "bottom" }, - class: "label color-label title board-title-text", + class: "badge color-label title board-title-text", ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.textColor ? list.label.textColor : \"#2e2e2e\") }" } {{ list.title }} @@ -22,10 +22,10 @@ %board-delete{ "inline-template" => true, ":list" => "list", "v-if" => "!list.preset && list.id" } - %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" } + %button.board-delete.has-tooltip.float-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" } = icon("trash") .issue-count-badge.clearfix{ "v-if" => 'list.type !== "blank"' } - %span.issue-count-badge-count.pull-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } + %span.issue-count-badge-count.float-left{ ":class" => '{ "has-btn": list.type !== "closed" && !disabled }' } {{ list.issuesSize }} - if can?(current_user, :admin_list, current_board_parent) %button.issue-count-badge-add-button.btn.btn-sm.btn-default.has-tooltip.js-no-trigger-collapse{ type: "button", diff --git a/app/views/shared/boards/components/_sidebar.html.haml b/app/views/shared/boards/components/_sidebar.html.haml index b385cc3f962..774dafe5f2c 100644 --- a/app/views/shared/boards/components/_sidebar.html.haml +++ b/app/views/shared/boards/components/_sidebar.html.haml @@ -3,14 +3,14 @@ %aside.right-sidebar.right-sidebar-expanded.issue-boards-sidebar{ "v-show" => "showSidebar" } .issuable-sidebar .block.issuable-sidebar-header - %span.issuable-header-text.hide-collapsed.pull-left + %span.issuable-header-text.hide-collapsed.float-left %strong {{ issue.title }} %br/ %span = precede "#" do {{ issue.iid }} - %a.gutter-toggle.pull-right{ role: "button", + %a.gutter-toggle.float-right{ role: "button", href: "#", "@click.prevent" => "closeSidebar", "aria-label" => "Toggle sidebar" } diff --git a/app/views/shared/boards/components/sidebar/_due_date.html.haml b/app/views/shared/boards/components/sidebar/_due_date.html.haml index d13b998e6f4..10217b6cbf0 100644 --- a/app/views/shared/boards/components/sidebar/_due_date.html.haml +++ b/app/views/shared/boards/components/sidebar/_due_date.html.haml @@ -3,7 +3,7 @@ Due date - if can_admin_issue? = icon("spinner spin", class: "block-loading") - = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right" + = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link float-right" .value .value-content %span.no-value{ "v-if" => "!issue.dueDate" } diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml index 1c73534c642..a112a9f1f7e 100644 --- a/app/views/shared/boards/components/sidebar/_labels.html.haml +++ b/app/views/shared/boards/components/sidebar/_labels.html.haml @@ -3,13 +3,13 @@ Labels - if can_admin_issue? = icon("spinner spin", class: "block-loading") - = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right" + = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link float-right" .value.issuable-show-labels.dont-hide %span.no-value{ "v-if" => "issue.labels && issue.labels.length === 0" } None %a{ href: "#", "v-for" => "label in issue.labels" } - %span.label.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" } + %span.badge.color-label.has-tooltip{ ":style" => "{ backgroundColor: label.color, color: label.textColor }" } {{ label.title }} - if can_admin_issue? .selectbox diff --git a/app/views/shared/boards/components/sidebar/_milestone.html.haml b/app/views/shared/boards/components/sidebar/_milestone.html.haml index f51c4a97f2b..f2bedd5e3c9 100644 --- a/app/views/shared/boards/components/sidebar/_milestone.html.haml +++ b/app/views/shared/boards/components/sidebar/_milestone.html.haml @@ -3,7 +3,7 @@ Milestone - if can_admin_issue? = icon("spinner spin", class: "block-loading") - = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link pull-right" + = link_to "Edit", "#", class: "js-sidebar-dropdown-toggle edit-link float-right" .value %span.no-value{ "v-if" => "!issue.milestone" } None diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml index 0b003125912..5c74e71b644 100644 --- a/app/views/shared/builds/_tabs.html.haml +++ b/app/views/shared/builds/_tabs.html.haml @@ -1,24 +1,24 @@ -%ul.nav-links.mobile-separator +%ul.nav-links.mobile-separator.nav.nav-tabs %li{ class: active_when(scope.nil?) }> = link_to build_path_proc.call(nil) do All - %span.badge.js-totalbuilds-count + %span.badge.badge-pill.js-totalbuilds-count = limited_counter_with_delimiter(all_builds) %li{ class: active_when(scope == 'pending') }> = link_to build_path_proc.call('pending') do Pending - %span.badge + %span.badge.badge-pill = limited_counter_with_delimiter(all_builds.pending) %li{ class: active_when(scope == 'running') }> = link_to build_path_proc.call('running') do Running - %span.badge + %span.badge.badge-pill = limited_counter_with_delimiter(all_builds.running) %li{ class: active_when(scope == 'finished') }> = link_to build_path_proc.call('finished') do Finished - %span.badge + %span.badge.badge-pill = limited_counter_with_delimiter(all_builds.finished) diff --git a/app/views/shared/deploy_keys/_form.html.haml b/app/views/shared/deploy_keys/_form.html.haml index 87c2965bb21..913c065e188 100644 --- a/app/views/shared/deploy_keys/_form.html.haml +++ b/app/views/shared/deploy_keys/_form.html.haml @@ -5,26 +5,26 @@ = form_errors(deploy_key) .form-group - = form.label :title, class: 'control-label' + = form.label :title, class: 'col-form-label col-sm-2' .col-sm-10= form.text_field :title, class: 'form-control' .form-group - if deploy_key.new_record? - = form.label :key, class: 'control-label' + = form.label :key, class: 'col-form-label col-sm-2' .col-sm-10 %p.light Paste a machine public key here. Read more about how to generate it = link_to 'here', help_page_path('ssh/README') = form.text_area :key, class: 'form-control thin_area', rows: 5 - else - = form.label :fingerprint, class: 'control-label' + = form.label :fingerprint, class: 'col-form-label col-sm-2' .col-sm-10 = form.text_field :fingerprint, class: 'form-control', readonly: 'readonly' - if deploy_keys_project.present? = form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form| .form-group - .control-label + .col-form-label.col-sm-2 .col-sm-10 = deploy_keys_project_form.label :can_push do = deploy_keys_project_form.check_box :can_push diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index 62437f5fc9d..c7c33288e9d 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -3,10 +3,10 @@ - has_button = button_path || project_select_button .row.empty-state - .col-xs-12 + .col-12 .svg-content = image_tag 'illustrations/issues.svg' - .col-xs-12 + .col-12 .text-content - if current_user %h4 diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml index 04db9de3606..e8749ee3956 100644 --- a/app/views/shared/empty_states/_labels.html.haml +++ b/app/views/shared/empty_states/_labels.html.haml @@ -1,8 +1,8 @@ .row.empty-state.labels - .col-xs-12 + .col-12 .svg-content = image_tag 'illustrations/labels.svg' - .col-xs-12 + .col-12 .text-content %h4= _("Labels can be applied to issues and merge requests to categorize them.") %p= _("You can also star a label to make it a priority label.") diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml index 2edf3557df4..014220761a9 100644 --- a/app/views/shared/empty_states/_merge_requests.html.haml +++ b/app/views/shared/empty_states/_merge_requests.html.haml @@ -3,10 +3,10 @@ - has_button = button_path || project_select_button .row.empty-state.merge-requests - .col-xs-12 + .col-12 .svg-content = image_tag 'illustrations/merge_requests.svg' - .col-xs-12 + .col-12 .text-content - if has_button %h4 diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml index 38e9899ca4b..e5dfa7dbf71 100644 --- a/app/views/shared/form_elements/_description.html.haml +++ b/app/views/shared/form_elements/_description.html.haml @@ -9,8 +9,8 @@ - else - preview_url = preview_markdown_path(project) -.form-group.detail-page-description - = form.label :description, 'Description', class: 'control-label' +.form-group.row.detail-page-description + = form.label :description, 'Description', class: 'col-form-label col-sm-2' .col-sm-10 = render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do diff --git a/app/views/shared/groups/_dropdown.html.haml b/app/views/shared/groups/_dropdown.html.haml index 8607be9cd06..2237b93a10b 100644 --- a/app/views/shared/groups/_dropdown.html.haml +++ b/app/views/shared/groups/_dropdown.html.haml @@ -13,7 +13,7 @@ %span.dropdown-label = options_hash[default_sort_by] = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable %li.dropdown-header = _("Sort by") - options_hash.each do |value, title| diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index 90395600d4e..a1b901aaffa 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -16,7 +16,7 @@ .avatar-container.s40 = link_to group do - = group_icon(group, class: "avatar s40 hidden-xs") + = group_icon(group, class: "avatar s40 d-none d-sm-block") .title = link_to group.full_name, group, class: 'group-name' diff --git a/app/views/shared/hook_logs/_content.html.haml b/app/views/shared/hook_logs/_content.html.haml index c80b179d525..532712ee6d1 100644 --- a/app/views/shared/hook_logs/_content.html.haml +++ b/app/views/shared/hook_logs/_content.html.haml @@ -6,8 +6,8 @@ %p %strong Trigger: - %td.hidden-xs - %span.label.label-gray.deploy-project-label + %td.d-none.d-sm-block + %span.badge.badge-gray.deploy-project-label = hook_log.trigger.singularize.titleize %p %strong Elapsed time: diff --git a/app/views/shared/hook_logs/_status_label.html.haml b/app/views/shared/hook_logs/_status_label.html.haml index b4ea8e6f952..993880b7d6e 100644 --- a/app/views/shared/hook_logs/_status_label.html.haml +++ b/app/views/shared/hook_logs/_status_label.html.haml @@ -1,3 +1,3 @@ -- label_status = hook_log.success? ? 'label-success' : 'label-danger' +- label_status = hook_log.success? ? 'badge-success' : 'badge-danger' %span{ class: "label #{label_status}" } = hook_log.response_status diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml index 0d507cc7a6e..ca02424215c 100644 --- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml +++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml @@ -4,9 +4,9 @@ .issuable-sidebar.hidden = form_tag [:bulk_update, @project.namespace.becomes(Namespace), @project, type], method: :post, class: "bulk-update" do .block.issuable-sidebar-header - .filter-item.inline.update-issues-btn.pull-left + .filter-item.inline.update-issues-btn.float-left = button_tag "Update all", class: "btn update-selected-issues btn-info", disabled: true - = button_tag "Cancel", class: "btn btn-default js-bulk-update-menu-hide pull-right" + = button_tag "Cancel", class: "btn btn-default js-bulk-update-menu-hide float-right" .block .title Status diff --git a/app/views/shared/issuable/_close_reopen_button.html.haml b/app/views/shared/issuable/_close_reopen_button.html.haml index 9ef015047c9..933d4b2ea65 100644 --- a/app/views/shared/issuable/_close_reopen_button.html.haml +++ b/app/views/shared/issuable/_close_reopen_button.html.haml @@ -4,11 +4,11 @@ - if can_update && is_current_user = link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method, - class: "hidden-xs hidden-sm btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}" + class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}" = link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method, - class: "hidden-xs hidden-sm btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}" + class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}" - elsif can_update && !is_current_user = render 'shared/issuable/close_reopen_report_toggle', issuable: issuable - else = link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)), - class: 'hidden-xs hidden-sm btn btn-grouped btn-close-color', title: 'Report abuse' + class: 'd-none d-sm-none d-md-block btn btn-grouped btn-close-color', title: 'Report abuse' diff --git a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml index 39a5171c1d6..0d59c9304b4 100644 --- a/app/views/shared/issuable/_close_reopen_report_toggle.html.haml +++ b/app/views/shared/issuable/_close_reopen_report_toggle.html.haml @@ -1,12 +1,12 @@ - display_issuable_type = issuable_display_type(issuable) - button_action = issuable.closed? ? 'reopen' : 'close' - display_button_action = button_action.capitalize -- button_responsive_class = 'hidden-xs hidden-sm' +- button_responsive_class = 'd-none d-sm-none d-md-block' - button_class = "#{button_responsive_class} btn btn-grouped js-issuable-close-button js-btn-issue-action issuable-close-button" - toggle_class = "#{button_responsive_class} btn btn-nr dropdown-toggle js-issuable-close-toggle" - button_method = issuable_close_reopen_button_method(issuable) -.pull-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown +.float-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown = link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_path(issuable), method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}" @@ -14,7 +14,7 @@ data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do = icon('caret-down', class: 'toggle-icon icon') - %ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ class: button_responsive_class, data: { dropdown: true } } + %ul#issuable-close-menu.js-issuable-close-menu.dropdown-menu{ data: { dropdown: true } } %li.close-item{ class: "#{issuable_button_visibility(issuable, true) || 'droplab-item-selected'}", data: { text: "Close #{display_issuable_type}", url: close_issuable_path(issuable), button_class: "#{button_class} btn-close", toggle_class: "#{toggle_class} btn-close-color", method: button_method } } diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index 1bd5b4164b1..1cd8ce0826c 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -25,7 +25,7 @@ = render "shared/issuable/label_dropdown", selected: selected_labels, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - unless @no_filters_set - .pull-right + .float-right = render 'shared/sort_dropdown' - has_labels = @labels && @labels.any? diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 4c8f03f1498..fbc608b207a 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -11,8 +11,8 @@ = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank", rel: 'noopener noreferrer' and make sure your changes will not unintentionally remove theirs -.form-group - = form.label :title, class: 'control-label' +.form-group.row + = form.label :title, class: 'col-form-label col-sm-2' = render 'shared/issuable/form/template_selector', issuable: issuable = render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?) @@ -20,9 +20,9 @@ = render 'shared/form_elements/description', model: issuable, form: form, project: project - if issuable.respond_to?(:confidential) - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox + .form-group.row + .offset-sm-2.col-sm-10 + .form-check = form.label :confidential do = form.check_box :confidential This issue is confidential and should only be visible to team members with at least Reporter access. @@ -36,8 +36,8 @@ = render 'shared/issuable/form/contribution', issuable: issuable, form: form - if @merge_request_to_resolve_discussions_of - .form-group - .col-sm-10.col-sm-offset-2 + .form-group.row + .col-sm-10.offset-sm-2 = icon('info-circle') - if @merge_request_to_resolve_discussions_of.discussions_can_be_resolved_by?(current_user) = hidden_field_tag 'merge_request_to_resolve_discussions_of', @merge_request_to_resolve_discussions_of.iid @@ -57,7 +57,7 @@ - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) .row-content-block{ class: (is_footer ? "footer-block" : "middle-block") } - .pull-right + .float-right - if issuable.new_record? = link_to 'Cancel', polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class]), class: 'btn btn-cancel' - else diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml index 91aa329eb93..9f4021802df 100644 --- a/app/views/shared/issuable/_label_page_create.html.haml +++ b/app/views/shared/issuable/_label_page_create.html.haml @@ -12,7 +12,7 @@ .dropdown-label-color-preview.js-dropdown-label-color-preview %input#new_label_color.default-dropdown-input{ type: "text", placeholder: _('Assign custom color like #FF0000') } .clearfix - %button.btn.btn-primary.pull-left.js-new-label-btn{ type: "button" } + %button.btn.btn-primary.float-left.js-new-label-btn{ type: "button" } = _('Create') - %button.btn.btn-default.pull-right.js-cancel-label-btn{ type: "button" } + %button.btn.btn-default.float-right.js-cancel-label-btn{ type: "button" } = _('Cancel') diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index a5f40ea934b..157637dbd11 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -2,7 +2,7 @@ - page_context_word = type.to_s.humanize(capitalize: false) - display_count = local_assigns.fetch(:display_count, :true) -%ul.nav-links.issues-state-filters.mobile-separator +%ul.nav-links.issues-state-filters.mobile-separator.nav.nav-tabs %li{ class: active_when(params[:state] == 'opened') }> = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened.", data: { state: 'opened' } do #{issuables_state_counter_text(type, :opened, display_count)} diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index fc6f71ef60f..644f7c4dd28 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -107,7 +107,7 @@ .dropdown.prepend-left-10#js-add-list %button.btn.btn-create.btn-inverted.js-new-board-list{ type: "button", data: board_list_data } Add list - .dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable + .dropdown-menu.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" } - if can?(current_user, :admin_label, board.parent) = render partial: "shared/issuable/label_page_create" diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 093033775a9..602729b172a 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -5,9 +5,9 @@ - can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project) .block.issuable-sidebar-header - if current_user - %span.issuable-header-text.hide-collapsed.pull-left + %span.issuable-header-text.hide-collapsed.float-left = _('Todo') - %a.gutter-toggle.pull-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left' } } + %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } } = sidebar_gutter_toggle_icon - if current_user = render "shared/issuable/sidebar_todo", todo: todo, issuable: issuable @@ -19,7 +19,7 @@ .block.assignee = render "shared/issuable/sidebar_assignees", issuable: issuable, can_edit_issuable: can_edit_issuable, signed_in: current_user.present? .block.milestone - .sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } } + .sidebar-collapsed-icon.has-tooltip{ title: milestone_tooltip_title(issuable.milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } = icon('clock-o', 'aria-hidden': 'true') %span.milestone-title - if issuable.milestone @@ -30,10 +30,10 @@ = _('Milestone') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable - = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' + = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.hide-collapsed - if issuable.milestone - = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 1 } + = link_to issuable.milestone.title, milestone_path(issuable.milestone), class: "bold has-tooltip", title: milestone_tooltip_due_date(issuable.milestone), data: { container: "body", html: 'true' } - else %span.no-value = _('None') @@ -49,7 +49,7 @@ = icon('spinner spin', 'aria-hidden': 'true') - if issuable.has_attribute?(:due_date) .block.due_date - .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 1 }, title: sidebar_due_date_tooltip_label(issuable) } + .sidebar-collapsed-icon.has-tooltip{ data: { placement: 'left', container: 'body', html: 'true', boundary: 'viewport' }, title: sidebar_due_date_tooltip_label(issuable) } = icon('calendar', 'aria-hidden': 'true') %span.js-due-date-sidebar-value = issuable.due_date.try(:to_s, :medium) || 'None' @@ -57,7 +57,7 @@ = _('Due date') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' + = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.hide-collapsed %span.value-content - if issuable.due_date @@ -86,7 +86,7 @@ - if @labels - selected_labels = issuable.labels .block.labels - .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } } + .sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body", boundary: 'viewport' } } = icon('tags', 'aria-hidden': 'true') %span = selected_labels.size @@ -94,7 +94,7 @@ = _('Labels') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable - = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' + = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.issuable-show-labels.dont-hide.hide-collapsed{ class: ("has-labels" if selected_labels.any?) } - if selected_labels.any? - selected_labels.each do |label| @@ -133,7 +133,7 @@ - project_ref = cross_project_reference(@project, issuable) .block.project-reference .sidebar-collapsed-icon.dont-change-state - = clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left") + = clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left", boundary: 'viewport') .cross-project-reference.hide-collapsed %span = _('Reference:') @@ -142,11 +142,11 @@ = clipboard_button(text: project_ref, title: _('Copy reference to clipboard'), placement: "left") - if current_user && issuable.can_move?(current_user) .block.js-sidebar-move-issue-block - .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body' }, title: _('Move issue') } + .sidebar-collapsed-icon{ data: { toggle: 'tooltip', placement: 'left', container: 'body', boundary: 'viewport' }, title: _('Move issue') } = custom_icon('icon_arrow_right') .dropdown.sidebar-move-issue-dropdown.hide-collapsed %button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button', - data: { toggle: 'dropdown' } } + data: { toggle: 'dropdown', display: 'static' } } = _('Move issue') .dropdown-menu.dropdown-menu-selectable = dropdown_title(_('Move issue')) diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml index 21006a76b28..e1cde527ad7 100644 --- a/app/views/shared/issuable/_sidebar_assignees.html.haml +++ b/app/views/shared/issuable/_sidebar_assignees.html.haml @@ -4,7 +4,7 @@ = _('Assignee') = icon('spinner spin') - else - .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: sidebar_assignee_tooltip_label(issuable) } + .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body", boundary: 'viewport' }, title: sidebar_assignee_tooltip_label(issuable) } - if issuable.assignee = link_to_member(@project, issuable.assignee, size: 24) - else @@ -13,15 +13,15 @@ = _('Assignee') = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable - = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' + = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' - if !signed_in - %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') } + %a.gutter-toggle.float-right.js-sidebar-toggle{ role: "button", href: "#", "aria-label" => _('Toggle sidebar') } = sidebar_gutter_toggle_icon .value.hide-collapsed - if issuable.assignee = link_to_member(@project, issuable.assignee, size: 32, extra_class: 'bold') do - if !issuable.can_be_merged_by?(issuable.assignee) - %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') } + %span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: _('Not allowed to merge') } = icon('exclamation-triangle', 'aria-hidden': 'true') %span.username = issuable.assignee.to_reference diff --git a/app/views/shared/issuable/_sidebar_todo.html.haml b/app/views/shared/issuable/_sidebar_todo.html.haml index 74327fb1ba8..583b33a8a1b 100644 --- a/app/views/shared/issuable/_sidebar_todo.html.haml +++ b/app/views/shared/issuable/_sidebar_todo.html.haml @@ -3,7 +3,7 @@ - todo_content = is_collapsed ? icon('plus-square') : _('Add todo') %button.issuable-todo-btn.js-issuable-todo{ type: 'button', - class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn pull-right'), + class: (is_collapsed ? 'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip' : 'btn btn-default issuable-header-btn float-right'), title: (todo.nil? ? _('Add todo') : _('Mark todo as done')), 'aria-label' => (todo.nil? ? _('Add todo') : _('Mark todo as done')), data: issuable_todo_button_data(issuable, todo, is_collapsed) } diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml index 9a589387255..fbc96baa0f7 100644 --- a/app/views/shared/issuable/form/_branch_chooser.html.haml +++ b/app/views/shared/issuable/form/_branch_chooser.html.haml @@ -6,13 +6,13 @@ %hr - if issuable.new_record? - .form-group - = form.label :source_branch, class: 'control-label' + .form-group.row + = form.label :source_branch, class: 'col-form-label col-sm-2' .col-sm-10 .issuable-form-select-holder = form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2 ref-name', disabled: true }) -.form-group - = form.label :target_branch, class: 'control-label' +.form-group.row + = form.label :target_branch, class: 'col-form-label col-sm-2' .col-sm-10.target-branch-select-dropdown-container .issuable-form-select-holder = form.hidden_field(:target_branch, diff --git a/app/views/shared/issuable/form/_contribution.html.haml b/app/views/shared/issuable/form/_contribution.html.haml index de508278d7c..f12c23cbc64 100644 --- a/app/views/shared/issuable/form/_contribution.html.haml +++ b/app/views/shared/issuable/form/_contribution.html.haml @@ -8,13 +8,13 @@ %hr .form-group - .control-label + .col-form-label = _('Contribution') .col-sm-10 - .checkbox + .form-check = form.label :allow_maintainer_to_push do = form.check_box :allow_maintainer_to_push, disabled: !issuable.can_allow_maintainer_to_push?(current_user) = _('Allow edits from maintainers.') = link_to 'About this feature', help_page_path('user/project/merge_requests/maintainer_access') - .help-block + .form-text.text-muted = allow_maintainer_push_unavailable_reason(issuable) diff --git a/app/views/shared/issuable/form/_issue_assignee.html.haml b/app/views/shared/issuable/form/_issue_assignee.html.haml index 9b2b6e572e7..0d13fccaf3e 100644 --- a/app/views/shared/issuable/form/_issue_assignee.html.haml +++ b/app/views/shared/issuable/form/_issue_assignee.html.haml @@ -11,7 +11,7 @@ Assignee = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable - = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' + = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.hide-collapsed - if assignees.any? - assignees.each do |assignee| diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml index 8f6509a8ce8..1df881e4102 100644 --- a/app/views/shared/issuable/form/_merge_params.html.haml +++ b/app/views/shared/issuable/form/_merge_params.html.haml @@ -7,10 +7,10 @@ -# This comment and the following line should only exist in CE. - return unless issuable.can_remove_source_branch?(current_user) -.form-group - .col-sm-10.col-sm-offset-2 +.form-group.row + .col-sm-10.offset-sm-2 - if issuable.can_remove_source_branch?(current_user) - .checkbox + .form-check = label_tag 'merge_request[force_remove_source_branch]' do = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch? diff --git a/app/views/shared/issuable/form/_merge_request_assignee.html.haml b/app/views/shared/issuable/form/_merge_request_assignee.html.haml index d7740eddcca..05c03dedd91 100644 --- a/app/views/shared/issuable/form/_merge_request_assignee.html.haml +++ b/app/views/shared/issuable/form/_merge_request_assignee.html.haml @@ -9,12 +9,12 @@ Assignee = icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true') - if can_edit_issuable - = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link pull-right' + = link_to 'Edit', '#', class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.hide-collapsed - if merge_request.assignee = link_to_member(@project, merge_request.assignee, size: 32, extra_class: 'bold') do - unless merge_request.can_be_merged_by?(merge_request.assignee) - %span.pull-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' } + %span.float-right.cannot-be-merged{ data: { toggle: 'tooltip', placement: 'left' }, title: 'Not allowed to merge' } = icon('exclamation-triangle', 'aria-hidden': 'true') %span.username = merge_request.assignee.to_reference diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml index 1608bd59cf1..1e27253aaeb 100644 --- a/app/views/shared/issuable/form/_metadata.html.haml +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -8,28 +8,28 @@ %hr .row - %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } - .form-group.issue-assignee + %div{ class: (has_due_date ? "col-lg-6" : "col-12") } + .form-group.row.issue-assignee - if issuable.is_a?(Issue) = render "shared/issuable/form/metadata_issue_assignee", issuable: issuable, form: form, has_due_date: has_due_date - else = render "shared/issuable/form/metadata_merge_request_assignee", issuable: issuable, form: form, has_due_date: has_due_date - .form-group.issue-milestone - = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" - .col-sm-10{ class: ("col-lg-8" if has_due_date) } + .form-group.row.issue-milestone + = form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-2"}" + .col-10{ class: ("col-md-8" if has_due_date) } .issuable-form-select-holder = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, show_started: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" - .form-group + .form-group.row - has_labels = @labels && @labels.any? - = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" + = form.label :label_ids, "Labels", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-2"}" = form.hidden_field :label_ids, multiple: true, value: '' - .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } + .col-10{ class: "#{"col-md-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" - if has_due_date .col-lg-6 - .form-group - = form.label :due_date, "Due date", class: "control-label" - .col-sm-10 + .form-group.row + = form.label :due_date, "Due date", class: "col-form-label col-md-2 col-lg-4" + .col-8 .issuable-form-select-holder = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" diff --git a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml index 567cde764e2..6d4f9ccd66f 100644 --- a/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml +++ b/app/views/shared/issuable/form/_metadata_issue_assignee.html.haml @@ -1,5 +1,5 @@ -= form.label :assignee_ids, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" -.col-sm-10{ class: ("col-lg-8" if has_due_date) } += form.label :assignee_ids, "Assignee", class: "col-form-label #{"col-md-2 col-lg-4" if has_due_date}" +.col-sm-10{ class: ("col-md-8" if has_due_date) } .issuable-form-select-holder.selectbox - issuable.assignees.each do |assignee| = hidden_field_tag "#{issuable.to_ability_name}[assignee_ids][]", assignee.id, id: nil, data: { meta: assignee.name, avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username } diff --git a/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml b/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml index d0ea4e149df..3521f71f409 100644 --- a/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml +++ b/app/views/shared/issuable/form/_metadata_merge_request_assignee.html.haml @@ -1,4 +1,4 @@ -= form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" += form.label :assignee_id, "Assignee", class: "col-form-label #{has_due_date ? "col-lg-4" : "col-sm-2"}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder = form.hidden_field :assignee_id diff --git a/app/views/shared/issuable/form/_title.html.haml b/app/views/shared/issuable/form/_title.html.haml index e81639f35ea..c4f30f5f4d9 100644 --- a/app/views/shared/issuable/form/_title.html.haml +++ b/app/views/shared/issuable/form/_title.html.haml @@ -9,7 +9,7 @@ autocomplete: 'off', class: 'form-control pad qa-issuable-form-title' - if issuable.respond_to?(:work_in_progress?) - %p.help-block + %p.form-text.text-muted .js-wip-explanation %a.js-toggle-wip{ href: '', tabindex: -1 } Remove the @@ -30,7 +30,7 @@ merge request from being merged before it's ready. - if no_issuable_templates && can?(current_user, :push_code, issuable.project) - %p.help-block + %p.form-text.text-muted Add = link_to 'description templates', help_page_path('user/project/description_templates'), tabindex: -1 to help your contributors communicate effectively! diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml index e8b04f56839..f79f66b144f 100644 --- a/app/views/shared/labels/_form.html.haml +++ b/app/views/shared/labels/_form.html.haml @@ -1,21 +1,22 @@ -= form_for @label, as: :label, url: url, html: { class: 'form-horizontal label-form js-quick-submit js-requires-input' } do |f| += form_for @label, as: :label, url: url, html: { class: 'label-form js-quick-submit js-requires-input' } do |f| = form_errors(@label) - .form-group - = f.label :title, class: 'control-label' + .form-group.row + = f.label :title, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :title, class: "form-control", required: true, autofocus: true - .form-group - = f.label :description, class: 'control-label' + .form-group.row + = f.label :description, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :description, class: "form-control js-quick-submit" - .form-group - = f.label :color, "Background color", class: 'control-label' + .form-group.row + = f.label :color, "Background color", class: 'col-form-label col-sm-2' .col-sm-10 .input-group - .input-group-addon.label-color-preview + .input-group-prepend + .input-group-text.label-color-preview = f.text_field :color, class: "form-control" - .help-block + .form-text.text-muted Choose any color. %br Or you can choose one of suggested colors below diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index fc634856061..67b8843a27f 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -14,7 +14,7 @@ %span{ class: ('text-warning' if group_link.expires_soon?) } Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls - = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do + = form_tag project_group_link_path(@project, group_link), method: :put, remote: true, class: 'js-edit-member-form' do = hidden_field_tag "group_link[group_access]", group_link.group_access .member-form-control.dropdown.append-right-5 %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button", @@ -23,7 +23,7 @@ %span.dropdown-toggle-text = group_link.human_access = icon("chevron-down") - .dropdown-menu.dropdown-select.dropdown-menu-align-right.dropdown-menu-selectable + .dropdown-menu.dropdown-select.dropdown-menu-right.dropdown-menu-selectable = dropdown_title("Change permissions") .dropdown-content %ul @@ -40,6 +40,6 @@ method: :delete, data: { confirm: "Are you sure you want to remove #{group.name}?" }, class: 'btn btn-remove prepend-left-10' do - %span.visible-xs-block + %span.d-block.d-sm-none Delete - = icon('trash', class: 'hidden-xs') + = icon('trash', class: 'd-none d-sm-block') diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 1961ad6d616..42b2d27c44a 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -14,10 +14,10 @@ %span.cgray= user.to_reference - if user == current_user - %span.label.label-success.prepend-left-5 It's you + %span.badge.badge-success.prepend-left-5 It's you - if user.blocked? - %label.label.label-danger + %label.badge.badge-danger %strong Blocked - if user.two_factor_enabled? @@ -57,11 +57,11 @@ - if member.can_resend_invite? = link_to icon('paper-plane'), polymorphic_path([:resend_invite, member]), method: :post, - class: 'btn btn-default prepend-left-10 hidden-xs', + class: 'btn btn-default prepend-left-10 d-none d-sm-block', title: 'Resend invite' - if user != current_user && member.can_update? - = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| + = form_for member, remote: true, html: { class: 'js-edit-member-form form-horizontal' } do |f| = f.hidden_field :access_level .member-form-control.dropdown.append-right-5 %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button", @@ -69,7 +69,7 @@ %span.dropdown-toggle-text = member.human_access = icon("chevron-down") - .dropdown-menu.dropdown-select.dropdown-menu-align-right.dropdown-menu-selectable + .dropdown-menu.dropdown-select.dropdown-menu-right.dropdown-menu-selectable = dropdown_title("Change permissions") .dropdown-content %ul @@ -93,10 +93,10 @@ method: :post, class: 'btn btn-success prepend-left-10', title: 'Grant access' do - %span{ class: ('visible-xs-block' unless force_mobile_view) } + %span{ class: ('d-block d-sm-none' unless force_mobile_view) } Grant access - unless force_mobile_view - = icon('check inverse', class: 'hidden-xs') + = icon('check inverse', class: 'd-none d-sm-block') - if member.can_remove? - if current_user == user @@ -110,9 +110,9 @@ data: { confirm: remove_member_message(member) }, class: 'btn btn-remove prepend-left-10', title: remove_member_title(member) do - %span{ class: ('visible-xs-block' unless force_mobile_view) } + %span{ class: ('d-block d-sm-none' unless force_mobile_view) } Delete - unless force_mobile_view - = icon('trash', class: 'hidden-xs') + = icon('trash', class: 'd-none d-sm-block') - else %span.member-access-text= member.human_access diff --git a/app/views/shared/members/_requests.html.haml b/app/views/shared/members/_requests.html.haml index 1fbd6bcc4cb..54679ab86cc 100644 --- a/app/views/shared/members/_requests.html.haml +++ b/app/views/shared/members/_requests.html.haml @@ -4,10 +4,10 @@ - return if requesters.empty? -.panel.panel-default.prepend-top-default{ class: ('panel-mobile' if force_mobile_view ) } - .panel-heading +.card.prepend-top-default{ class: ('card-mobile' if force_mobile_view ) } + .card-header Users requesting access to %strong= membership_source.name - %span.badge= requesters.size + %span.badge.badge-pill= requesters.size %ul.content-list.members-list = render partial: 'shared/members/member', collection: requesters, as: :member, locals: { force_mobile_view: force_mobile_view } diff --git a/app/views/shared/members/_sort_dropdown.html.haml b/app/views/shared/members/_sort_dropdown.html.haml index bad0891f9f2..56b8c8f033e 100644 --- a/app/views/shared/members/_sort_dropdown.html.haml +++ b/app/views/shared/members/_sort_dropdown.html.haml @@ -1,6 +1,6 @@ .dropdown.inline.member-sort-dropdown = dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' }) - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable %li.dropdown-header Sort by - member_sort_options_hash.each do |value, title| diff --git a/app/views/shared/milestones/_form_dates.html.haml b/app/views/shared/milestones/_form_dates.html.haml index a74cdbe274b..608dd35182d 100644 --- a/app/views/shared/milestones/_form_dates.html.haml +++ b/app/views/shared/milestones/_form_dates.html.haml @@ -1,12 +1,11 @@ .col-md-6 - .form-group - = f.label :start_date, "Start Date", class: "control-label" + .form-group.row + = f.label :start_date, "Start Date", class: "col-form-label col-sm-2" .col-sm-10 = f.text_field :start_date, class: "datepicker form-control", placeholder: "Select start date" - %a.inline.pull-right.prepend-top-5.js-clear-start-date{ href: "#" } Clear start date -.col-md-6 - .form-group - = f.label :due_date, "Due Date", class: "control-label" + %a.inline.float-right.prepend-top-5.js-clear-start-date{ href: "#" } Clear start date + .form-group.row + = f.label :due_date, "Due Date", class: "col-form-label col-sm-2" .col-sm-10 = f.text_field :due_date, class: "datepicker form-control", placeholder: "Select due date" - %a.inline.pull-right.prepend-top-5.js-clear-due-date{ href: "#" } Clear due date + %a.inline.float-right.prepend-top-5.js-clear-due-date{ href: "#" } Clear due date diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml index 7175e275f95..d8e4d2ff88c 100644 --- a/app/views/shared/milestones/_issuables.html.haml +++ b/app/views/shared/milestones/_issuables.html.haml @@ -1,9 +1,9 @@ - show_counter = local_assigns.fetch(:show_counter, false) - primary = local_assigns.fetch(:primary, false) -- panel_class = primary ? 'panel-primary' : 'panel-default' +- panel_class = primary ? 'bg-primary text-white' : '' -.panel{ class: panel_class } - .panel-heading +.card{ class: panel_class } + .card-header .title = title - if show_counter diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml index a26b3b8009e..6797520650d 100644 --- a/app/views/shared/milestones/_labels_tab.html.haml +++ b/app/views/shared/milestones/_labels_tab.html.haml @@ -10,7 +10,7 @@ %span.prepend-description-left = markdown_field(label, :description) - .pull-right.hidden-xs.hidden-sm.hidden-md + .float-right.d-none.d-lg-block.d-xl-block = link_to milestones_label_path(options.merge(state: 'opened')), class: 'btn btn-transparent btn-action' do - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), 'open issue' = link_to milestones_label_path(options.merge(state: 'closed')), class: 'btn btn-transparent btn-action' do diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index ac494814f55..09bbd04c2bf 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -11,7 +11,7 @@ %span - Project Milestone .col-sm-6 - .pull-right.light #{milestone.percent_complete(current_user)}% complete + .float-right.light #{milestone.percent_complete(current_user)}% complete .row .col-sm-6 = link_to pluralize(milestone.total_issues_count(current_user), 'Issue'), issues_path @@ -26,19 +26,19 @@ .projects - milestone.milestones.each do |milestone| = link_to milestone_path(milestone) do - %span.label.label-gray + %span.badge.badge-gray = dashboard ? milestone.project.full_name : milestone.project.name - if @group .col-sm-6.milestone-actions - if can?(current_user, :admin_milestones, @group) - if milestone.group_milestone? - = link_to edit_group_milestone_path(@group, milestone), class: "btn btn-xs btn-grouped" do + = link_to edit_group_milestone_path(@group, milestone), class: "btn btn-sm btn-grouped" do Edit \ - if milestone.closed? - = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen" + = link_to 'Reopen Milestone', group_milestone_route(milestone, {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" - else - = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-xs btn-grouped btn-close" + = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close" - if @project .row @@ -46,12 +46,12 @@ = render('shared/milestone_expired', milestone: milestone) .col-sm-6.milestone-actions - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-xs btn-grouped" do + = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-sm btn-grouped" do Edit \ - if @project.group - %button.js-promote-project-milestone-button.btn.btn-xs.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'), + %button.js-promote-project-milestone-button.btn.btn-sm.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'), disabled: true, type: 'button', data: { url: promote_project_milestone_path(milestone.project, milestone), @@ -62,9 +62,9 @@ toggle: 'modal' } } = _('Promote') - = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close btn-grouped" + = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close btn-grouped" - %button.js-delete-milestone-button.btn.btn-xs.btn-grouped.btn-danger{ data: { toggle: 'modal', + %button.js-delete-milestone-button.btn.btn-sm.btn-grouped.btn-danger{ data: { toggle: 'modal', target: '#delete-milestone-modal', milestone_id: milestone.id, milestone_title: markdown_field(milestone, :title), diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index 8e9a1b56bb8..95138af3950 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -4,7 +4,7 @@ %aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => affix_offset, "spy" => "affix", "always-show-toggle" => true }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' } .issuable-sidebar.milestone-sidebar .block.milestone-progress.issuable-sidebar-header - %a.gutter-toggle.pull-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left' } } + %a.gutter-toggle.float-right.js-sidebar-toggle.has-tooltip{ role: "button", href: "#", "aria-label" => "Toggle sidebar", title: sidebar_gutter_tooltip_text, data: { container: 'body', placement: 'left', boundary: 'viewport' } } = sidebar_gutter_toggle_icon .title.hide-collapsed %strong.bold== #{milestone.percent_complete(current_user)}% @@ -14,7 +14,7 @@ = milestone_progress_bar(milestone) .block.milestone-progress.hide-expanded - .sidebar-collapsed-icon.has-tooltip{ title: milestone_progress_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } } + .sidebar-collapsed-icon.has-tooltip{ title: milestone_progress_tooltip_text(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } %span== #{milestone.percent_complete(current_user)}% = milestone_progress_bar(milestone) @@ -22,7 +22,7 @@ .title Start date - if @project && can?(current_user, :admin_milestone, @project) - = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'js-sidebar-dropdown-toggle edit-link pull-right' + = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'js-sidebar-dropdown-toggle edit-link float-right' .value %span.value-content - if milestone.start_date @@ -36,29 +36,29 @@ %span.collapsed-milestone-date - if milestone.start_date && milestone.due_date - if milestone.start_date.year == milestone.due_date.year - .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } } + .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } = milestone.start_date.strftime('%b %-d') - else - .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } } + .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } = milestone.start_date.strftime('%b %-d %Y') .date-separator - - .due_date.has-tooltip{ title: milestone_time_for(milestone.due_date, :end), data: { container: 'body', html: 1, placement: 'left' } } + .due_date.has-tooltip{ title: milestone_time_for(milestone.due_date, :end), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } = milestone.due_date.strftime('%b %-d %Y') - elsif milestone.start_date From - .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } } + .milestone-date.has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } = milestone.start_date.strftime('%b %-d %Y') - elsif milestone.due_date Until - .milestone-date.has-tooltip{ title: milestone_time_for(milestone.due_date, :end), data: { container: 'body', html: 1, placement: 'left' } } + .milestone-date.has-tooltip{ title: milestone_time_for(milestone.due_date, :end), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } = milestone.due_date.strftime('%b %-d %Y') - else - .has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 1, placement: 'left' } } + .has-tooltip{ title: milestone_time_for(milestone.start_date, :start), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } None .title.hide-collapsed Due date - if @project && can?(current_user, :admin_milestone, @project) - = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'js-sidebar-dropdown-toggle edit-link pull-right' + = link_to 'Edit', edit_project_milestone_path(@project, @milestone), class: 'js-sidebar-dropdown-toggle edit-link float-right' .value.hide-collapsed %span.value-content - if milestone.due_date @@ -72,15 +72,15 @@ - if !project || can?(current_user, :read_issue, project) .block.issues - .sidebar-collapsed-icon.has-tooltip{ title: milestone_issues_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } } + .sidebar-collapsed-icon.has-tooltip{ title: milestone_issues_tooltip_text(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } %strong = custom_icon('issues') %span= milestone.issues_visible_to_user(current_user).count .title.hide-collapsed Issues - %span.badge= milestone.issues_visible_to_user(current_user).count + %span.badg.badge-pille= milestone.issues_visible_to_user(current_user).count - if show_new_issue_link?(project) - = link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do + = link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "float-right", title: "New Issue" do New issue .value.hide-collapsed.bold %span.milestone-stat @@ -100,13 +100,13 @@ = icon('spinner spin') .block.merge-requests - .sidebar-collapsed-icon.has-tooltip{ title: milestone_merge_requests_tooltip_text(milestone), data: { container: 'body', html: 1, placement: 'left' } } + .sidebar-collapsed-icon.has-tooltip{ title: milestone_merge_requests_tooltip_text(milestone), data: { container: 'body', html: 'true', placement: 'left', boundary: 'viewport' } } %strong = custom_icon('mr_bold') %span= milestone.merge_requests.count .title.hide-collapsed Merge requests - %span.badge= milestone.merge_requests.count + %span.badge.badge-pill= milestone.merge_requests.count .value.hide-collapsed.bold - if !project || can?(current_user, :read_merge_request, project) %span.milestone-stat @@ -136,10 +136,10 @@ - if milestone_ref.present? .block.reference .sidebar-collapsed-icon.dont-change-state - = clipboard_button(text: milestone_ref, title: "Copy reference to clipboard", placement: "left") + = clipboard_button(text: milestone_ref, title: "Copy reference to clipboard", placement: "left", boundary: 'viewport') .cross-project-reference.hide-collapsed %span Reference: %cite{ title: milestone_ref } = milestone_ref - = clipboard_button(text: milestone_ref, title: "Copy reference to clipboard", placement: "left") + = clipboard_button(text: milestone_ref, title: "Copy reference to clipboard", placement: "left", boundary: 'viewport') diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml index b95a4ea674d..e6a65161ed6 100644 --- a/app/views/shared/milestones/_tabs.html.haml +++ b/app/views/shared/milestones/_tabs.html.haml @@ -3,29 +3,29 @@ .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller .fade-left= icon('angle-left') .fade-right= icon('angle-right') - %ul.nav-links.scrolling-tabs.js-milestone-tabs + %ul.nav-links.scrolling-tabs.js-milestone-tabs.nav.nav-tabs - if issues_accessible %li.active = link_to '#tab-issues', 'data-toggle' => 'tab', 'data-show' => '.tab-issues-buttons' do Issues - %span.badge= milestone.issues_visible_to_user(current_user).size + %span.badge.badge-pill= milestone.issues_visible_to_user(current_user).size %li = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do Merge Requests - %span.badge= milestone.merge_requests.size + %span.badge.badge-pill= milestone.merge_requests.size - else %li.active = link_to '#tab-merge-requests', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do Merge Requests - %span.badge= milestone.merge_requests.size + %span.badge.badge-pill= milestone.merge_requests.size %li = link_to '#tab-participants', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do Participants - %span.badge= milestone.participants.count + %span.badge.badge-pill= milestone.participants.count %li = link_to '#tab-labels', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do Labels - %span.badge= milestone.labels.count + %span.badge.badge-pill= milestone.labels.count - issues = milestone.sorted_issues(current_user) - show_project_name = local_assigns.fetch(:show_project_name, false) diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 797ff034bb2..ee0e35cedc6 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -5,7 +5,7 @@ - is_dynamic_milestone = milestone.legacy_group_milestone? || milestone.dashboard_milestone? .detail-page-header - %a.btn.btn-default.btn-grouped.pull-right.visible-xs-block.js-sidebar-toggle{ href: "#" } + %a.btn.btn-default.btn-grouped.float-right.d-block.d-sm-none.js-sidebar-toggle{ href: "#" } = icon('angle-double-left') .status-box{ class: "status-box-#{milestone.closed? ? 'closed' : 'open'}" } @@ -22,7 +22,7 @@ · = milestone_date_range(milestone) - if group - .pull-right + .float-right - if can?(current_user, :admin_milestones, group) - if milestone.group_milestone? = link_to edit_group_milestone_path(group, milestone), class: "btn btn btn-grouped" do diff --git a/app/views/shared/notes/_comment_button.html.haml b/app/views/shared/notes/_comment_button.html.haml index 4b9af78bc1a..ed336df4e9d 100644 --- a/app/views/shared/notes/_comment_button.html.haml +++ b/app/views/shared/notes/_comment_button.html.haml @@ -1,6 +1,6 @@ - noteable_name = @note.noteable.human_class_name -.pull-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown +.float-left.btn-group.append-right-10.droplab-dropdown.comment-type-dropdown.js-comment-type-dropdown %input.btn.btn-nr.btn-create.comment-btn.js-comment-button.js-comment-submit-button{ type: 'submit', value: 'Comment' } - if @note.can_be_discussion_note? diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml index bc1ac3d8ac2..00eae553279 100644 --- a/app/views/shared/notes/_hints.html.haml +++ b/app/views/shared/notes/_hints.html.haml @@ -32,4 +32,4 @@ = icon('file-image-o', class: 'toolbar-button-icon') Attach a file - %button.btn.btn-default.btn-xs.hide.button-cancel-uploading-files{ type: 'button' } Cancel + %button.btn.btn-default.btn-sm.hide.button-cancel-uploading-files{ type: 'button' } Cancel diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index d4e67b5e7e3..ca6e3602f05 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -25,7 +25,7 @@ - elsif note_counter == 0 - counter = badge_counter if local_assigns[:badge_counter] - badge_class = "hidden" if @fresh_discussion || counter.nil? - %span.badge{ class: badge_class } + %span.badge.badge-pill{ class: badge_class } = counter .timeline-content .note-header diff --git a/app/views/shared/notes/_notes_with_form.html.haml b/app/views/shared/notes/_notes_with_form.html.haml index 1db7c4e67cf..b98d5339d2d 100644 --- a/app/views/shared/notes/_notes_with_form.html.haml +++ b/app/views/shared/notes/_notes_with_form.html.haml @@ -13,7 +13,7 @@ .timeline-entry-inner .flash-container.timeline-content - .timeline-icon.hidden-xs.hidden-sm + .timeline-icon.d-none.d-sm-none.d-md-block %a.author_link{ href: user_path(current_user) } = image_tag avatar_icon_for_user(current_user), alt: current_user.to_reference, class: 'avatar s40' .timeline-content.timeline-content-form diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml index 9186c2ba9c9..d830225d169 100644 --- a/app/views/shared/notifications/_custom_notifications.html.haml +++ b/app/views/shared/notifications/_custom_notifications.html.haml @@ -22,7 +22,7 @@ - NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index| - field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]" .form-group - .checkbox{ class: ("prepend-top-0" if index == 0) } + .form-check{ class: ("prepend-top-0" if index == 0) } %label{ for: field_id } = check_box("notification_setting", event, id: field_id, class: "js-custom-notification-event", checked: notification_setting.public_send(event)) %strong diff --git a/app/views/shared/plugins/_index.html.haml b/app/views/shared/plugins/_index.html.haml index fc643c3ecc2..7bcc54e7459 100644 --- a/app/views/shared/plugins/_index.html.haml +++ b/app/views/shared/plugins/_index.html.haml @@ -10,8 +10,8 @@ .col-lg-8.append-bottom-default - if plugins.any? - .panel.panel-default - .panel-heading + .card + .card-header Plugins (#{plugins.count}) %ul.content-list - plugins.each do |file| @@ -19,5 +19,5 @@ .monospace = File.basename(file) - else - %p.light-well.text-center + %p.card.bg-light.text-center No plugins found. diff --git a/app/views/shared/projects/_dropdown.html.haml b/app/views/shared/projects/_dropdown.html.haml index 3d917346f6b..98b258d9275 100644 --- a/app/views/shared/projects/_dropdown.html.haml +++ b/app/views/shared/projects/_dropdown.html.haml @@ -1,8 +1,8 @@ - @sort ||= sort_value_latest_activity .dropdown.js-project-filter-dropdown-wrap - toggle_text = projects_sort_options_hash[@sort] - = dropdown_toggle(toggle_text, { toggle: 'dropdown' }, { id: 'sort-projects-dropdown' }) - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable + = dropdown_toggle(toggle_text, { toggle: 'dropdown', display: 'static' }, { id: 'sort-projects-dropdown' }) + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable %li.dropdown-header Sort by - projects_sort_options_hash.each do |value, title| diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 0687f6d961d..88f0675f795 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -46,7 +46,7 @@ .controls .prepend-top-0 - if project.archived - %span.prepend-left-10.label.label-warning archived + %span.prepend-left-10.badge.badge-warning archived - if can?(current_user, :read_cross_project) && project.pipeline_status.has_status? %span.prepend-left-10 = render_project_pipeline_status(project.pipeline_status) diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml index 302a543cf12..660123d8b07 100644 --- a/app/views/shared/runners/_form.html.haml +++ b/app/views/shared/runners/_form.html.haml @@ -1,56 +1,56 @@ -= form_for runner, url: runner_form_url, html: { class: 'form-horizontal' } do |f| += form_for runner, url: runner_form_url do |f| = form_errors(runner) - .form-group - = label :active, "Active", class: 'control-label' + .form-group.row + = label :active, "Active", class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.check_box :active %span.light Paused Runners don't accept new jobs - .form-group - = label :protected, "Protected", class: 'control-label' + .form-group.row + = label :protected, "Protected", class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.check_box :access_level, {}, 'ref_protected', 'not_protected' %span.light This runner will only run on pipelines triggered on protected branches - .form-group - = label :run_untagged, 'Run untagged jobs', class: 'control-label' + .form-group.row + = label :run_untagged, 'Run untagged jobs', class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.check_box :run_untagged %span.light Indicates whether this runner can pick jobs without tags - unless runner.group_type? - .form-group - = label :locked, _('Lock to current projects'), class: 'control-label' + .form-group.row + = label :locked, _('Lock to current projects'), class: 'col-form-label col-sm-2' .col-sm-10 - .checkbox + .form-check = f.check_box :locked %span.light= _('When a runner is locked, it cannot be assigned to other projects') - .form-group - = label_tag :token, class: 'control-label' do + .form-group.row + = label_tag :token, class: 'col-form-label col-sm-2' do Token .col-sm-10 = f.text_field :token, class: 'form-control', readonly: true - .form-group - = label_tag :ip_address, class: 'control-label' do + .form-group.row + = label_tag :ip_address, class: 'col-form-label col-sm-2' do IP Address .col-sm-10 = f.text_field :ip_address, class: 'form-control', readonly: true - .form-group - = label_tag :description, class: 'control-label' do + .form-group.row + = label_tag :description, class: 'col-form-label col-sm-2' do Description .col-sm-10 = f.text_field :description, class: 'form-control' - .form-group - = label_tag :maximum_timeout_human_readable, class: 'control-label' do + .form-group.row + = label_tag :maximum_timeout_human_readable, class: 'col-form-label col-sm-2' do Maximum job timeout .col-sm-10 = f.text_field :maximum_timeout_human_readable, class: 'form-control' - .help-block This timeout will take precedence when lower than Project-defined timeout - .form-group - = label_tag :tag_list, class: 'control-label' do + .form-text.text-muted This timeout will take precedence when lower than Project-defined timeout + .form-group.row + = label_tag :tag_list, class: 'col-form-label col-sm-2' do Tags .col-sm-10 = f.text_field :tag_list, value: runner.tag_list.sort.join(', '), class: 'form-control' - .help-block You can setup jobs to only use Runners with specific tags. Separate tags with commas. + .form-text.text-muted You can setup jobs to only use Runners with specific tags. Separate tags with commas. .form-actions = f.submit 'Save changes', class: 'btn btn-save' diff --git a/app/views/shared/runners/_runner_description.html.haml b/app/views/shared/runners/_runner_description.html.haml index 1d59c2f7078..da5c032add5 100644 --- a/app/views/shared/runners/_runner_description.html.haml +++ b/app/views/shared/runners/_runner_description.html.haml @@ -9,8 +9,8 @@ %div %ul %li - %span.label.label-success active + %span.badge.badge-success active = _('- Runner is active and can process any new jobs') %li - %span.label.label-danger paused + %span.badge.badge-danger paused = _('- Runner is paused and will not receive any new jobs') diff --git a/app/views/shared/runners/show.html.haml b/app/views/shared/runners/show.html.haml index 93c6ba86640..e50b7fa68dd 100644 --- a/app/views/shared/runners/show.html.haml +++ b/app/views/shared/runners/show.html.haml @@ -2,7 +2,7 @@ %h3.page-title Runner ##{@runner.id} - .pull-right + .float-right - if @runner.shared? %span.runner-state.runner-state-shared Shared @@ -36,7 +36,7 @@ %td Tags %td - @runner.tag_list.sort.each do |tag| - %span.label.label-primary + %span.badge.badge-primary = tag %tr %td Name diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml index 11f0fa7c49f..2132fcbccc5 100644 --- a/app/views/shared/snippets/_blob.html.haml +++ b/app/views/shared/snippets/_blob.html.haml @@ -2,7 +2,7 @@ .js-file-title.file-title-flex-parent = render 'projects/blob/header_content', blob: blob - .file-actions.hidden-xs + .file-actions.d-none.d-sm-block = render 'projects/blob/viewer_switcher', blob: blob .btn-group{ role: "group" }< diff --git a/app/views/shared/snippets/_embed.html.haml b/app/views/shared/snippets/_embed.html.haml index 2d93e51a2d9..36f56fbad1a 100644 --- a/app/views/shared/snippets/_embed.html.haml +++ b/app/views/shared/snippets/_embed.html.haml @@ -15,7 +15,7 @@ %span.logo-text GitLab - .file-actions.hidden-xs + .file-actions.d-none.d-sm-block .btn-group{ role: "group" }< = embedded_snippet_raw_button diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index c75c882a693..858adc8be37 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -2,11 +2,11 @@ = page_specific_javascript_tag('lib/ace.js') .snippet-form-holder - = form_for @snippet, url: url, html: { class: "form-horizontal snippet-form js-requires-input js-quick-submit common-note-form" } do |f| + = form_for @snippet, url: url, html: { class: "snippet-form js-requires-input js-quick-submit common-note-form" } do |f| = form_errors(@snippet) - .form-group - = f.label :title, class: 'control-label' + .form-group.row + = f.label :title, class: 'col-form-label col-sm-2' .col-sm-10 = f.text_field :title, class: 'form-control', required: true, autofocus: true @@ -15,8 +15,8 @@ = render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet .file-editor - .form-group - = f.label :file_name, "File", class: 'control-label' + .form-group.row + = f.label :file_name, "File", class: 'col-form-label col-sm-2' .col-sm-10 .file-holder.snippet .js-file-title.file-title diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 9f55c10d19b..ddf54866b99 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -7,7 +7,7 @@ %span.creator Authored = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago') - by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")} + by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "d-none d-sm-inline")} .detail-page-header-actions - if @snippet.project_id? diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index 3acec88c2e3..e036b21b23f 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -1,13 +1,13 @@ - link_project = local_assigns.fetch(:link_project, false) %li.snippet-row - = image_tag avatar_icon_for_user(snippet.author), class: "avatar s40 hidden-xs", alt: '' + = image_tag avatar_icon_for_user(snippet.author), class: "avatar s40 d-none d-sm-block", alt: '' .title = link_to reliable_snippet_path(snippet) do = snippet.title - if snippet.file_name - %span.snippet-filename.monospace.hidden-xs + %span.snippet-filename.monospace.d-none.d-sm-inline-block = snippet.file_name %ul.controls @@ -28,10 +28,10 @@ = link_to user_snippets_path(snippet.author) do = snippet.author_name - if link_project && snippet.project_id? - %span.hidden-xs + %span.d-none.d-sm-block in = link_to project_path(snippet.project) do = snippet.project.full_name - .pull-right.snippet-updated-at + .float-right.snippet-updated-at %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')} diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index d36ca032558..0d1c007dd78 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -6,76 +6,76 @@ .form-group = form.label :token, 'Secret Token', class: 'label-light' = form.text_field :token, class: 'form-control', placeholder: '' - %p.help-block + %p.form-text.text-muted Use this token to validate received payloads. It will be sent with the request in the X-Gitlab-Token HTTP header. .form-group = form.label :url, 'Trigger', class: 'label-light' %ul.list-unstyled %li - = form.check_box :push_events, class: 'pull-left' + = form.check_box :push_events, class: 'float-left' .prepend-left-20 = form.label :push_events, class: 'list-label' do %strong Push events %p.light This URL will be triggered by a push to the repository %li - = form.check_box :tag_push_events, class: 'pull-left' + = form.check_box :tag_push_events, class: 'float-left' .prepend-left-20 = form.label :tag_push_events, class: 'list-label' do %strong Tag push events %p.light This URL will be triggered when a new tag is pushed to the repository %li - = form.check_box :note_events, class: 'pull-left' + = form.check_box :note_events, class: 'float-left' .prepend-left-20 = form.label :note_events, class: 'list-label' do %strong Comments %p.light This URL will be triggered when someone adds a comment %li - = form.check_box :confidential_note_events, class: 'pull-left' + = form.check_box :confidential_note_events, class: 'float-left' .prepend-left-20 = form.label :confidential_note_events, class: 'list-label' do %strong Confidential Comments %p.light This URL will be triggered when someone adds a comment on a confidential issue %li - = form.check_box :issues_events, class: 'pull-left' + = form.check_box :issues_events, class: 'float-left' .prepend-left-20 = form.label :issues_events, class: 'list-label' do %strong Issues events %p.light This URL will be triggered when an issue is created/updated/merged %li - = form.check_box :confidential_issues_events, class: 'pull-left' + = form.check_box :confidential_issues_events, class: 'float-left' .prepend-left-20 = form.label :confidential_issues_events, class: 'list-label' do %strong Confidential Issues events %p.light This URL will be triggered when a confidential issue is created/updated/merged %li - = form.check_box :merge_requests_events, class: 'pull-left' + = form.check_box :merge_requests_events, class: 'float-left' .prepend-left-20 = form.label :merge_requests_events, class: 'list-label' do %strong Merge request events %p.light This URL will be triggered when a merge request is created/updated/merged %li - = form.check_box :job_events, class: 'pull-left' + = form.check_box :job_events, class: 'float-left' .prepend-left-20 = form.label :job_events, class: 'list-label' do %strong Job events %p.light This URL will be triggered when the job status changes %li - = form.check_box :pipeline_events, class: 'pull-left' + = form.check_box :pipeline_events, class: 'float-left' .prepend-left-20 = form.label :pipeline_events, class: 'list-label' do %strong Pipeline events %p.light This URL will be triggered when the pipeline status changes %li - = form.check_box :wiki_page_events, class: 'pull-left' + = form.check_box :wiki_page_events, class: 'float-left' .prepend-left-20 = form.label :wiki_page_events, class: 'list-label' do %strong Wiki Page events @@ -83,7 +83,7 @@ This URL will be triggered when a wiki page is created/updated .form-group = form.label :enable_ssl_verification, 'SSL verification', class: 'label-light checkbox' - .checkbox + .form-check = form.label :enable_ssl_verification do = form.check_box :enable_ssl_verification %strong Enable SSL verification diff --git a/app/views/shared/web_hooks/_test_button.html.haml b/app/views/shared/web_hooks/_test_button.html.haml index cf1d5e061c6..5ece8b1d4c7 100644 --- a/app/views/shared/web_hooks/_test_button.html.haml +++ b/app/views/shared/web_hooks/_test_button.html.haml @@ -6,7 +6,7 @@ %button.btn{ 'data-toggle' => 'dropdown', class: button_class } Test = icon('caret-down') - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } - triggers.each_value do |event| %li = link_to_test_hook(hook, event) diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml index 1a6e2542dc1..7255d352775 100644 --- a/app/views/sherlock/file_samples/show.html.haml +++ b/app/views/sherlock/file_samples/show.html.haml @@ -4,7 +4,7 @@ - header_title t('sherlock.title'), sherlock_transactions_path .row-content-block - .pull-right + .float-right = link_to(sherlock_transaction_path(@transaction), class: 'btn') do %i.fa.fa-arrow-left = t('sherlock.transaction') diff --git a/app/views/sherlock/queries/_backtrace.html.haml b/app/views/sherlock/queries/_backtrace.html.haml index 30e956e5f40..4f5146cefb9 100644 --- a/app/views/sherlock/queries/_backtrace.html.haml +++ b/app/views/sherlock/queries/_backtrace.html.haml @@ -1,6 +1,6 @@ .prepend-top-default - .panel.panel-default - .panel-heading + .card + .card-header %strong = t('sherlock.application_backtrace') %ul.well-list @@ -15,8 +15,8 @@ = t('sherlock.line') = location.line - .panel.panel-default - .panel-heading + .card + .card-header %strong = t('sherlock.full_backtrace') %ul.well-list diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml index 5a447f791dc..34c0cc4da39 100644 --- a/app/views/sherlock/queries/_general.html.haml +++ b/app/views/sherlock/queries/_general.html.haml @@ -1,6 +1,6 @@ .prepend-top-default - .panel.panel-default - .panel-heading + .card + .card-header %strong = t('sherlock.general') %ul.well-list @@ -23,10 +23,10 @@ = t('sherlock.line') = frame.line - .panel.panel-default - .panel-heading - .pull-right - %button.js-clipboard-trigger.btn.btn-xs{ title: t('sherlock.copy_to_clipboard'), type: :button } + .card + .card-header + .float-right + %button.js-clipboard-trigger.btn.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button } %i.fa.fa-clipboard %pre.hidden = @query.formatted_query @@ -38,10 +38,10 @@ :preserve #{highlight("#{@query.id}.sql", @query.formatted_query)} - .panel.panel-default - .panel-heading - .pull-right - %button.js-clipboard-trigger.btn.btn-xs{ title: t('sherlock.copy_to_clipboard'), type: :button } + .card + .card-header + .float-right + %button.js-clipboard-trigger.btn.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button } %i.fa.fa-clipboard %pre.hidden = @query.explain diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml index c45da6ee9a4..413130a2907 100644 --- a/app/views/sherlock/queries/show.html.haml +++ b/app/views/sherlock/queries/show.html.haml @@ -1,7 +1,7 @@ - page_title t('sherlock.title'), t('sherlock.transaction'), t('sherlock.query') - header_title t('sherlock.title'), sherlock_transactions_path -%ul.nav-links +%ul.nav-links.nav.nav-tabs %li.active %a{ href: "#tab-general", data: { toggle: "tab" } } = t('sherlock.general') @@ -10,7 +10,7 @@ = t('sherlock.backtrace') .row-content-block - .pull-right + .float-right = link_to(sherlock_transaction_path(@transaction), class: 'btn') do %i.fa.fa-arrow-left = t('sherlock.transaction') diff --git a/app/views/sherlock/transactions/_file_samples.html.haml b/app/views/sherlock/transactions/_file_samples.html.haml index 4349c9b7ace..5b3448605f2 100644 --- a/app/views/sherlock/transactions/_file_samples.html.haml +++ b/app/views/sherlock/transactions/_file_samples.html.haml @@ -21,4 +21,4 @@ %td = link_to(t('sherlock.view'), sherlock_transaction_file_sample_path(@transaction, sample), - class: 'btn btn-xs') + class: 'btn btn-sm') diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml index a37fb5d449a..7ec8dde8421 100644 --- a/app/views/sherlock/transactions/_general.html.haml +++ b/app/views/sherlock/transactions/_general.html.haml @@ -1,6 +1,6 @@ .prepend-top-default - .panel.panel-default - .panel-heading + .card + .card-header %strong = t('sherlock.general') %ul.well-list diff --git a/app/views/sherlock/transactions/_queries.html.haml b/app/views/sherlock/transactions/_queries.html.haml index b8d93e9ff45..c1ec4b91bb6 100644 --- a/app/views/sherlock/transactions/_queries.html.haml +++ b/app/views/sherlock/transactions/_queries.html.haml @@ -21,4 +21,4 @@ %td = link_to(t('sherlock.view'), sherlock_transaction_query_path(@transaction, query), - class: 'btn btn-xs') + class: 'btn btn-sm') diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml index 6ed7e9e21a6..4d9df01ae31 100644 --- a/app/views/sherlock/transactions/index.html.haml +++ b/app/views/sherlock/transactions/index.html.haml @@ -2,7 +2,7 @@ - header_title t('sherlock.title'), sherlock_transactions_path .row-content-block - .pull-right + .float-right = link_to(destroy_all_sherlock_transactions_path, class: 'btn btn-danger', method: :delete) do @@ -37,5 +37,5 @@ %td = time_ago_with_tooltip trans.finished_at %td - = link_to(sherlock_transaction_path(trans), class: 'btn btn-xs') do + = link_to(sherlock_transaction_path(trans), class: 'btn btn-sm') do = t('sherlock.view') diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml index eab91e8fbe4..565b337d446 100644 --- a/app/views/sherlock/transactions/show.html.haml +++ b/app/views/sherlock/transactions/show.html.haml @@ -1,23 +1,23 @@ - page_title t('sherlock.title'), t('sherlock.transaction') - header_title t('sherlock.title'), sherlock_transactions_path -%ul.nav-links +%ul.nav-links.nav.nav-tabs %li.active %a{ href: "#tab-general", data: { toggle: "tab" } } = t('sherlock.general') %li %a{ href: "#tab-queries", data: { toggle: "tab" } } = t('sherlock.queries') - %span.badge + %span.badge.badge-pill #{@transaction.queries.length} %li %a{ href: "#tab-file-samples", data: { toggle: "tab" } } = t('sherlock.file_samples') - %span.badge + %span.badge.badge-pill #{@transaction.file_samples.length} .row-content-block - .pull-right + .float-right = link_to(sherlock_transactions_path, class: 'btn') do %i.fa.fa-arrow-left = t('sherlock.all_transactions') diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index a7f118d3f7d..ae69d0d07c7 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,6 +1,6 @@ - return unless current_user -.hidden-xs +.d-none.d-sm-block - if can?(current_user, :update_personal_snippet, @snippet) = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do Edit @@ -11,7 +11,7 @@ New snippet - if @snippet.submittable_as_spam_by?(current_user) = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' -.visible-xs-block.dropdown +.d-block.d-sm-none.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } Options = icon('caret-down') diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml index 65aa4fbc757..dc4b0fd9ba0 100644 --- a/app/views/snippets/_snippets_scope_menu.html.haml +++ b/app/views/snippets/_snippets_scope_menu.html.haml @@ -1,11 +1,11 @@ - subject = local_assigns.fetch(:subject, current_user) - include_private = local_assigns.fetch(:include_private, false) -.nav-links.snippet-scope-menu.mobile-separator +.nav-links.snippet-scope-menu.mobile-separator.nav.nav-tabs %li{ class: active_when(params[:scope].nil?) } = link_to subject_snippets_path(subject) do All - %span.badge + %span.badge.badge-pill - if include_private = subject.snippets.count - else @@ -15,17 +15,17 @@ %li{ class: active_when(params[:scope] == "are_private") } = link_to subject_snippets_path(subject, scope: 'are_private') do Private - %span.badge + %span.badge.badge-pill = subject.snippets.are_private.count %li{ class: active_when(params[:scope] == "are_internal") } = link_to subject_snippets_path(subject, scope: 'are_internal') do Internal - %span.badge + %span.badge.badge-pill = subject.snippets.are_internal.count %li{ class: active_when(params[:scope] == "are_public") } = link_to subject_snippets_path(subject, scope: 'are_public') do Public - %span.badge + %span.badge.badge-pill = subject.snippets.are_public.count diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 7e4918a6085..9b4a7dbe68d 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -1,12 +1,12 @@ - page_title "By #{@user.name}", "Snippets" %ol.breadcrumb - %li + %li.breadcrumb-item = link_to snippets_path do Snippets - %li + %li.breadcrumb-item = @user.name - .pull-right.hidden-xs + .float-right.d-none.d-sm-block = link_to user_path(@user) do #{@user.name} profile page diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index fb909237b9a..b2ec7166832 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -81,7 +81,7 @@ .scrolling-tabs-container .fade-left= icon('angle-left') .fade-right= icon('angle-right') - %ul.nav-links.user-profile-nav.scrolling-tabs + %ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs - if profile_tab?(:activity) %li.js-activity-tab = link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do @@ -107,7 +107,7 @@ .tab-content - if profile_tab?(:activity) #activity.tab-pane - .row-content-block.calender-block.white.second-block.hidden-xs + .row-content-block.calender-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 diff --git a/changelogs/unreleased/22846-notifications-broken-during-email-address-change-before-email-confirmed.yml b/changelogs/unreleased/22846-notifications-broken-during-email-address-change-before-email-confirmed.yml new file mode 100644 index 00000000000..2b4727c5f03 --- /dev/null +++ b/changelogs/unreleased/22846-notifications-broken-during-email-address-change-before-email-confirmed.yml @@ -0,0 +1,6 @@ +--- +title: Fix an issue where the notification email address would be set to an unconfirmed + email address +merge_request: 18474 +author: +type: fixed diff --git a/changelogs/unreleased/39710-search-placeholder-cut-off.yml b/changelogs/unreleased/39710-search-placeholder-cut-off.yml new file mode 100644 index 00000000000..59290768c6a --- /dev/null +++ b/changelogs/unreleased/39710-search-placeholder-cut-off.yml @@ -0,0 +1,5 @@ +--- +title: 'Fixes: Runners search input placeholder is cut off' +merge_request: 19015 +author: Jacopo Beschi @jacopo-beschi +type: fixed diff --git a/changelogs/unreleased/44579-ide-add-pipeline-to-status-bar.yml b/changelogs/unreleased/44579-ide-add-pipeline-to-status-bar.yml new file mode 100644 index 00000000000..21e7c795815 --- /dev/null +++ b/changelogs/unreleased/44579-ide-add-pipeline-to-status-bar.yml @@ -0,0 +1,5 @@ +--- +title: Add pipeline status to the status bar of the Web IDE +merge_request: +author: +type: added diff --git a/changelogs/unreleased/45404-remove-gemnasium-badge-from-project-s-readme-md.yml b/changelogs/unreleased/45404-remove-gemnasium-badge-from-project-s-readme-md.yml new file mode 100644 index 00000000000..af2cb65445c --- /dev/null +++ b/changelogs/unreleased/45404-remove-gemnasium-badge-from-project-s-readme-md.yml @@ -0,0 +1,5 @@ +--- +title: Remove Gemnasium badge from project README.md +merge_request: 19136 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/45827-expose_readme_url_in_project_api.yml b/changelogs/unreleased/45827-expose_readme_url_in_project_api.yml new file mode 100644 index 00000000000..7c495cf4dc0 --- /dev/null +++ b/changelogs/unreleased/45827-expose_readme_url_in_project_api.yml @@ -0,0 +1,5 @@ +--- +title: Expose readme url in Project API +merge_request: 18960 +author: Imre Farkas +type: changed diff --git a/changelogs/unreleased/45850-close-mr-checkout-modal-on-escape.yml b/changelogs/unreleased/45850-close-mr-checkout-modal-on-escape.yml new file mode 100644 index 00000000000..c3955c9d8b3 --- /dev/null +++ b/changelogs/unreleased/45850-close-mr-checkout-modal-on-escape.yml @@ -0,0 +1,5 @@ +--- +title: Closes MR check out branch modal with escape +merge_request: Jacopo Beschi @jacopo-beschi +author: 19050 +type: added diff --git a/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml b/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml deleted file mode 100644 index 2f885c5c927..00000000000 --- a/changelogs/unreleased/46177-fix-present-on-generic-commit-status.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow CommitStatus class to use presentable methods -merge_request: 18979 -author: -type: fixed diff --git a/changelogs/unreleased/46354-deprecate_gemnasium_service.yml b/changelogs/unreleased/46354-deprecate_gemnasium_service.yml new file mode 100644 index 00000000000..c5ead45d883 --- /dev/null +++ b/changelogs/unreleased/46354-deprecate_gemnasium_service.yml @@ -0,0 +1,5 @@ +--- +title: Deprecate Gemnasium project service +merge_request: 18954 +author: +type: deprecated diff --git a/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml b/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml deleted file mode 100644 index e610e53f71c..00000000000 --- a/changelogs/unreleased/46454-wrong-value-in-ci-deploy-user.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes deploy token variables on Ci::Build -merge_request: 19047 -author: -type: fixed diff --git a/changelogs/unreleased/46600-fix-gitlab-revision-when-not-in-git-repo.yml b/changelogs/unreleased/46600-fix-gitlab-revision-when-not-in-git-repo.yml new file mode 100644 index 00000000000..1d0b11cfd2a --- /dev/null +++ b/changelogs/unreleased/46600-fix-gitlab-revision-when-not-in-git-repo.yml @@ -0,0 +1,6 @@ +--- +title: Replace Gitlab::REVISION with Gitlab.revision and handle installations without + a .git directory +merge_request: 19125 +author: +type: fixed diff --git a/changelogs/unreleased/ab-43706-composite-primary-keys.yml b/changelogs/unreleased/ab-43706-composite-primary-keys.yml new file mode 100644 index 00000000000..b17050a64c8 --- /dev/null +++ b/changelogs/unreleased/ab-43706-composite-primary-keys.yml @@ -0,0 +1,5 @@ +--- +title: Add NOT NULL constraints to project_authorizations. +merge_request: 18980 +author: +type: other diff --git a/changelogs/unreleased/ab-46530-mediumtext-for-gpg-keys.yml b/changelogs/unreleased/ab-46530-mediumtext-for-gpg-keys.yml new file mode 100644 index 00000000000..88ef62ebc0e --- /dev/null +++ b/changelogs/unreleased/ab-46530-mediumtext-for-gpg-keys.yml @@ -0,0 +1,5 @@ +--- +title: Increase text limit for GPG keys (mysql only). +merge_request: 19069 +author: +type: other diff --git a/changelogs/unreleased/commit-branch-tag-icon-update.yml b/changelogs/unreleased/commit-branch-tag-icon-update.yml new file mode 100644 index 00000000000..136b7cbf0f4 --- /dev/null +++ b/changelogs/unreleased/commit-branch-tag-icon-update.yml @@ -0,0 +1,5 @@ +--- +title: Updated icons for branch and tag names in commit details +merge_request: 18953 +author: Constance Okoghenun +type: changed diff --git a/changelogs/unreleased/fix-devops-remove-beta.yml b/changelogs/unreleased/fix-devops-remove-beta.yml new file mode 100644 index 00000000000..326003eb956 --- /dev/null +++ b/changelogs/unreleased/fix-devops-remove-beta.yml @@ -0,0 +1,5 @@ +--- +title: Removed "(Beta)" from "Auto DevOps" messages +merge_request: 18759 +author: +type: changed diff --git a/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml b/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml deleted file mode 100644 index 1f64ab9f30f..00000000000 --- a/changelogs/unreleased/fix-kube_client-proxy_url-exception.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix corrupted environment pages with unathorized proxy url -merge_request: 18989 -author: -type: fixed diff --git a/changelogs/unreleased/fix-unverified-hover-state.yml b/changelogs/unreleased/fix-unverified-hover-state.yml new file mode 100644 index 00000000000..003138f9821 --- /dev/null +++ b/changelogs/unreleased/fix-unverified-hover-state.yml @@ -0,0 +1,5 @@ +--- +title: Unverified hover state color changed to black +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/fj-46411-fix-badge-api-endpoint-route-with-relative-url.yml b/changelogs/unreleased/fj-46411-fix-badge-api-endpoint-route-with-relative-url.yml new file mode 100644 index 00000000000..bd4e5a43352 --- /dev/null +++ b/changelogs/unreleased/fj-46411-fix-badge-api-endpoint-route-with-relative-url.yml @@ -0,0 +1,5 @@ +--- +title: Fixed badge api endpoint route when relative url is set +merge_request: 19004 +author: +type: fixed diff --git a/changelogs/unreleased/fj-46459-fix-expose-url-when-base-url-set.yml b/changelogs/unreleased/fj-46459-fix-expose-url-when-base-url-set.yml new file mode 100644 index 00000000000..16b0ee06898 --- /dev/null +++ b/changelogs/unreleased/fj-46459-fix-expose-url-when-base-url-set.yml @@ -0,0 +1,5 @@ +--- +title: Fixed bug where generated api urls didn't add the base url if set +merge_request: 19003 +author: +type: fixed diff --git a/changelogs/unreleased/issue-25256.yml b/changelogs/unreleased/issue-25256.yml new file mode 100644 index 00000000000..e981b5f9a8f --- /dev/null +++ b/changelogs/unreleased/issue-25256.yml @@ -0,0 +1,5 @@ +--- +title: Don't trim incoming emails that create new issues +merge_request: +author: Cameron Crockett +type: fixed diff --git a/changelogs/unreleased/issue_38418.yml b/changelogs/unreleased/issue_38418.yml new file mode 100644 index 00000000000..79452b27e4b --- /dev/null +++ b/changelogs/unreleased/issue_38418.yml @@ -0,0 +1,5 @@ +--- +title: Fix issue count on sidebar +merge_request: +author: +type: other diff --git a/changelogs/unreleased/jprovazn-fix-resolvable.yml b/changelogs/unreleased/jprovazn-fix-resolvable.yml new file mode 100644 index 00000000000..e17c409e290 --- /dev/null +++ b/changelogs/unreleased/jprovazn-fix-resolvable.yml @@ -0,0 +1,5 @@ +--- +title: Fix resolvable check if note's commit could not be found. +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/migrate-restore-repo-to-gitaly.yml b/changelogs/unreleased/migrate-restore-repo-to-gitaly.yml new file mode 100644 index 00000000000..59f375de20e --- /dev/null +++ b/changelogs/unreleased/migrate-restore-repo-to-gitaly.yml @@ -0,0 +1,5 @@ +--- +title: Support restoring repositories into gitaly +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/mr-conflict-notification.yml b/changelogs/unreleased/mr-conflict-notification.yml new file mode 100644 index 00000000000..d3d5f1fc373 --- /dev/null +++ b/changelogs/unreleased/mr-conflict-notification.yml @@ -0,0 +1,5 @@ +--- +title: When MR becomes unmergeable, notify and create todo for author and merge user +merge_request: 18042 +author: +type: added diff --git a/changelogs/unreleased/rename-merge-request-widget-author-component.yml b/changelogs/unreleased/rename-merge-request-widget-author-component.yml new file mode 100644 index 00000000000..15e6eafd826 --- /dev/null +++ b/changelogs/unreleased/rename-merge-request-widget-author-component.yml @@ -0,0 +1,5 @@ +--- +title: Rename merge request widget author component +merge_request: 19079 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/sh-fix-backup-specific-rake-task.yml b/changelogs/unreleased/sh-fix-backup-specific-rake-task.yml new file mode 100644 index 00000000000..71b121710ee --- /dev/null +++ b/changelogs/unreleased/sh-fix-backup-specific-rake-task.yml @@ -0,0 +1,5 @@ +--- +title: Fix backup creation and restore for specific Rake tasks +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/sh-fix-blocked-user-account-ldap.yml b/changelogs/unreleased/sh-fix-blocked-user-account-ldap.yml deleted file mode 100644 index f7abe763ea8..00000000000 --- a/changelogs/unreleased/sh-fix-blocked-user-account-ldap.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix system hook not firing for blocked users when LDAP sign-in is used -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/winh-make-it-right-now.yml b/changelogs/unreleased/winh-make-it-right-now.yml new file mode 100644 index 00000000000..7b386c0b332 --- /dev/null +++ b/changelogs/unreleased/winh-make-it-right-now.yml @@ -0,0 +1,5 @@ +--- +title: Use "right now" for short time periods +merge_request: 19095 +author: +type: changed diff --git a/changelogs/unreleased/winh-new-merge-request-encoding.yml b/changelogs/unreleased/winh-new-merge-request-encoding.yml new file mode 100644 index 00000000000..f797657e660 --- /dev/null +++ b/changelogs/unreleased/winh-new-merge-request-encoding.yml @@ -0,0 +1,5 @@ +--- +title: Fix encoding of branch names on compare and new merge request page +merge_request: 19143 +author: +type: fixed diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 5248bd858a0..dd36700964a 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -470,6 +470,3 @@ if Rails.env.test? Settings.gitlab['default_can_create_group'] = true Settings.gitlab['default_can_create_team'] = false end - -# Force a refresh of application settings at startup -ApplicationSetting.expire diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb index eb7959e4da6..146c4b1e024 100644 --- a/config/initializers/7_prometheus_metrics.rb +++ b/config/initializers/7_prometheus_metrics.rb @@ -25,7 +25,7 @@ Sidekiq.configure_server do |config| end end -if Gitlab::Metrics.prometheus_metrics_enabled? +if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled? unless Sidekiq.server? Gitlab::Metrics::Samplers::UnicornSampler.initialize_instance(Settings.monitoring.unicorn_sampler_interval).start end diff --git a/config/initializers/console_message.rb b/config/initializers/console_message.rb index 536ab337d85..2c46a25f365 100644 --- a/config/initializers/console_message.rb +++ b/config/initializers/console_message.rb @@ -3,7 +3,7 @@ if defined?(Rails::Console) # note that this will not print out when using `spring` justify = 15 puts "-------------------------------------------------------------------------------------" - puts " Gitlab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab::REVISION})" + puts " Gitlab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision})" puts " Gitlab Shell:".ljust(justify) + Gitlab::Shell.new.version puts " #{Gitlab::Database.adapter_name}:".ljust(justify) + Gitlab::Database.version puts "-------------------------------------------------------------------------------------" diff --git a/config/initializers/kubeclient.rb b/config/initializers/kubeclient.rb new file mode 100644 index 00000000000..7f115268b37 --- /dev/null +++ b/config/initializers/kubeclient.rb @@ -0,0 +1,16 @@ +class Kubeclient::Client + # We need to monkey patch this method until + # https://github.com/abonas/kubeclient/pull/323 is merged + def proxy_url(kind, name, port, namespace = '') + discover unless @discovered + entity_name_plural = + if %w[services pods nodes].include?(kind.to_s) + kind.to_s + else + @entities[kind.to_s].resource_name + end + + ns_prefix = build_namespace_prefix(namespace) + rest_client["#{ns_prefix}#{entity_name_plural}/#{name}:#{port}/proxy"].url + end +end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index b2da3b3dc19..17d09293205 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -13,7 +13,7 @@ def configure_sentry if sentry_enabled Raven.configure do |config| config.dsn = Gitlab::CurrentSettings.current_application_settings.sentry_dsn - config.release = Gitlab::REVISION + config.release = Gitlab.revision # Sanitize fields based on those sanitized from Rails. config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) diff --git a/config/webpack.config.js b/config/webpack.config.js index 5096f35e808..d6ab32972fb 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -2,20 +2,25 @@ const fs = require('fs'); const path = require('path'); const glob = require('glob'); const webpack = require('webpack'); +const VueLoaderPlugin = require('vue-loader/lib/plugin'); const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; const CopyWebpackPlugin = require('copy-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const ROOT_PATH = path.resolve(__dirname, '..'); +const CACHE_PATH = path.join(ROOT_PATH, 'tmp/cache'); const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1; const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost'; const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808; -const DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false'; +const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false'; const WEBPACK_REPORT = process.env.WEBPACK_REPORT; const NO_COMPRESSION = process.env.NO_COMPRESSION; +const VUE_VERSION = require('vue/package.json').version; +const VUE_LOADER_VERSION = require('vue-loader/package.json').version; + let autoEntriesCount = 0; let watchAutoEntries = []; const defaultEntries = ['./main']; @@ -61,7 +66,7 @@ function generateEntries() { return Object.assign(manualEntries, autoEntries); } -const config = { +module.exports = { mode: IS_PRODUCTION ? 'production' : 'development', context: path.join(ROOT_PATH, 'app/assets/javascripts'), @@ -76,46 +81,43 @@ const config = { globalObject: 'this', // allow HMR and web workers to play nice }, - optimization: { - nodeEnv: false, - runtimeChunk: 'single', - splitChunks: { - maxInitialRequests: 4, - cacheGroups: { - default: false, - common: () => ({ - priority: 20, - name: 'main', - chunks: 'initial', - minChunks: autoEntriesCount * 0.9, - }), - vendors: { - priority: 10, - chunks: 'async', - test: /[\\/](node_modules|vendor[\\/]assets[\\/]javascripts)[\\/]/, - }, - commons: { - chunks: 'all', - minChunks: 2, - reuseExistingChunk: true, - }, - }, + resolve: { + extensions: ['.js'], + alias: { + '~': path.join(ROOT_PATH, 'app/assets/javascripts'), + emojis: path.join(ROOT_PATH, 'fixtures/emojis'), + empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'), + icons: path.join(ROOT_PATH, 'app/views/shared/icons'), + images: path.join(ROOT_PATH, 'app/assets/images'), + vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'), + vue$: 'vue/dist/vue.esm.js', + spec: path.join(ROOT_PATH, 'spec/javascripts'), }, }, module: { + strictExportPresence: true, rules: [ { test: /\.js$/, - exclude: /(node_modules|vendor\/assets)/, + exclude: path => /node_modules|vendor[\\/]assets/.test(path) && !/\.vue\.js/.test(path), loader: 'babel-loader', options: { - cacheDirectory: path.join(ROOT_PATH, 'tmp/cache/babel-loader'), + cacheDirectory: path.join(CACHE_PATH, 'babel-loader'), }, }, { test: /\.vue$/, loader: 'vue-loader', + options: { + cacheDirectory: path.join(CACHE_PATH, 'vue-loader'), + cacheIdentifier: [ + process.env.NODE_ENV || 'development', + webpack.version, + VUE_VERSION, + VUE_LOADER_VERSION, + ].join('|'), + }, }, { test: /\.svg$/, @@ -147,10 +149,9 @@ const config = { }, }, { - test: /katex.min.css$/, - include: /node_modules\/katex\/dist/, + test: /.css$/, use: [ - { loader: 'style-loader' }, + 'vue-style-loader', { loader: 'css-loader', options: { @@ -175,9 +176,34 @@ const config = { ], }, ], - noParse: [/monaco-editor\/\w+\/vs\//], - strictExportPresence: true, + }, + + optimization: { + nodeEnv: false, + runtimeChunk: 'single', + splitChunks: { + maxInitialRequests: 4, + cacheGroups: { + default: false, + common: () => ({ + priority: 20, + name: 'main', + chunks: 'initial', + minChunks: autoEntriesCount * 0.9, + }), + vendors: { + priority: 10, + chunks: 'async', + test: /[\\/](node_modules|vendor[\\/]assets[\\/]javascripts)[\\/]/, + }, + commons: { + chunks: 'all', + minChunks: 2, + reuseExistingChunk: true, + }, + }, + }, }, plugins: [ @@ -197,6 +223,9 @@ const config = { }, }), + // enable vue-loader to use existing loader rules for other module types + new VueLoaderPlugin(), + // prevent pikaday from including moment.js new webpack.IgnorePlugin(/moment/, /pikaday/), @@ -228,87 +257,66 @@ const config = { }, }, ]), - ], - resolve: { - extensions: ['.js'], - alias: { - '~': path.join(ROOT_PATH, 'app/assets/javascripts'), - emojis: path.join(ROOT_PATH, 'fixtures/emojis'), - empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'), - icons: path.join(ROOT_PATH, 'app/views/shared/icons'), - images: path.join(ROOT_PATH, 'app/assets/images'), - vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'), - vue$: 'vue/dist/vue.esm.js', - spec: path.join(ROOT_PATH, 'spec/javascripts'), - }, - }, + // compression can require a lot of compute time and is disabled in CI + IS_PRODUCTION && !NO_COMPRESSION && new CompressionPlugin(), - // sqljs requires fs - node: { - fs: 'empty', - }, -}; + // WatchForChangesPlugin + // TODO: publish this as a separate plugin + IS_DEV_SERVER && { + apply(compiler) { + compiler.hooks.emit.tapAsync('WatchForChangesPlugin', (compilation, callback) => { + const missingDeps = Array.from(compilation.missingDependencies); + const nodeModulesPath = path.join(ROOT_PATH, 'node_modules'); + const hasMissingNodeModules = missingDeps.some( + file => file.indexOf(nodeModulesPath) !== -1 + ); -if (IS_PRODUCTION) { - config.devtool = 'source-map'; + // watch for changes to missing node_modules + if (hasMissingNodeModules) compilation.contextDependencies.add(nodeModulesPath); - // compression can require a lot of compute time and is disabled in CI - if (!NO_COMPRESSION) { - config.plugins.push(new CompressionPlugin()); - } -} + // watch for changes to automatic entrypoints + watchAutoEntries.forEach(watchPath => compilation.contextDependencies.add(watchPath)); + + // report our auto-generated bundle count + console.log( + `${autoEntriesCount} entries from '/pages' automatically added to webpack output.` + ); + + callback(); + }); + }, + }, + + // enable HMR only in webpack-dev-server + DEV_SERVER_LIVERELOAD && new webpack.HotModuleReplacementPlugin(), -if (IS_DEV_SERVER) { - config.devtool = 'cheap-module-eval-source-map'; - config.devServer = { + // optionally generate webpack bundle analysis + WEBPACK_REPORT && + new BundleAnalyzerPlugin({ + analyzerMode: 'static', + generateStatsFile: true, + openAnalyzer: false, + reportFilename: path.join(ROOT_PATH, 'webpack-report/index.html'), + statsFilename: path.join(ROOT_PATH, 'webpack-report/stats.json'), + }), + ].filter(Boolean), + + devServer: { host: DEV_SERVER_HOST, port: DEV_SERVER_PORT, disableHostCheck: true, - headers: { 'Access-Control-Allow-Origin': '*' }, + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': '*', + }, stats: 'errors-only', hot: DEV_SERVER_LIVERELOAD, inline: DEV_SERVER_LIVERELOAD, - }; - config.plugins.push({ - apply(compiler) { - compiler.hooks.emit.tapAsync('WatchForChangesPlugin', (compilation, callback) => { - const missingDeps = Array.from(compilation.missingDependencies); - const nodeModulesPath = path.join(ROOT_PATH, 'node_modules'); - const hasMissingNodeModules = missingDeps.some( - file => file.indexOf(nodeModulesPath) !== -1 - ); - - // watch for changes to missing node_modules - if (hasMissingNodeModules) compilation.contextDependencies.add(nodeModulesPath); - - // watch for changes to automatic entrypoints - watchAutoEntries.forEach(watchPath => compilation.contextDependencies.add(watchPath)); - - // report our auto-generated bundle count - console.log( - `${autoEntriesCount} entries from '/pages' automatically added to webpack output.` - ); - - callback(); - }); - }, - }); - if (DEV_SERVER_LIVERELOAD) { - config.plugins.push(new webpack.HotModuleReplacementPlugin()); - } -} + }, -if (WEBPACK_REPORT) { - config.plugins.push( - new BundleAnalyzerPlugin({ - analyzerMode: 'static', - generateStatsFile: true, - openAnalyzer: false, - reportFilename: path.join(ROOT_PATH, 'webpack-report/index.html'), - statsFilename: path.join(ROOT_PATH, 'webpack-report/stats.json'), - }) - ); -} + devtool: IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map', -module.exports = config; + // sqljs requires fs + node: { fs: 'empty' }, +}; diff --git a/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb b/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb new file mode 100644 index 00000000000..970a53d68d0 --- /dev/null +++ b/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb @@ -0,0 +1,15 @@ +class EnsureMissingColumnsToProjectMirrorData < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status) + add_column :project_mirror_data, :jid, :string unless column_exists?(:project_mirror_data, :jid) + add_column :project_mirror_data, :last_error, :text unless column_exists?(:project_mirror_data, :last_error) + end + + def down + # db/migrate/20180502122856_create_project_mirror_data.rb will remove the table + end +end diff --git a/db/migrate/20180517082340_add_not_null_constraints_to_project_authorizations.rb b/db/migrate/20180517082340_add_not_null_constraints_to_project_authorizations.rb new file mode 100644 index 00000000000..3b7b877232b --- /dev/null +++ b/db/migrate/20180517082340_add_not_null_constraints_to_project_authorizations.rb @@ -0,0 +1,38 @@ +class AddNotNullConstraintsToProjectAuthorizations < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + if Gitlab::Database.postgresql? + # One-pass version for PostgreSQL + execute <<~SQL + ALTER TABLE project_authorizations + ALTER COLUMN user_id SET NOT NULL, + ALTER COLUMN project_id SET NOT NULL, + ALTER COLUMN access_level SET NOT NULL + SQL + else + change_column_null :project_authorizations, :user_id, false + change_column_null :project_authorizations, :project_id, false + change_column_null :project_authorizations, :access_level, false + end + end + + def down + if Gitlab::Database.postgresql? + # One-pass version for PostgreSQL + execute <<~SQL + ALTER TABLE project_authorizations + ALTER COLUMN user_id DROP NOT NULL, + ALTER COLUMN project_id DROP NOT NULL, + ALTER COLUMN access_level DROP NOT NULL + SQL + else + change_column_null :project_authorizations, :user_id, true + change_column_null :project_authorizations, :project_id, true + change_column_null :project_authorizations, :access_level, true + end + end +end diff --git a/db/migrate/20180521171529_increase_mysql_text_limit_for_gpg_keys.rb b/db/migrate/20180521171529_increase_mysql_text_limit_for_gpg_keys.rb new file mode 100644 index 00000000000..df84898003f --- /dev/null +++ b/db/migrate/20180521171529_increase_mysql_text_limit_for_gpg_keys.rb @@ -0,0 +1,2 @@ +# rubocop:disable all +require_relative 'gpg_keys_limits_to_mysql' diff --git a/db/migrate/gpg_keys_limits_to_mysql.rb b/db/migrate/gpg_keys_limits_to_mysql.rb new file mode 100644 index 00000000000..780340d0564 --- /dev/null +++ b/db/migrate/gpg_keys_limits_to_mysql.rb @@ -0,0 +1,15 @@ +class IncreaseMysqlTextLimitForGpgKeys < ActiveRecord::Migration + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + return unless Gitlab::Database.mysql? + + change_column :gpg_keys, :key, :text, limit: 16.megabytes - 1 + end + + def down + # no-op + end +end diff --git a/db/optional_migrations/composite_primary_keys.rb b/db/optional_migrations/composite_primary_keys.rb new file mode 100644 index 00000000000..0fd3fca52dd --- /dev/null +++ b/db/optional_migrations/composite_primary_keys.rb @@ -0,0 +1,63 @@ +# This migration adds a primary key constraint to tables +# that only have a composite unique key. +# +# This is not strictly relevant to Rails (v4 does not +# support composite primary keys). However this becomes +# useful for e.g. PostgreSQL's logical replication (pglogical) +# which requires all tables to have a primary key constraint. +# +# In that sense, the migration is optional and not strictly needed. +class CompositePrimaryKeysMigration < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + Index = Struct.new(:table, :name, :columns) + + TABLES = [ + Index.new(:issue_assignees, 'index_issue_assignees_on_issue_id_and_user_id', %i(issue_id user_id)), + Index.new(:user_interacted_projects, 'index_user_interacted_projects_on_project_id_and_user_id', %i(project_id user_id)), + Index.new(:merge_request_diff_files, 'index_merge_request_diff_files_on_mr_diff_id_and_order', %i(merge_request_diff_id relative_order)), + Index.new(:merge_request_diff_commits, 'index_merge_request_diff_commits_on_mr_diff_id_and_order', %i(merge_request_diff_id relative_order)), + Index.new(:project_authorizations, 'index_project_authorizations_on_user_id_project_id_access_level', %i(user_id project_id access_level)), + Index.new(:push_event_payloads, 'index_push_event_payloads_on_event_id', %i(event_id)), + Index.new(:schema_migrations, 'unique_schema_migrations', %(version)), + ] + + disable_ddl_transaction! + + def up + return unless Gitlab::Database.postgresql? + + disable_statement_timeout + TABLES.each do |index| + add_primary_key(index) + end + end + + def down + return unless Gitlab::Database.postgresql? + + disable_statement_timeout + TABLES.each do |index| + remove_primary_key(index) + end + end + + private + def add_primary_key(index) + execute "ALTER TABLE #{index.table} ADD PRIMARY KEY USING INDEX #{index.name}" + end + + def remove_primary_key(index) + temp_index_name = "#{index.name[0..58]}_old" + rename_index index.table, index.name, temp_index_name if index_exists_by_name?(index.table, index.name) + + # re-create unique key index + add_concurrent_index index.table, index.columns, unique: true, name: index.name + + # This also drops the `temp_index_name` as this is owned by the constraint + execute "ALTER TABLE #{index.table} DROP CONSTRAINT IF EXISTS #{temp_index_name}" + end +end + diff --git a/db/post_migrate/20180514161336_remove_gemnasium_service.rb b/db/post_migrate/20180514161336_remove_gemnasium_service.rb new file mode 100644 index 00000000000..6d7806e8daa --- /dev/null +++ b/db/post_migrate/20180514161336_remove_gemnasium_service.rb @@ -0,0 +1,15 @@ +class RemoveGemnasiumService < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + disable_statement_timeout + + execute("DELETE FROM services WHERE type='GemnasiumService';") + end + + def down + # noop + end +end diff --git a/db/schema.rb b/db/schema.rb index 27bdb7701c1..eab5bf24703 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180512061621) do +ActiveRecord::Schema.define(version: 20180521171529) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1451,9 +1451,9 @@ ActiveRecord::Schema.define(version: 20180512061621) do add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree create_table "project_authorizations", id: false, force: :cascade do |t| - t.integer "user_id" - t.integer "project_id" - t.integer "access_level" + t.integer "user_id", null: false + t.integer "project_id", null: false + t.integer "access_level", null: false end add_index "project_authorizations", ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree diff --git a/doc/api/issues.md b/doc/api/issues.md index d0063e0b8a2..5613cb6d915 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -467,7 +467,7 @@ POST /projects/:id/issues | `description` | string | no | The description of an issue | | `confidential` | boolean | no | Set an issue to be confidential. Default is `false`. | | `assignee_ids` | Array[integer] | no | The ID of the users to assign issue | -| `milestone_id` | integer | no | The ID of a milestone to assign issue | +| `milestone_id` | integer | no | The global ID of a milestone to assign issue | | `labels` | string | no | Comma-separated label names for an issue | | `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | | `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` | @@ -548,7 +548,7 @@ PUT /projects/:id/issues/:issue_iid | `description` | string | no | The description of an issue | | `confidential` | boolean | no | Updates an issue to be confidential | | `assignee_ids` | Array[integer] | no | The ID of the user(s) to assign the issue to. Set to `0` or provide an empty value to unassign all assignees. | -| `milestone_id` | integer | no | The ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.| +| `milestone_id` | integer | no | The global ID of a milestone to assign the issue to. Set to `0` or provide an empty value to unassign a milestone.| | `labels` | string | no | Comma-separated label names for an issue. Set to an empty string to unassign all labels. | | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | | `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` (requires admin or project owner rights) | diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index cbd51c9870c..4e34831422a 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -539,7 +539,7 @@ POST /projects/:id/merge_requests | `description` | string | no | Description of MR | | `target_project_id` | integer | no | The target project (numeric id) | | `labels` | string | no | Labels for MR as a comma-separated list | -| `milestone_id` | integer | no | The ID of a milestone | +| `milestone_id` | integer | no | The global ID of a milestone | | `remove_source_branch` | boolean | no | Flag indicating if a merge request should remove the source branch when merging | | `allow_maintainer_to_push` | boolean | no | Whether or not a maintainer of the target project can push to the source branch | @@ -622,7 +622,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid | `target_branch` | string | no | The target branch | | `title` | string | no | Title of MR | | `assignee_id` | integer | no | The ID of the user to assign the merge request to. Set to `0` or provide an empty value to unassign all assignees. | -| `milestone_id` | integer | no | The ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.| +| `milestone_id` | integer | no | The global ID of a milestone to assign the merge request to. Set to `0` or provide an empty value to unassign a milestone.| | `labels` | string | no | Comma-separated label names for a merge request. Set to an empty string to unassign all labels. | | `description` | string | no | Description of MR | | `state_event` | string | no | New state (close/reopen) | diff --git a/doc/api/projects.md b/doc/api/projects.md index fe21dfe23f9..79cf5e1cc10 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -66,6 +66,7 @@ GET /projects "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", "web_url": "http://example.com/diaspora/diaspora-client", + "readme_url": "http://example.com/diaspora/diaspora-client/blob/master/README.md", "tag_list": [ "example", "disapora client" @@ -135,6 +136,7 @@ GET /projects "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", "http_url_to_repo": "http://example.com/brightbox/puppet.git", "web_url": "http://example.com/brightbox/puppet", + "readme_url": "http://example.com/brightbox/puppet/blob/master/README.md", "tag_list": [ "example", "puppet" @@ -252,6 +254,7 @@ GET /users/:user_id/projects "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", "web_url": "http://example.com/diaspora/diaspora-client", + "readme_url": "http://example.com/diaspora/diaspora-client/blob/master/README.md", "tag_list": [ "example", "disapora client" @@ -321,6 +324,7 @@ GET /users/:user_id/projects "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", "http_url_to_repo": "http://example.com/brightbox/puppet.git", "web_url": "http://example.com/brightbox/puppet", + "readme_url": "http://example.com/brightbox/puppet/blob/master/README.md", "tag_list": [ "example", "puppet" @@ -420,6 +424,7 @@ GET /projects/:id "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", + "readme_url": "http://example.com/diaspora/diaspora-project-site/blob/master/README.md", "tag_list": [ "example", "disapora project" @@ -710,6 +715,7 @@ Example responses: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", + "readme_url": "http://example.com/diaspora/diaspora-project-site/blob/master/README.md", "tag_list": [ "example", "disapora project" @@ -788,6 +794,7 @@ Example response: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", + "readme_url": "http://example.com/diaspora/diaspora-project-site/blob/master/README.md", "tag_list": [ "example", "disapora project" @@ -865,6 +872,7 @@ Example response: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", + "readme_url": "http://example.com/diaspora/diaspora-project-site/blob/master/README.md", "tag_list": [ "example", "disapora project" @@ -966,6 +974,7 @@ Example response: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", + "readme_url": "http://example.com/diaspora/diaspora-project-site/blob/master/README.md", "tag_list": [ "example", "disapora project" @@ -1061,6 +1070,7 @@ Example response: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", + "readme_url": "http://example.com/diaspora/diaspora-project-site/blob/master/README.md", "tag_list": [ "example", "disapora project" @@ -1421,4 +1431,3 @@ GET /projects/:id/snapshot | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | | `wiki` | boolean | no | Whether to download the wiki, rather than project, repository | - diff --git a/doc/api/services.md b/doc/api/services.md index ec632125325..f23303ef836 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -405,6 +405,13 @@ GET /projects/:id/services/flowdock Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities. +CAUTION: **Warning:** +Gemnasium service integration has been deprecated in GitLab 11.0. Gemnasium has been +[acquired by GitLab](https://about.gitlab.com/press/releases/2018-01-30-gemnasium-acquisition.html) +in January 2018 and since May 15, 2018, the service provided by Gemnasium is no longer available. +You can [migrate from Gemnasium to GitLab](https://docs.gitlab.com/ee/user/project/import/gemnasium.html) +to keep monitoring your dependencies. + ### Create/Edit Gemnasium service Set Gemnasium service for a project. diff --git a/doc/ci/examples/code_climate.md b/doc/ci/examples/code_climate.md index d1aa783cc9c..cc19e090964 100644 --- a/doc/ci/examples/code_climate.md +++ b/doc/ci/examples/code_climate.md @@ -5,10 +5,10 @@ GitLab CI and Docker. First, you need GitLab Runner with [docker-in-docker executor][dind]. -Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `codequality`: +Once you set up the Runner, add a new job to `.gitlab-ci.yml`, called `code_quality`: ```yaml -codequality: +code_quality: image: docker:stable variables: DOCKER_DRIVER: overlay2 @@ -23,20 +23,27 @@ codequality: --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code artifacts: - paths: [codeclimate.json] + paths: [gl-code-quality-report.json] ``` -The above example will create a `codequality` job in your CI/CD pipeline which +The above example will create a `code_quality` job in your CI/CD pipeline which will scan your source code for code quality issues. The report will be saved as an artifact that you can later download and analyze. TIP: **Tip:** Starting with [GitLab Starter][ee] 9.3, this information will be automatically extracted and shown right in the merge request widget. To do -so, the CI/CD job must be named `codequality` and the artifact path must be -`codeclimate.json`. +so, the CI/CD job must be named `code_quality` and the artifact path must be +`gl-code-quality-report.json`. [Learn more on code quality diffs in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html). +CAUTION: **Caution:** +Code Quality was previously using `codeclimate` and `codequality` for job name and +`codeclimate.json` for the artifact name. While these old names +are still maintained they have been deprecated with GitLab 11.0 and may be removed +in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml` +configuration to reflect that change. + [cli]: https://github.com/codeclimate/codeclimate [dind]: ../docker/using_docker_build.md#use-docker-in-docker-executor [ee]: https://about.gitlab.com/products/ diff --git a/doc/ci/examples/container_scanning.md b/doc/ci/examples/container_scanning.md index a9501f6c577..92ff90507ee 100644 --- a/doc/ci/examples/container_scanning.md +++ b/doc/ci/examples/container_scanning.md @@ -7,10 +7,10 @@ for Vulnerability Static Analysis for containers. All you need is a GitLab Runner with the Docker executor (the shared Runners on GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`, -called `sast:container`: +called `container_scanning`: ```yaml -sast:container: +container_scanning: image: docker:stable variables: DOCKER_DRIVER: overlay2 @@ -34,12 +34,12 @@ sast:container: - retries=0 - echo "Waiting for clair daemon to start" - while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done - - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true + - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true artifacts: - paths: [gl-sast-container-report.json] + paths: [gl-container-scanning-report.json] ``` -The above example will create a `sast:container` job in your CI/CD pipeline, pull +The above example will create a `container_scanning` job in your CI/CD pipeline, pull the image from the [Container Registry](../../user/project/container_registry.md) (whose name is defined from the two `CI_APPLICATION_` variables) and scan it for possible vulnerabilities. The report will be saved as an artifact that you @@ -52,8 +52,15 @@ in our case its named `clair-whitelist.yml`. TIP: **Tip:** Starting with [GitLab Ultimate][ee] 10.4, this information will be automatically extracted and shown right in the merge request widget. To do -so, the CI/CD job must be named `sast:container` and the artifact path must be -`gl-sast-container-report.json`. +so, the CI/CD job must be named `container_scanning` and the artifact path must be +`gl-container-scanning-report.json`. [Learn more on container scanning results shown in merge requests](https://docs.gitlab.com/ee/user/project/merge_requests/container_scanning.html). +CAUTION: **Caution:** +Container Scanning was previously using `sast:container` for job name and +`gl-sast-container-report.json` for the artifact name. While these old names +are still maintained they have been deprecated with GitLab 11.0 and may be removed +in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml` +configuration to reflect that change. + [ee]: https://about.gitlab.com/products/ diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 4873090a2d4..057a4094aed 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -53,6 +53,9 @@ stub_licensed_features(variable_environment_scope: true) EE-specific comments should not be backported to CE. +**Note:** This is only meant as a workaround, we should follow up and +resolve this soon. + ### Detection of EE-only files For each commit (except on `master`), the `ee-files-location-check` CI job tries @@ -105,11 +108,14 @@ is applied not only to models. Here's a list of other examples: - `ee/app/services/foo/create_service.rb` - `ee/app/validators/foo_attr_validator.rb` - `ee/app/workers/foo_worker.rb` +- `ee/app/views/foo.html.haml` +- `ee/app/views/foo/_bar.html.haml` This works because for every path that are present in CE's eager-load/auto-load paths, we add the same `ee/`-prepended path in [`config/application.rb`]. +This also applies to views. -[`config/application.rb`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/d278b76d6600a0e27d8019a0be27971ba23ab640/config/application.rb#L41-51 +[`config/application.rb`]: https://gitlab.com/gitlab-org/gitlab-ee/blob/925d3d4ebc7a2c72964ce97623ae41b8af12538d/config/application.rb#L42-52 ### EE features based on CE features @@ -359,9 +365,37 @@ Blocks of code that are EE-specific should be moved to partials. This avoids conflicts with big chunks of HAML code that that are not fun to resolve when you add the indentation to the equation. -EE-specific views should be placed in `ee/app/views/ee/`, using extra +EE-specific views should be placed in `ee/app/views/`, using extra sub-directories if appropriate. +Instead of using regular `render`, we should use `render_if_exists`, which +will not render anything if it cannot find the specific partial. We use this +so that we could put `render_if_exists` in CE, keeping code the same between +CE and EE. + +Also, it should search for the EE partial first, and then CE partial, and +then if nothing found, render nothing. + +This has two uses: + +- CE renders nothing, and EE renders its EE partial. +- CE renders its CE partial, and EE renders its EE partial, while the view + file stays the same. + +The advantages of this: + +- Minimal code difference between CE and EE. +- Very clear hints about where we're extending EE views while reading CE codes. +- Whenever we want to show something different in CE, we could just add CE + partials. Same applies the other way around. If we just use + `render_if_exists`, it would be very easy to change the content in EE. + +The disadvantage of this: + +- Slightly more work while developing EE features, because now we need to + port `render_if_exists` to CE. +- If we have typos in the partial name, it would be silently ignored. + ### Code in `lib/` Place EE-specific logic in the top-level `EE` module namespace. Namespace the diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md index b469a9c6aef..3d8da6accc1 100644 --- a/doc/development/fe_guide/icons.md +++ b/doc/development/fe_guide/icons.md @@ -1,26 +1,44 @@ -# Icons +# Icons and SVG Illustrations -We are using SVG Icons in GitLab with a SVG Sprite, due to this the icons are only loaded once and then referenced through an ID. The sprite SVG is located under `/assets/icons.svg`. Our goal is to replace one by one all inline SVG Icons (as those currently bloat the HTML) and also all Font Awesome usages. +We manage our own Icon and Illustration library in the [gitlab-svgs][gitlab-svgs] repository. +This repository is published on [npm][npm] and managed as a dependency via yarn. +You can browse all available Icons and Illustrations [here][svg-preview]. +To upgrade to a new version run `yarn upgrade @gitlab-org/gitlab-svgs`. -### Usage in HAML/Rails +## Icons -To use a sprite Icon in HAML or Rails we use a specific helper function : +We are using SVG Icons in GitLab with a SVG Sprite. +This means the icons are only loaded once, and are referenced through an ID. +The sprite SVG is located under `/assets/icons.svg`. + +Our goal is to replace one by one all inline SVG Icons (as those currently bloat the HTML) and also all Font Awesome icons. -`sprite_icon(icon_name, size: nil, css_class: '')` +### Usage in HAML/Rails -**icon_name** Use the icon_name that you can find in the SVG Sprite ([Overview is available here](http://gitlab-org.gitlab.io/gitlab-svgs/)`). +To use a sprite Icon in HAML or Rails we use a specific helper function : -**size (optional)** Use one of the following sizes : 16,24,32,48,72 (this will be translated into a `s16` class) +```ruby +sprite_icon(icon_name, size: nil, css_class: '') +``` -**css_class (optional)** If you want to add additional css classes +- **icon_name** Use the icon_name that you can find in the SVG Sprite + ([Overview is available here][svg-preview]). +- **size (optional)** Use one of the following sizes : 16, 24, 32, 48, 72 (this will be translated into a `s16` class) +- **css_class (optional)** If you want to add additional css classes **Example** -`= sprite_icon('issues', size: 72, css_class: 'icon-danger')` +```haml += sprite_icon('issues', size: 72, css_class: 'icon-danger') +``` **Output from example above** -`<svg class="s72 icon-danger"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons.svg#issues"></use></svg>` +```html +<svg class="s72 icon-danger"> + <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/icons.svg#issues"></use> +</svg> +``` ### Usage in Vue @@ -28,33 +46,71 @@ We have a special Vue component for our sprite icons in `\vue_shared\components\ Sample usage : -`<icon - name="retry" - :size="32" - css-classes="top" - />` - -**name** Name of the Icon in the SVG Sprite ([Overview is available here](http://gitlab-org.gitlab.io/gitlab-svgs/)`). - -**size (optional)** Number value for the size which is then mapped to a specific CSS class (Available Sizes: 8,12,16,18,24,32,48,72 are mapped to `sXX` css classes) - -**css-classes (optional)** Additional CSS Classes to add to the svg tag. +```javascript +<script> +import Icon from "~/vue_shared/components/icon.vue" + +export default { + components: { + Icon, + }, +}; +<script> +<template> + <icon + name="issues" + :size="72" + css-classes="icon-danger" + /> +</template> +``` + +- **name** Name of the Icon in the SVG Sprite ([Overview is available here][svg-preview]). +- **size (optional)** Number value for the size which is then mapped to a specific CSS class + (Available Sizes: 8, 12, 16, 18, 24, 32, 48, 72 are mapped to `sXX` css classes) +- **css-classes (optional)** Additional CSS Classes to add to the svg tag. ### Usage in HTML/JS -Please use the following function inside JS to render an icon : +Please use the following function inside JS to render an icon: `gl.utils.spriteIcon(iconName)` -## Adding a new icon to the sprite +## SVG Illustrations -All Icons and Illustrations are managed in the [gitlab-svgs](https://gitlab.com/gitlab-org/gitlab-svgs) repository which is added as a dev-dependency. +Please use from now on for any SVG based illustrations simple `img` tags to show an illustration by simply using either `image_tag` or `image_path` helpers. +Please use the class `svg-content` around it to ensure nice rendering. -To upgrade to a new SVG Sprite version run `yarn upgrade @gitlab-org/gitlab-svgs`. +### Usage in HAML/Rails -# SVG Illustrations +**Example** -Please use from now on for any SVG based illustrations simple `img` tags to show an illustration by simply using either `image_tag` or `image_path` helpers. Please use the class `svg-content` around it to ensure nice rendering. The illustrations are also organised in the [gitlab-svgs](https://gitlab.com/gitlab-org/gitlab-svgs) repository (as they are then automatically optimised). +```haml +.svg-content + = image_tag 'illustrations/merge_requests.svg' +``` -**Example** +### Usage in Vue -`= image_tag 'illustrations/merge_requests.svg'` +To use an SVG illustrations in a template provide the path as a property and display it through a standard img tag. + +Component: + +```js +<script> +export default { + props: { + svgIllustrationPath: { + type: String, + required: true, + }, + }, +}; +<script> +<template> + <img :src="svgIllustrationPath" /> +</template> +``` + +[npm]: https://www.npmjs.com/package/@gitlab-org/gitlab-svgs +[gitlab-svgs]: https://gitlab.com/gitlab-org/gitlab-svgs +[svg-preview]: https://gitlab-org.gitlab.io/gitlab-svgs diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md index 6d3796e7560..11b9a2e6a64 100644 --- a/doc/development/fe_guide/index.md +++ b/doc/development/fe_guide/index.md @@ -54,8 +54,8 @@ Vuex specific design patterns and practices. ## [Axios](axios.md) Axios specific practices and gotchas. -## [Icons](icons.md) -How we use SVG for our Icons. +## [Icons and Illustrations](icons.md) +How we use SVG for our Icons and Illustrations. ## [Components](components.md) diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md index 04dfe418dbe..284b4b53334 100644 --- a/doc/development/fe_guide/style_guide_js.md +++ b/doc/development/fe_guide/style_guide_js.md @@ -632,7 +632,7 @@ Useful links: // good <span title="tooltip text">Foo</span> - $('span').tooltip('fixTitle'); + $('span').tooltip('_fixTitle'); ``` ### The Javascript/Vue Accord diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index f971d8b7388..e31ee087358 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -8,7 +8,7 @@ All new features built with Vue.js must follow a [Flux architecture][flux]. The main goal we are trying to achieve is to have only one data flow and only one data entry. In order to achieve this goal, you can either use [vuex](#vuex) or use the [store pattern][state-management], explained below: -Each Vue bundle needs a Store - where we keep all the data -,a Service - that we use to communicate with the server - and a main Vue component. +Each Vue bundle needs a Store - where we keep all the data -, a Service - that we use to communicate with the server - and a main Vue component. Think of the Main Vue Component as the entry point of your application. This is the only smart component that should exist in each Vue feature. @@ -17,7 +17,7 @@ This component is responsible for: 1. Calling the Store to store the data received 1. Mounting all the other components - ![Vue Architecture](img/vue_arch.png) +![Vue Architecture](img/vue_arch.png) You can also read about this architecture in vue docs about [state management][state-management] and about [one way data flow][one-way-data-flow]. @@ -51,14 +51,14 @@ of the new feature should be. The Store and the Service should be imported and initialized in this file and provided as a prop to the main component. -Don't forget to follow [these steps.][page_specific_javascript] +Don't forget to follow [these steps][page_specific_javascript]. ### Bootstrapping Gotchas -#### Providing data from Haml to JavaScript +#### Providing data from HAML to JavaScript While mounting a Vue application may be a need to provide data from Rails to JavaScript. To do that, provide the data through `data` attributes in the HTML element and query them while mounting the application. -_Note:_ You should only do this while initing the application, because the mounted element will be replaced with Vue-generated DOM. +_Note:_ You should only do this while initializing the application, because the mounted element will be replaced with Vue-generated DOM. The advantage of providing data from the DOM to the Vue instance through `props` in the `render` function instead of querying the DOM inside the main vue component is that makes tests easier by avoiding the need to @@ -68,6 +68,7 @@ create a fixture or an HTML element in the unit test. See the following example: // haml .js-vue-app{ data: { endpoint: 'foo' }} +// index.js document.addEventListener('DOMContentLoaded', () => new Vue({ el: '.js-vue-app', data() { @@ -87,13 +88,11 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ ``` #### Accessing the `gl` object -When we need to query the `gl` object for data that won't change during the application's lyfecyle, we should do it in the same place where we query the DOM. +When we need to query the `gl` object for data that won't change during the application's life cyle, we should do it in the same place where we query the DOM. By following this practice, we can avoid the need to mock the `gl` object, which will make tests easier. It should be done while initializing our Vue instance, and the data should be provided as `props` to the main component: -##### example: ```javascript - document.addEventListener('DOMContentLoaded', () => new Vue({ el: '.js-vue-app', render(createElement) { @@ -121,25 +120,6 @@ in one table would not be a good use of this pattern. You can read more about components in Vue.js site, [Component System][component-system] -#### Components Gotchas -1. Using SVGs icons in components: To use an SVG icon in a template use the `icon.vue` -1. Using SVGs illustrations in components: To use an SVG illustrations in a template provide the path as a prop and display it through a standard img tag. - ```javascript - <script> - export default { - props: { - svgIllustrationPath: { - type: String, - required: true, - }, - }, - }; - <script> - <template> - <img :src="svgIllustrationPath" /> - </template> - ``` - ### A folder for the Store #### Vuex @@ -163,13 +143,13 @@ Refer to [axios](axios.md) for more details. Axios instance should only be imported in the service file. - ```javascript - import axios from 'javascripts/lib/utils/axios_utils'; - ``` +```javascript +import axios from '~/lib/utils/axios_utils'; +``` ### End Result -The following example shows an application: +The following example shows an application: ```javascript // store.js @@ -177,8 +157,8 @@ export default class Store { /** * This is where we will iniatialize the state of our data. - * Usually in a small SPA you don't need any options when starting the store. In the case you do - * need guarantee it's an Object and it's documented. + * Usually in a small SPA you don't need any options when starting the store. + * In that case you do need guarantee it's an Object and it's documented. * * @param {Object} options */ @@ -186,7 +166,7 @@ export default class Store { this.options = options; // Create a state object to handle all our data in the same place - this.todos = []: + this.todos = []; } setTodos(todos = []) { @@ -207,7 +187,7 @@ export default class Store { } // service.js -import axios from 'javascripts/lib/utils/axios_utils' +import axios from '~/lib/utils/axios_utils' export default class Service { constructor(options) { @@ -233,8 +213,8 @@ export default { type: Object, required: true, }, - } -} + }, +}; </script> <template> <div> @@ -275,7 +255,7 @@ export default { }, created() { - this.service = new Service('todos'); + this.service = new Service('/todos'); this.getTodos(); }, @@ -284,9 +264,9 @@ export default { getTodos() { this.isLoading = true; - this.service.getTodos() - .then(response => response.json()) - .then((response) => { + this.service + .getTodos() + .then(response => { this.store.setTodos(response); this.isLoading = false; }) @@ -296,18 +276,21 @@ export default { }); }, - addTodo(todo) { - this.service.addTodo(todo) - then(response => response.json()) - .then((response) => { - this.store.addTodo(response); - }) - .catch(() => { - // Show an error - }); - } - } -} + addTodo(event) { + this.service + .addTodo({ + title: 'New entry', + text: `You clicked on ${event.target.tagName}`, + }) + .then(response => { + this.store.addTodo(response); + }) + .catch(() => { + // Show an error + }); + }, + }, +}; </script> <template> <div class="container"> @@ -333,7 +316,7 @@ export default { <div> </template> -// bundle.js +// index.js import todoComponent from 'todos_main_component.vue'; new Vue({ @@ -365,76 +348,79 @@ Each Vue component has a unique output. This output is always present in the ren Although we can test each method of a Vue component individually, our goal must be to test the output of the render/template function, which represents the state at all times. -Make use of Vue Resource Interceptors to mock data returned by the service. +Make use of the [axios mock adapter](axios.md#mock-axios-response-on-tests) to mock data returned. Here's how we would test the Todo App above: ```javascript -import component from 'todos_main_component'; +import Vue from 'vue'; +import axios from '~/lib/utils/axios_utils'; +import MockAdapter from 'axios-mock-adapter'; describe('Todos App', () => { - it('should render the loading state while the request is being made', () => { + let vm; + let mock; + + beforeEach(() => { + // Create a mock adapter for stubbing axios API requests + mock = new MockAdapter(axios); + const Component = Vue.extend(component); - const vm = new Component().$mount(); + // Mount the Component + vm = new Component().$mount(); + }); + + afterEach(() => { + // Reset the mock adapter + mock.restore(); + // Destroy the mounted component + vm.$destroy(); + }); + it('should render the loading state while the request is being made', () => { expect(vm.$el.querySelector('i.fa-spin')).toBeDefined(); }); - describe('with data', () => { - // Mock the service to return data - const interceptor = (request, next) => { - next(request.respondWith(JSON.stringify([{ + it('should render todos returned by the endpoint', done => { + // Mock the get request on the API endpoint to return data + mock.onGet('/todos').replyOnce(200, [ + { title: 'This is a todo', - body: 'This is the text' - }]), { - status: 200, - })); - }; - - let vm; - - beforeEach(() => { - Vue.http.interceptors.push(interceptor); - - const Component = Vue.extend(component); + text: 'This is the text', + }, + ]); - vm = new Component().$mount(); + Vue.nextTick(() => { + const items = vm.$el.querySelectorAll('.js-todo-list div') + expect(items.length).toBe(1); + expect(items[0].textContent).toContain('This is the text'); + done(); }); + }); - afterEach(() => { - Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor); - }); + it('should add a todos on button click', (done) => { + // Mock the put request and check that the sent data object is correct + mock.onPut('/todos').replyOnce((req) => { + expect(req.data).toContain('text'); + expect(req.data).toContain('title'); - it('should render todos', (done) => { - setTimeout(() => { - expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(1); - done(); - }, 0); + return [201, {}]; }); - }); - describe('add todo', () => { - let vm; - beforeEach(() => { - const Component = Vue.extend(component); - vm = new Component().$mount(); - }); - it('should add a todos', (done) => { - setTimeout(() => { - vm.$el.querySelector('.js-add-todo').click(); + vm.$el.querySelector('.js-add-todo').click(); - // Add a new interceptor to mock the add Todo request - Vue.nextTick(() => { - expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(2); - }); - }, 0); + // Add a new interceptor to mock the add Todo request + Vue.nextTick(() => { + expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(2); + done(); }); }); }); ``` -#### `mountComponent` helper + +### `mountComponent` helper There is a helper in `spec/javascripts/helpers/vue_mount_component_helper.js` that allows you to mount a component with the given props: ```javascript @@ -447,13 +433,10 @@ const data = {prop: 'foo'}; const vm = mountComponent(Component, data); ``` -#### Test the component's output +### Test the component's output The main return value of a Vue component is the rendered output. In order to test the component we need to test the rendered output. [Vue][vue-test] guide's to unit test show us exactly that: -### Stubbing API responses -Refer to [mock axios](axios.md#mock-axios-response-on-tests) - [vue-docs]: http://vuejs.org/guide/index.html [issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards @@ -466,4 +449,3 @@ Refer to [mock axios](axios.md#mock-axios-response-on-tests) [issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6 [flux]: https://facebook.github.io/flux [axios]: https://github.com/axios/axios -[axios-interceptors]: https://github.com/axios/axios#interceptors diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md index ed35f08432f..2a3a126ca5c 100644 --- a/doc/development/new_fe_guide/development/accessibility.md +++ b/doc/development/new_fe_guide/development/accessibility.md @@ -1,3 +1,48 @@ -# Accessibility +# Accessiblity +Using semantic HTML plays a key role when it comes to accessibility. -> TODO: Add content +## Accessible Rich Internet Applications - ARIA +WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities. + +> Note: It is [recommended][using-aria] to use semantic elements as the primary method to achieve accessibility rather than adding aria attributes. Adding aria attributes should be seen as a secondary method for creating accessible elements. + +### Role +The `role` attribute describes the role the element plays in the context of the document. + +Check the list of WAI-ARIA roles [here][roles] + +## Icons +When using icons or images that aren't absolutely needed to understand the context, we should use `aria-hidden="true"`. + +On the other hand, if an icon is crucial to understand the context we should do one of the following: +1. Use `aria-label` in the element with a meaningful description +1. Use `aria-labelledby` to point to an element that contains the explanation for that icon + +## Form inputs +In forms we should use the `for` attribute in the label statement: +``` +<div> + <label for="name">Fill in your name:</label> + <input type="text" id="name" name="name"> +</div> +``` + +## Testing + +1. On MacOS you can use [VoiceOver][voice-over] by pressing `cmd+F5`. +1. On Windows you can use [Narrator][narrator] by pressing Windows logo key + Ctrl + Enter. + +## Online resources + +- [Chrome Accessibility Developer Tools][dev-tools] for testing accessibility +- [Audit Rules Page][audit-rules] for best practices +- [Lighthouse Accessibility Score][lighthouse] for accessibility audits + +[using-aria]: https://www.w3.org/TR/using-aria/#notes2 +[dev-tools]: https://github.com/GoogleChrome/accessibility-developer-tools +[audit-rules]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules +[aria-w3c]: https://www.w3.org/TR/wai-aria-1.1/ +[roles]: https://www.w3.org/TR/wai-aria-1.1/#landmark_roles +[voice-over]: https://www.apple.com/accessibility/mac/vision/ +[narrator]: https://www.microsoft.com/en-us/accessibility/windows +[lighthouse]: https://developers.google.com/web/tools/lighthouse/scoring#a11y diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 1f2b4d9d3d9..1f399a8a3f7 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -27,8 +27,8 @@ Please see the [installation from source guide](installation.md) and the [instal ### Non-Unix operating systems such as Windows -GitLab is developed for Unix operating systems. -GitLab does **not** run on Windows and we have no plans of supporting it in the near future. +GitLab is developed for Unix operating systems. +It does **not** run on Windows, and we have no plans to support it in the near future. For the latest development status view this [issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/46567). Please consider using a virtual machine to run GitLab. ## Ruby versions diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 77139c50d07..5faae7ca2d6 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -31,8 +31,8 @@ sudo apt-get install -y rsync >**Note:** In GitLab 9.2 the timestamp format was changed from `EPOCH_YYYY_MM_DD` to -`EPOCH_YYYY_MM_DD_GitLab version`, for example `1493107454_2017_04_25` -would become `1493107454_2017_04_25_9.1.0`. +`EPOCH_YYYY_MM_DD_GitLab_version`, for example `1493107454_2018_04_25` +would become `1493107454_2018_04_25_10.6.4-ce`. The backup archive will be saved in `backup_path`, which is specified in the `config/gitlab.yml` file. @@ -41,8 +41,8 @@ identifies the time at which each backup was created, plus the GitLab version. The timestamp is needed if you need to restore GitLab and multiple backups are available. -For example, if the backup name is `1493107454_2017_04_25_9.1.0_gitlab_backup.tar`, -then the timestamp is `1493107454_2017_04_25_9.1.0`. +For example, if the backup name is `1493107454_2018_04_25_10.6.4-ce_gitlab_backup.tar`, +then the timestamp is `1493107454_2018_04_25_10.6.4-ce`. ### Creating a backup of the GitLab system @@ -574,7 +574,7 @@ First make sure your backup tar file is in the backup directory described in the `/var/opt/gitlab/backups`. ```shell -sudo cp 1493107454_2017_04_25_9.1.0_gitlab_backup.tar /var/opt/gitlab/backups/ +sudo cp 11493107454_2018_04_25_10.6.4-ce_gitlab_backup.tar /var/opt/gitlab/backups/ ``` Stop the processes that are connected to the database. Leave the rest of GitLab @@ -592,7 +592,7 @@ restore: ```shell # This command will overwrite the contents of your GitLab database! -sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2017_04_25_9.1.0 +sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2018_04_25_10.6.4-ce ``` Next, restore `/etc/gitlab/gitlab-secrets.json` if necessary as mentioned above. diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 33e2d710410..efec365042a 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -1,7 +1,5 @@ # Auto DevOps -DANGER: Auto DevOps is currently in **Beta** and _not recommended for production use_. - > [Introduced][ce-37115] in GitLab 10.0. Auto DevOps automatically detects, builds, tests, deploys, and monitors your @@ -222,8 +220,8 @@ tests, it's up to you to add them. ### Auto Code Quality -Auto Code Quality uses the open source -[`codeclimate` image](https://hub.docker.com/r/codeclimate/codeclimate/) to run +Auto Code Quality uses the +[Code Quality image](https://gitlab.com/gitlab-org/security-products/codequality) to run static analysis and other code checks on the current code. The report is created, and is uploaded as an artifact which you can later download and check out. @@ -496,7 +494,16 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-variables-environment-variables). Set it to use a custom database name. | | `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` | | `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | +| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). | | `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. | +| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. | +| `CODEQUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. | +| `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. | +| `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dependency_scanning` job. If the variable is present, the job will not be created. | +| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast:container` job. If the variable is present, the job will not be created. | +| `REVIEW_DISABLED` | From GitLab 11.0, this variable can be used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs will not be created. | +| `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. | +| `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. | TIP: **Tip:** Set up the replica variables using a @@ -579,6 +586,21 @@ If `STAGING_ENABLED` is defined in your project (e.g., set `STAGING_ENABLED` to to a `staging` environment, and a `production_manual` job will be created for you when you're ready to manually deploy to production. +#### Deploy policy for canary environments **[PREMIUM]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ci-yml/merge_requests/171) +in GitLab 11.0. + +A [canary environment](https://docs.gitlab.com/ee/user/project/canary_deployments.html) can be used +before any changes are deployed to production. + +If `CANARY_ENABLED` is defined in your project (e.g., set `CANARY_ENABLED` to +`1` as a secret variable) then two manual jobs will be created: + +- `canary` which will deploy the application to the canary environment +- `production_manual` which is to be used by you when you're ready to manually + deploy to production. + #### Incremental rollout to production **[PREMIUM]** > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5415) in GitLab 10.8. diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md index 0b16af2953b..61c04f3d9bb 100644 --- a/doc/topics/autodevops/quick_start_guide.md +++ b/doc/topics/autodevops/quick_start_guide.md @@ -1,7 +1,5 @@ # Auto DevOps: quick start guide -DANGER: Auto DevOps is currently in **Beta** and _not recommended for production use_. - > [Introduced][ce-37115] in GitLab 10.0. This is a step-by-step guide to deploying a project hosted on GitLab.com to @@ -128,10 +126,10 @@ Next, a pipeline needs to be triggered. Since the test project doesn't have a manually visit `https://gitlab.com/<username>/minimal-ruby-app/pipelines/new`, where `<username>` is your username. -This will create a new pipeline with several jobs: `build`, `test`, `codequality`, +This will create a new pipeline with several jobs: `build`, `test`, `code_quality`, and `production`. The `build` job will create a Docker image with your new change and push it to the Container Registry. The `test` job will test your -changes, whereas the `codequality` job will run static analysis on your changes. +changes, whereas the `code_quality` job will run static analysis on your changes. Finally, the `production` job will deploy your changes to a production application. Once the deploy job succeeds you should be able to see your application by diff --git a/doc/university/README.md b/doc/university/README.md index 55865ac23e8..aa68c841f92 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -28,7 +28,6 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project #### 1.1. Version Control and Git 1. [Version Control Systems](https://docs.google.com/presentation/d/16sX7hUrCZyOFbpvnrAFrg6tVO5_yT98IgdAqOmXwBho/edit#slide=id.g72f2e4906_2_29) -1. [Operating Systems and How Git Works](https://drive.google.com/a/gitlab.com/file/d/0B41DBToSSIG_OVYxVFJDOGI3Vzg/view?usp=sharing) 1. [Code School: An Introduction to Git](https://www.codeschool.com/account/courses/try-git) #### 1.2. GitLab Basics diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md index 5933bcedc8b..4d5b2c97291 100644 --- a/doc/user/project/integrations/jira.md +++ b/doc/user/project/integrations/jira.md @@ -111,7 +111,7 @@ in the table below. | ----- | ----------- | | `Web URL` | The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., `https://jira.example.com`. | | `JIRA API URL` | The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., `https://jira-api.example.com`. | -| `Username` | The user name created in [configuring JIRA step](#configuring-jira). | +| `Username` | The user name created in [configuring JIRA step](#configuring-jira). Using the email address will cause `401 unauthorized`. | | `Password` |The password of the user created in [configuring JIRA step](#configuring-jira). | | `Transition ID` | This is the ID of a transition that moves issues to the desired state. **Closing JIRA issues via commits or Merge Requests won't work if you don't set the ID correctly.** | diff --git a/doc/user/project/integrations/project_services.md b/doc/user/project/integrations/project_services.md index 9496d6f2ce0..074eeb729e3 100644 --- a/doc/user/project/integrations/project_services.md +++ b/doc/user/project/integrations/project_services.md @@ -34,7 +34,7 @@ Click on the service links to see further configuration instructions and details | [Emails on push](emails_on_push.md) | Email the commits and diff of each push to a list of recipients | | External Wiki | Replaces the link to the internal wiki with a link to an external wiki | | Flowdock | Flowdock is a collaboration web app for technical teams | -| Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities | +| Gemnasium _(Has been deprecated in GitLab 11.0)_ | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities | | [HipChat](hipchat.md) | Private group chat and IM | | [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway | | [JIRA](jira.md) | JIRA issue tracker | diff --git a/doc/user/project/merge_requests/resolve_conflicts.md b/doc/user/project/merge_requests/resolve_conflicts.md index 68c49054e47..ecbc8534eea 100644 --- a/doc/user/project/merge_requests/resolve_conflicts.md +++ b/doc/user/project/merge_requests/resolve_conflicts.md @@ -29,7 +29,7 @@ The merge conflict resolution editor allows for more complex merge conflicts, which require the user to manually modify a file in order to resolve a conflict, to be solved right form the GitLab interface. Use the **Edit inline** button to open the editor. Once you're sure about your changes, hit the -**Commit conflict resolution** button. +**Commit to source branch** button. ![Merge conflict editor](img/merge_conflict_editor.png) diff --git a/doc/user/snippets.md b/doc/user/snippets.md index 2170b079f62..8397c0b00ef 100644 --- a/doc/user/snippets.md +++ b/doc/user/snippets.md @@ -27,3 +27,36 @@ Personal snippets are not related to any project and can be created completely i You can download the raw content of a snippet. By default snippets will be downloaded with Linux-style line endings (`LF`). If you want to preserve the original line endings you need to add a parameter `line_ending=raw` (eg. `https://gitlab.com/snippets/SNIPPET_ID/raw?line_ending=raw`). In case a snippet was created using the GitLab web interface the original line ending is Windows-like (`CRLF`). + +## Embedded Snippets + +> Introduced in GitLab 10.8. + +Public snippets can not only be shared, but also embedded on any website. This +allows to reuse a GitLab snippet in multiple places and any change to the source +is automatically reflected in the embedded snippet. + +To embed a snippet, first make sure that: + +- The project is public (if it's a project snippet) +- The snippet is public +- In **Project > Settings > Permissions**, the snippets permissions are + set to **Everyone with access** + +Once the above conditions are met, the "Embed" section will appear in your snippet +where you can simply click on the "Copy to clipboard" button. This copies a one-line +script that you can add to any website or blog post. + +Here's how an example code looks like: + +```html +<script src="https://gitlab.com/namespace/project/snippets/SNIPPET_ID.js"></script> +``` + +Here's how an embedded snippet looks like: + +<script src="https://gitlab.com/gitlab-org/gitlab-ce/snippets/1717978.js"></script> + +Embedded snippets are displayed with a header that shows the file name if defined, +the snippet size, a link to GitLab, and the actual snippet content. Actions in +the header allow users to see the snippet in raw format and download it. diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index a68bb8b27ca..dc6da1938f3 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -1 +1 @@ -This document was moved to [user/project/merge_requests](../user/project/merge_requests.md). +This document was moved to [user/project/merge_requests/index.md](../user/project/merge_requests/index.md). diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md index f1501c81b27..fc592b99860 100644 --- a/doc/workflow/notifications.md +++ b/doc/workflow/notifications.md @@ -106,6 +106,10 @@ by yourself (except when an issue is due). You will only receive automatic notifications when somebody else comments or adds changes to the ones that you've created or mentions you. +If a merge request becomes unmergeable, its author will be notified about the cause. +If a user has also set the merge request to automatically merge once pipeline succeeds, +then that user will also be notified. + ### Email Headers Notification emails include headers that provide extra content about the notification received: diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index c99505e6bdf..b2f1cbec204 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -11,7 +11,7 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | <kbd>f</kbd> | Focus filter | | <kbd>p</kbd> + <kbd>b</kbd> | Show/hide the Performance Bar | | <kbd>?</kbd> | Show/hide this dialog | -| <kbd>⌘</kbd> + <kbd>shift</kbd> + <kbd>p</kbd> | Toggle markdown preview | +| <kbd>Cmd</kbd>/<kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>p</kbd> | Toggle markdown preview | | <kbd>↑</kbd> | Edit last comment (when focused on an empty textarea) | ## Project Files Browsing @@ -70,8 +70,8 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' | <kbd>→</kbd> or <kbd>l</kbd> | Scroll right | | <kbd>↑</kbd> or <kbd>k</kbd> | Scroll up | | <kbd>↓</kbd> or <kbd>j</kbd> | Scroll down | -| <kbd>shift</kbd> + <kbd>↑</kbd> or <kbd>shift</kbd> + <kbd>k</kbd> | Scroll to top | -| <kbd>shift</kbd> + <kbd>↓</kbd> or <kbd>shift</kbd> + <kbd>j</kbd> | Scroll to bottom | +| <kbd>Shift</kbd> + <kbd>↑</kbd> or <kbd>Shift</kbd> + <kbd>k</kbd> | Scroll to top | +| <kbd>Shift</kbd> + <kbd>↓</kbd> or <kbd>Shift</kbd> + <kbd>j</kbd> | Scroll to bottom | ## Issues and Merge Requests diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index f13d29884d4..762bf616268 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -31,6 +31,9 @@ A Todo appears in your Todos dashboard when: - you are `@mentioned` in a comment on a commit, - a job in the CI pipeline running for your merge request failed, but this job is not allowed to fail. +- a merge request becomes unmergeable, and you are either: + - the author, or + - have set it to automatically merge once pipeline succeeds. ### Directly addressed Todos diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 25d78fc761d..174c5af91d5 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -125,7 +125,7 @@ module API # (fixed in https://github.com/rails/rails/pull/25976). project.tags.map(&:name).sort end - expose :ssh_url_to_repo, :http_url_to_repo, :web_url + expose :ssh_url_to_repo, :http_url_to_repo, :web_url, :readme_url expose :avatar_url do |project, options| project.avatar_url(only_path: false) end diff --git a/lib/api/helpers/pagination.rb b/lib/api/helpers/pagination.rb index 09805049169..3308212216e 100644 --- a/lib/api/helpers/pagination.rb +++ b/lib/api/helpers/pagination.rb @@ -2,67 +2,240 @@ module API module Helpers module Pagination def paginate(relation) - relation = add_default_order(relation) + strategy = if params[:pagination] == 'keyset' && Feature.enabled?('api_keyset_pagination') + KeysetPaginationStrategy + else + DefaultPaginationStrategy + end - relation.page(params[:page]).per(params[:per_page]).tap do |data| - add_pagination_headers(data) - end + strategy.new(self).paginate(relation) end - private + class KeysetPaginationInfo + attr_reader :relation, :request_context - def add_pagination_headers(paginated_data) - header 'X-Per-Page', paginated_data.limit_value.to_s - header 'X-Page', paginated_data.current_page.to_s - header 'X-Next-Page', paginated_data.next_page.to_s - header 'X-Prev-Page', paginated_data.prev_page.to_s - header 'Link', pagination_links(paginated_data) + def initialize(relation, request_context) + # This is because it's rather complex to support multiple values with possibly different sort directions + # (and we don't need this in the API) + if relation.order_values.size > 1 + raise "Pagination only supports ordering by a single column." \ + "The following columns were given: #{relation.order_values.map { |v| v.expr.name }}" + end - return if data_without_counts?(paginated_data) + @relation = relation + @request_context = request_context + end - header 'X-Total', paginated_data.total_count.to_s - header 'X-Total-Pages', total_pages(paginated_data).to_s - end + def fields + keys.zip(values).reject { |_, v| v.nil? }.to_h + end - def pagination_links(paginated_data) - request_url = request.url.split('?').first - request_params = params.clone - request_params[:per_page] = paginated_data.limit_value + def column_for_order_by(relation) + relation.order_values.first&.expr&.name + end - links = [] + # Sort direction (`:asc` or `:desc`) + def sort + @sort ||= if order_by_primary_key? + # Default order is by id DESC + :desc + else + # API defaults to DESC order if param `sort` not present + request_context.params[:sort]&.to_sym || :desc + end + end - request_params[:page] = paginated_data.prev_page - links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") if request_params[:page] + # Do we only sort by primary key? + def order_by_primary_key? + keys.size == 1 && keys.first == primary_key + end - request_params[:page] = paginated_data.next_page - links << %(<#{request_url}?#{request_params.to_query}>; rel="next") if request_params[:page] + def primary_key + relation.model.primary_key.to_sym + end - request_params[:page] = 1 - links << %(<#{request_url}?#{request_params.to_query}>; rel="first") + def sort_ascending? + sort == :asc + end - unless data_without_counts?(paginated_data) - request_params[:page] = total_pages(paginated_data) - links << %(<#{request_url}?#{request_params.to_query}>; rel="last") + # Build hash of request parameters for a given record (relevant to pagination) + def params_for(record) + return {} unless record + + keys.each_with_object({}) do |key, h| + h["ks_prev_#{key}".to_sym] = record.attributes[key.to_s] + end end - links.join(', ') - end + private + + # All values present in request parameters that correspond to #keys. + def values + @values ||= keys.map do |key| + request_context.params["ks_prev_#{key}".to_sym] + end + end - def total_pages(paginated_data) - # Ensure there is in total at least 1 page - [paginated_data.total_pages, 1].max + # All keys relevant to pagination. + # This always includes the primary key. Optionally, the `order_by` key is prepended. + def keys + @keys ||= [column_for_order_by(relation), primary_key].compact.uniq + end end - def add_default_order(relation) - if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty? - relation = relation.order(:id) + class KeysetPaginationStrategy + attr_reader :request_context + delegate :params, :header, :request, to: :request_context + + def initialize(request_context) + @request_context = request_context + end + + def paginate(relation) + pagination = KeysetPaginationInfo.new(relation, request_context) + + paged_relation = relation.limit(per_page) + + if conds = conditions(pagination) + paged_relation = paged_relation.where(*conds) + end + + # In all cases: sort by primary key (possibly in addition to another sort column) + paged_relation = paged_relation.order(pagination.primary_key => pagination.sort) + + add_default_pagination_headers + + if last_record = paged_relation.last + next_page_params = pagination.params_for(last_record) + add_navigation_links(next_page_params) + end + + paged_relation + end + + private + + def conditions(pagination) + fields = pagination.fields + + return nil if fields.empty? + + placeholder = fields.map { '?' } + + comp = if pagination.sort_ascending? + '>' + else + '<' + end + + [ + # Row value comparison: + # (A, B) < (a, b) <=> (A < a) OR (A = a AND B < b) + # <=> A <= a AND ((A < a) OR (A = a AND B < b)) + "(#{fields.keys.join(',')}) #{comp} (#{placeholder.join(',')})", + *fields.values + ] + end + + def per_page + params[:per_page] + end + + def add_default_pagination_headers + header 'X-Per-Page', per_page.to_s + end + + def add_navigation_links(next_page_params) + header 'X-Next-Page', page_href(next_page_params) + header 'Link', link_for('next', next_page_params) end - relation + def page_href(next_page_params) + request_url = request.url.split('?').first + request_params = params.dup + request_params[:per_page] = per_page + + request_params.merge!(next_page_params) if next_page_params + + "#{request_url}?#{request_params.to_query}" + end + + def link_for(rel, next_page_params) + %(<#{page_href(next_page_params)}>; rel="#{rel}") + end end - def data_without_counts?(paginated_data) - paginated_data.is_a?(Kaminari::PaginatableWithoutCount) + class DefaultPaginationStrategy + attr_reader :request_context + delegate :params, :header, :request, to: :request_context + + def initialize(request_context) + @request_context = request_context + end + + def paginate(relation) + relation = add_default_order(relation) + + relation.page(params[:page]).per(params[:per_page]).tap do |data| + add_pagination_headers(data) + end + end + + private + + def add_default_order(relation) + if relation.is_a?(ActiveRecord::Relation) && relation.order_values.empty? + relation = relation.order(:id) + end + + relation + end + + def add_pagination_headers(paginated_data) + header 'X-Per-Page', paginated_data.limit_value.to_s + header 'X-Page', paginated_data.current_page.to_s + header 'X-Next-Page', paginated_data.next_page.to_s + header 'X-Prev-Page', paginated_data.prev_page.to_s + header 'Link', pagination_links(paginated_data) + + return if data_without_counts?(paginated_data) + + header 'X-Total', paginated_data.total_count.to_s + header 'X-Total-Pages', total_pages(paginated_data).to_s + end + + def pagination_links(paginated_data) + request_url = request.url.split('?').first + request_params = params.clone + request_params[:per_page] = paginated_data.limit_value + + links = [] + + request_params[:page] = paginated_data.prev_page + links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") if request_params[:page] + + request_params[:page] = paginated_data.next_page + links << %(<#{request_url}?#{request_params.to_query}>; rel="next") if request_params[:page] + + request_params[:page] = 1 + links << %(<#{request_url}?#{request_params.to_query}>; rel="first") + + unless data_without_counts?(paginated_data) + request_params[:page] = total_pages(paginated_data) + links << %(<#{request_url}?#{request_params.to_query}>; rel="last") + end + + links.join(', ') + end + + def total_pages(paginated_data) + # Ensure there is in total at least 1 page + [paginated_data.total_pages, 1].max + end + + def data_without_counts?(paginated_data) + paginated_data.is_a?(Kaminari::PaginatableWithoutCount) + end end end end diff --git a/lib/api/helpers/related_resources_helpers.rb b/lib/api/helpers/related_resources_helpers.rb index 7f4d6e58b34..a2cbed30229 100644 --- a/lib/api/helpers/related_resources_helpers.rb +++ b/lib/api/helpers/related_resources_helpers.rb @@ -13,9 +13,14 @@ module API def expose_url(path) url_options = Gitlab::Application.routes.default_url_options - protocol, host, port = url_options.slice(:protocol, :host, :port).values + protocol, host, port, script_name = url_options.values_at(:protocol, :host, :port, :script_name) - URI::Generic.build(scheme: protocol, host: host, port: port, path: path).to_s + # Using a blank component at the beginning of the join we ensure + # that the resulted path will start with '/'. If the resulted path + # does not start with '/', URI::Generic#build will fail + path_with_script_name = File.join('', [script_name, path].select(&:present?)) + + URI::Generic.build(scheme: protocol, host: host, port: port, path: path_with_script_name).to_s end private diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 6b72caea8fd..a3dac36b8b6 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -113,7 +113,7 @@ module API { api_version: API.version, gitlab_version: Gitlab::VERSION, - gitlab_rev: Gitlab::REVISION, + gitlab_rev: Gitlab.revision, redis: redis_ping } end diff --git a/lib/api/milestone_responses.rb b/lib/api/milestone_responses.rb index c570eace862..a8eb137e46a 100644 --- a/lib/api/milestone_responses.rb +++ b/lib/api/milestone_responses.rb @@ -24,7 +24,7 @@ module API optional :state_event, type: String, values: %w[close activate], desc: 'The state event of the milestone ' use :optional_params - at_least_one_of :title, :description, :due_date, :state_event + at_least_one_of :title, :description, :start_date, :due_date, :state_event end def list_milestones_for(parent) diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 152df23a327..e31c332b6e4 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -5,7 +5,7 @@ module API helpers do def current_settings @current_setting ||= - (ApplicationSetting.current || ApplicationSetting.create_from_defaults) + (ApplicationSetting.current_without_cache || ApplicationSetting.create_from_defaults) end end diff --git a/lib/api/v3/settings.rb b/lib/api/v3/settings.rb index 9b4ab7630fb..fc56495c8b1 100644 --- a/lib/api/v3/settings.rb +++ b/lib/api/v3/settings.rb @@ -6,7 +6,7 @@ module API helpers do def current_settings @current_setting ||= - (ApplicationSetting.current || ApplicationSetting.create_from_defaults) + (ApplicationSetting.current_without_cache || ApplicationSetting.create_from_defaults) end end diff --git a/lib/api/version.rb b/lib/api/version.rb index 9ba576bd828..3b10bfa6a7d 100644 --- a/lib/api/version.rb +++ b/lib/api/version.rb @@ -6,7 +6,7 @@ module API detail 'This feature was introduced in GitLab 8.13.' end get '/version' do - { version: Gitlab::VERSION, revision: Gitlab::REVISION } + { version: Gitlab::VERSION, revision: Gitlab.revision } end end end diff --git a/lib/backup/artifacts.rb b/lib/backup/artifacts.rb index 6a5a223a614..45a935ab352 100644 --- a/lib/backup/artifacts.rb +++ b/lib/backup/artifacts.rb @@ -2,7 +2,11 @@ require 'backup/files' module Backup class Artifacts < Files - def initialize + attr_reader :progress + + def initialize(progress) + @progress = progress + super('artifacts', JobArtifactUploader.root) end end diff --git a/lib/backup/builds.rb b/lib/backup/builds.rb index f869916e199..adf85ca4719 100644 --- a/lib/backup/builds.rb +++ b/lib/backup/builds.rb @@ -2,7 +2,11 @@ require 'backup/files' module Backup class Builds < Files - def initialize + attr_reader :progress + + def initialize(progress) + @progress = progress + super('builds', Settings.gitlab_ci.builds_path) end end diff --git a/lib/backup/database.rb b/lib/backup/database.rb index 5e6828de597..1608f7ad02d 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -2,9 +2,11 @@ require 'yaml' module Backup class Database + attr_reader :progress attr_reader :config, :db_file_name - def initialize + def initialize(progress) + @progress = progress @config = YAML.load_file(File.join(Rails.root, 'config', 'database.yml'))[Rails.env] @db_file_name = File.join(Gitlab.config.backup.path, 'db', 'database.sql.gz') end @@ -19,12 +21,12 @@ module Backup dump_pid = case config["adapter"] when /^mysql/ then - $progress.print "Dumping MySQL database #{config['database']} ... " + progress.print "Dumping MySQL database #{config['database']} ... " # Workaround warnings from MySQL 5.6 about passwords on cmd line ENV['MYSQL_PWD'] = config["password"].to_s if config["password"] spawn('mysqldump', *mysql_args, config['database'], out: compress_wr) when "postgresql" then - $progress.print "Dumping PostgreSQL database #{config['database']} ... " + progress.print "Dumping PostgreSQL database #{config['database']} ... " pg_env pgsql_args = ["--clean"] # Pass '--clean' to include 'DROP TABLE' statements in the DB dump. if Gitlab.config.backup.pg_schema @@ -53,12 +55,12 @@ module Backup restore_pid = case config["adapter"] when /^mysql/ then - $progress.print "Restoring MySQL database #{config['database']} ... " + progress.print "Restoring MySQL database #{config['database']} ... " # Workaround warnings from MySQL 5.6 about passwords on cmd line ENV['MYSQL_PWD'] = config["password"].to_s if config["password"] spawn('mysql', *mysql_args, config['database'], in: decompress_rd) when "postgresql" then - $progress.print "Restoring PostgreSQL database #{config['database']} ... " + progress.print "Restoring PostgreSQL database #{config['database']} ... " pg_env spawn('psql', config['database'], in: decompress_rd) end @@ -111,9 +113,9 @@ module Backup def report_success(success) if success - $progress.puts '[DONE]'.color(:green) + progress.puts '[DONE]'.color(:green) else - $progress.puts '[FAILED]'.color(:red) + progress.puts '[FAILED]'.color(:red) end end end diff --git a/lib/backup/lfs.rb b/lib/backup/lfs.rb index 4e234e50a7a..185ff8ae6bd 100644 --- a/lib/backup/lfs.rb +++ b/lib/backup/lfs.rb @@ -2,7 +2,11 @@ require 'backup/files' module Backup class Lfs < Files - def initialize + attr_reader :progress + + def initialize(progress) + @progress = progress + super('lfs', Settings.lfs.storage_path) end end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index f27ce4d2b2b..a8da0c7edef 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -4,6 +4,12 @@ module Backup FOLDERS_TO_BACKUP = %w[repositories db].freeze FILE_NAME_SUFFIX = '_gitlab_backup.tar'.freeze + attr_reader :progress + + def initialize(progress) + @progress = progress + end + def pack # Make sure there is a connection ActiveRecord::Base.connection.reconnect! @@ -14,11 +20,11 @@ module Backup end # create archive - $progress.print "Creating backup archive: #{tar_file} ... " + progress.print "Creating backup archive: #{tar_file} ... " # Set file permissions on open to prevent chmod races. tar_system_options = { out: [tar_file, 'w', Gitlab.config.backup.archive_permissions] } if Kernel.system('tar', '-cf', '-', *backup_contents, tar_system_options) - $progress.puts "done".color(:green) + progress.puts "done".color(:green) else puts "creating archive #{tar_file} failed".color(:red) abort 'Backup failed' @@ -29,11 +35,11 @@ module Backup end def upload - $progress.print "Uploading backup archive to remote storage #{remote_directory} ... " + progress.print "Uploading backup archive to remote storage #{remote_directory} ... " connection_settings = Gitlab.config.backup.upload.connection if connection_settings.blank? - $progress.puts "skipped".color(:yellow) + progress.puts "skipped".color(:yellow) return end @@ -43,7 +49,7 @@ module Backup multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size, encryption: Gitlab.config.backup.upload.encryption, storage_class: Gitlab.config.backup.upload.storage_class) - $progress.puts "done".color(:green) + progress.puts "done".color(:green) else puts "uploading backup to #{remote_directory} failed".color(:red) abort 'Backup failed' @@ -51,13 +57,13 @@ module Backup end def cleanup - $progress.print "Deleting tmp directories ... " + progress.print "Deleting tmp directories ... " backup_contents.each do |dir| next unless File.exist?(File.join(backup_path, dir)) if FileUtils.rm_rf(File.join(backup_path, dir)) - $progress.puts "done".color(:green) + progress.puts "done".color(:green) else puts "deleting tmp directory '#{dir}' failed".color(:red) abort 'Backup failed' @@ -67,7 +73,7 @@ module Backup def remove_old # delete backups - $progress.print "Deleting old backups ... " + progress.print "Deleting old backups ... " keep_time = Gitlab.config.backup.keep_time.to_i if keep_time > 0 @@ -88,31 +94,32 @@ module Backup FileUtils.rm(file) removed += 1 rescue => e - $progress.puts "Deleting #{file} failed: #{e.message}".color(:red) + progress.puts "Deleting #{file} failed: #{e.message}".color(:red) end end end end - $progress.puts "done. (#{removed} removed)".color(:green) + progress.puts "done. (#{removed} removed)".color(:green) else - $progress.puts "skipping".color(:yellow) + progress.puts "skipping".color(:yellow) end end + # rubocop: disable Metrics/AbcSize def unpack Dir.chdir(backup_path) do # check for existing backups in the backup dir if backup_file_list.empty? - $progress.puts "No backups found in #{backup_path}" - $progress.puts "Please make sure that file name ends with #{FILE_NAME_SUFFIX}" + progress.puts "No backups found in #{backup_path}" + progress.puts "Please make sure that file name ends with #{FILE_NAME_SUFFIX}" exit 1 elsif backup_file_list.many? && ENV["BACKUP"].nil? - $progress.puts 'Found more than one backup:' + progress.puts 'Found more than one backup:' # print list of available backups - $progress.puts " " + available_timestamps.join("\n ") - $progress.puts 'Please specify which one you want to restore:' - $progress.puts 'rake gitlab:backup:restore BACKUP=timestamp_of_backup' + progress.puts " " + available_timestamps.join("\n ") + progress.puts 'Please specify which one you want to restore:' + progress.puts 'rake gitlab:backup:restore BACKUP=timestamp_of_backup' exit 1 end @@ -123,31 +130,31 @@ module Backup end unless File.exist?(tar_file) - $progress.puts "The backup file #{tar_file} does not exist!" + progress.puts "The backup file #{tar_file} does not exist!" exit 1 end - $progress.print 'Unpacking backup ... ' + progress.print 'Unpacking backup ... ' unless Kernel.system(*%W(tar -xf #{tar_file})) - $progress.puts 'unpacking backup failed'.color(:red) + progress.puts 'unpacking backup failed'.color(:red) exit 1 else - $progress.puts 'done'.color(:green) + progress.puts 'done'.color(:green) end ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0 # restoring mismatching backups can lead to unexpected problems if settings[:gitlab_version] != Gitlab::VERSION - $progress.puts(<<~HEREDOC.color(:red)) + progress.puts(<<~HEREDOC.color(:red)) GitLab version mismatch: Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup! Please switch to the following version and try again: version: #{settings[:gitlab_version]} HEREDOC - $progress.puts - $progress.puts "Hint: git checkout v#{settings[:gitlab_version]}" + progress.puts + progress.puts "Hint: git checkout v#{settings[:gitlab_version]}" exit 1 end end diff --git a/lib/backup/pages.rb b/lib/backup/pages.rb index 5830b209d6e..542e35a7c7c 100644 --- a/lib/backup/pages.rb +++ b/lib/backup/pages.rb @@ -2,7 +2,11 @@ require 'backup/files' module Backup class Pages < Files - def initialize + attr_reader :progress + + def initialize(progress) + @progress = progress + super('pages', Gitlab.config.pages.path) end end diff --git a/lib/backup/registry.rb b/lib/backup/registry.rb index 91698669402..35821805797 100644 --- a/lib/backup/registry.rb +++ b/lib/backup/registry.rb @@ -2,7 +2,11 @@ require 'backup/files' module Backup class Registry < Files - def initialize + attr_reader :progress + + def initialize(progress) + @progress = progress + super('registry', Settings.registry.path) end end diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 65e06fd78c0..c3360c391af 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -6,6 +6,12 @@ module Backup include Backup::Helper # rubocop:disable Metrics/AbcSize + attr_reader :progress + + def initialize(progress) + @progress = progress + end + def dump prepare @@ -67,6 +73,9 @@ module Backup end def prepare_directories + # TODO: Need to find a way to do this for gitaly + # Gitaly discussion issue: https://gitlab.com/gitlab-org/gitaly/issues/1194 + Gitlab.config.repositories.storages.each do |name, repository_storage| path = repository_storage.legacy_disk_path next unless File.exist?(path) @@ -87,70 +96,65 @@ module Backup end end + def restore_custom_hooks(project) + # TODO: Need to find a way to do this for gitaly + # Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/1195 + in_path(path_to_tars(project)) do |dir| + path_to_project_repo = path_to_repo(project) + cmd = %W(tar -xf #{path_to_tars(project, dir)} -C #{path_to_project_repo} #{dir}) + + output, status = Gitlab::Popen.popen(cmd) + unless status.zero? + progress_warn(project, cmd.join(' '), output) + end + end + end + def restore prepare_directories + gitlab_shell = Gitlab::Shell.new Project.find_each(batch_size: 1000) do |project| - progress.print " * #{display_repo_path(project)} ... " - path_to_project_repo = path_to_repo(project) + progress.print " * #{project.full_path} ... " path_to_project_bundle = path_to_bundle(project) - project.ensure_storage_path_exists - cmd = if File.exist?(path_to_project_bundle) - %W(#{Gitlab.config.git.bin_path} clone --bare --mirror #{path_to_project_bundle} #{path_to_project_repo}) - else - %W(#{Gitlab.config.git.bin_path} init --bare #{path_to_project_repo}) - end + restore_repo_success = nil + if File.exist?(path_to_project_bundle) + begin + gitlab_shell.remove_repository(project.repository_storage, project.disk_path) if project.repository_exists? + project.repository.create_from_bundle path_to_project_bundle + restore_repo_success = true + rescue => e + restore_repo_success = false + progress.puts "Error: #{e}".color(:red) + end + else + restore_repo_success = gitlab_shell.create_repository(project.repository_storage, project.disk_path) + end - output, status = Gitlab::Popen.popen(cmd) - if status.zero? + if restore_repo_success progress.puts "[DONE]".color(:green) else - progress_warn(project, cmd.join(' '), output) + progress.puts "[Failed] restoring #{project.full_path} repository".color(:red) end - in_path(path_to_tars(project)) do |dir| - cmd = %W(tar -xf #{path_to_tars(project, dir)} -C #{path_to_project_repo} #{dir}) - - output, status = Gitlab::Popen.popen(cmd) - unless status.zero? - progress_warn(project, cmd.join(' '), output) - end - end + restore_custom_hooks(project) wiki = ProjectWiki.new(project) - path_to_wiki_repo = path_to_repo(wiki) path_to_wiki_bundle = path_to_bundle(wiki) if File.exist?(path_to_wiki_bundle) - progress.print " * #{display_repo_path(wiki)} ... " - - # If a wiki bundle exists, first remove the empty repo - # that was initialized with ProjectWiki.new() and then - # try to restore with 'git clone --bare'. - FileUtils.rm_rf(path_to_wiki_repo) - cmd = %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_wiki_bundle} #{path_to_wiki_repo}) - - output, status = Gitlab::Popen.popen(cmd) - if status.zero? - progress.puts " [DONE]".color(:green) - else - progress_warn(project, cmd.join(' '), output) + progress.print " * #{wiki.full_path} ... " + begin + gitlab_shell.remove_repository(wiki.repository_storage, wiki.disk_path) if wiki.repository_exists? + wiki.repository.create_from_bundle(path_to_wiki_bundle) + progress.puts "[DONE]".color(:green) + rescue => e + progress.puts "[Failed] restoring #{wiki.full_path} wiki".color(:red) + progress.puts "Error #{e}".color(:red) end end end - - progress.print 'Put GitLab hooks in repositories dirs'.color(:yellow) - cmd = %W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args - - output, status = Gitlab::Popen.popen(cmd) - if status.zero? - progress.puts " [DONE]".color(:green) - else - puts " [FAILED]".color(:red) - puts "failed: #{cmd}" - puts output - end end # rubocop:enable Metrics/AbcSize @@ -217,10 +221,6 @@ module Backup Gitlab.config.repositories.storages.values.map { |rs| rs.legacy_disk_path } end - def progress - $progress - end - def display_repo_path(project) project.hashed_storage?(:repository) ? "#{project.full_path} (#{project.disk_path})" : project.full_path end diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb index d46e2cd869d..49b117a7ee3 100644 --- a/lib/backup/uploads.rb +++ b/lib/backup/uploads.rb @@ -2,7 +2,11 @@ require 'backup/files' module Backup class Uploads < Files - def initialize + attr_reader :progress + + def initialize(progress) + @progress = progress + super('uploads', Rails.root.join('public/uploads')) end end diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb index 5325819d828..28933c78966 100644 --- a/lib/banzai/filter/plantuml_filter.rb +++ b/lib/banzai/filter/plantuml_filter.rb @@ -23,7 +23,7 @@ module Banzai private def settings - ApplicationSetting.current || ApplicationSetting.create_from_defaults + Gitlab::CurrentSettings.current_application_settings end def plantuml_setup diff --git a/lib/gitlab.rb b/lib/gitlab.rb index c5498d0da1a..a129746e2c6 100644 --- a/lib/gitlab.rb +++ b/lib/gitlab.rb @@ -9,11 +9,30 @@ module Gitlab Settings end + def self.migrations_hash + @_migrations_hash ||= Digest::MD5.hexdigest(ActiveRecord::Migrator.get_all_versions.to_s) + end + + def self.revision + @_revision ||= begin + if File.exist?(root.join("REVISION")) + File.read(root.join("REVISION")).strip.freeze + else + result = Gitlab::Popen.popen_with_detail(%W[#{config.git.bin_path} log --pretty=format:%h -n 1]) + + if result.status.success? + result.stdout.chomp.freeze + else + "Unknown".freeze + end + end + end + end + COM_URL = 'https://gitlab.com'.freeze APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))} SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z} VERSION = File.read(root.join("VERSION")).strip.freeze - REVISION = Gitlab::Popen.popen(%W(#{config.git.bin_path} log --pretty=format:%h -n 1)).first.chomp.freeze def self.com? # Check `gl_subdomain?` as well to keep parity with gitlab.com diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index e392a015b91..6cf7aa1bf0d 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -9,8 +9,8 @@ module Gitlab end end - def fake_application_settings(defaults = ::ApplicationSetting.defaults) - Gitlab::FakeApplicationSettings.new(defaults) + def fake_application_settings(attributes = {}) + Gitlab::FakeApplicationSettings.new(::ApplicationSetting.defaults.merge(attributes || {})) end def method_missing(name, *args, &block) @@ -25,43 +25,35 @@ module Gitlab def ensure_application_settings! return in_memory_application_settings if ENV['IN_MEMORY_APPLICATION_SETTINGS'] == 'true' - - cached_application_settings || uncached_application_settings - end - - def cached_application_settings - begin - ::ApplicationSetting.cached - rescue ::Redis::BaseError, ::Errno::ENOENT, ::Errno::EADDRNOTAVAIL - # In case Redis isn't running or the Redis UNIX socket file is not available - end - end - - def uncached_application_settings return fake_application_settings unless connect_to_db? - db_settings = ::ApplicationSetting.current - + current_settings = ::ApplicationSetting.current # If there are pending migrations, it's possible there are columns that # need to be added to the application settings. To prevent Rake tasks # and other callers from failing, use any loaded settings and return # defaults for missing columns. if ActiveRecord::Migrator.needs_migration? - defaults = ::ApplicationSetting.defaults - defaults.merge!(db_settings.attributes.symbolize_keys) if db_settings.present? - return fake_application_settings(defaults) + return fake_application_settings(current_settings&.attributes) end - return db_settings if db_settings.present? + return current_settings if current_settings.present? - ::ApplicationSetting.create_from_defaults || in_memory_application_settings + with_fallback_to_fake_application_settings do + ::ApplicationSetting.create_from_defaults || in_memory_application_settings + end end def in_memory_application_settings - @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting.defaults) # rubocop:disable Gitlab/ModuleWithInstanceVariables - rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError - # In case migrations the application_settings table is not created yet, - # we fallback to a simple OpenStruct + with_fallback_to_fake_application_settings do + @in_memory_application_settings ||= ::ApplicationSetting.build_from_defaults # rubocop:disable Gitlab/ModuleWithInstanceVariables + end + end + + def with_fallback_to_fake_application_settings(&block) + yield + rescue + # In case the application_settings table is not created yet, or if a new + # ApplicationSetting column is not yet migrated we fallback to a simple OpenStruct fake_application_settings end diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 05a60deb7d3..764f93f6d3d 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -47,7 +47,7 @@ module Gitlab project, author, title: mail.subject, - description: message + description: message_including_reply ).execute end end diff --git a/lib/gitlab/email/handler/reply_processing.rb b/lib/gitlab/email/handler/reply_processing.rb index da5ff350549..38b1425364f 100644 --- a/lib/gitlab/email/handler/reply_processing.rb +++ b/lib/gitlab/email/handler/reply_processing.rb @@ -16,8 +16,12 @@ module Gitlab @message ||= process_message end - def process_message - message = ReplyParser.new(mail).execute.strip + def message_including_reply + @message_with_reply ||= process_message(trim_reply: false) + end + + def process_message(**kwargs) + message = ReplyParser.new(mail, **kwargs).execute.strip add_attachments(message) end diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 01c28d051ee..ae6b84607d6 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -4,8 +4,9 @@ module Gitlab class ReplyParser attr_accessor :message - def initialize(message) + def initialize(message, trim_reply: true) @message = message + @trim_reply = trim_reply end def execute @@ -13,7 +14,9 @@ module Gitlab encoding = body.encoding - body = EmailReplyTrimmer.trim(body) + if @trim_reply + body = EmailReplyTrimmer.trim(body) + end return '' unless body diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb index 77af7a868d0..49bc9c0b671 100644 --- a/lib/gitlab/file_detector.rb +++ b/lib/gitlab/file_detector.rb @@ -14,7 +14,7 @@ module Gitlab avatar: /\Alogo\.(png|jpg|gif)\z/, issue_template: %r{\A\.gitlab/issue_templates/[^/]+\.md\z}, merge_request_template: %r{\A\.gitlab/merge_request_templates/[^/]+\.md\z}, - xcode_config: %r{\A[^/]*\.(xcodeproj|xcworkspace)\z}, + xcode_config: %r{\A[^/]*\.(xcodeproj|xcworkspace)(/.+)?\z}, # Configuration files gitignore: '.gitignore', diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb index 68373460d23..00c943fdb25 100644 --- a/lib/gitlab/git/gitlab_projects.rb +++ b/lib/gitlab/git/gitlab_projects.rb @@ -53,7 +53,7 @@ module Gitlab # Import project via git clone --bare # URL must be publicly cloneable def import_project(source, timeout) - Gitlab::GitalyClient.migrate(:import_repository) do |is_enabled| + Gitlab::GitalyClient.migrate(:import_repository, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_import_repository(source) else diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 2be604a5b13..1a21625a322 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1048,7 +1048,7 @@ module Gitlab return @info_attributes if @info_attributes content = - gitaly_migrate(:get_info_attributes) do |is_enabled| + gitaly_migrate(:get_info_attributes, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_repository_client.info_attributes else @@ -1334,7 +1334,7 @@ module Gitlab end def squash_in_progress?(squash_id) - gitaly_migrate(:squash_in_progress) do |is_enabled| + gitaly_migrate(:squash_in_progress, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_repository_client.squash_in_progress?(squash_id) else diff --git a/lib/gitlab/git/wiki.rb b/lib/gitlab/git/wiki.rb index d75a5f15c29..1ab8c4e0229 100644 --- a/lib/gitlab/git/wiki.rb +++ b/lib/gitlab/git/wiki.rb @@ -131,7 +131,7 @@ module Gitlab def page_formatted_data(title:, dir: nil, version: nil) version = version&.id - @repository.gitaly_migrate(:wiki_page_formatted_data) do |is_enabled| + @repository.gitaly_migrate(:wiki_page_formatted_data, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_wiki_client.get_formatted_data(title: title, dir: dir, version: version) else diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index c741dabe168..0d31934347f 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -15,7 +15,7 @@ module Gitlab gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class gon.sentry_dsn = Gitlab::CurrentSettings.clientside_sentry_dsn if Gitlab::CurrentSettings.clientside_sentry_enabled gon.gitlab_url = Gitlab.config.gitlab.url - gon.revision = Gitlab::REVISION + gon.revision = Gitlab.revision gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png') gon.sprite_icons = IconsHelper.sprite_icon_path gon.sprite_file_icons = IconsHelper.sprite_file_icons_path diff --git a/lib/gitlab/serializer/pagination.rb b/lib/gitlab/serializer/pagination.rb index 9c92b83dddc..6bb00d8ae21 100644 --- a/lib/gitlab/serializer/pagination.rb +++ b/lib/gitlab/serializer/pagination.rb @@ -17,8 +17,6 @@ module Gitlab end end - private - # Methods needed by `API::Helpers::Pagination` # diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 24e37f6c6cc..e96fbb64372 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -6,7 +6,6 @@ namespace :gitlab do desc "GitLab | Create a backup of the GitLab system" task create: :gitlab_environment do warn_user_is_not_gitlab - configure_cron_mode Rake::Task["gitlab:backup:db:create"].invoke Rake::Task["gitlab:backup:repo:create"].invoke @@ -17,7 +16,7 @@ namespace :gitlab do Rake::Task["gitlab:backup:lfs:create"].invoke Rake::Task["gitlab:backup:registry:create"].invoke - backup = Backup::Manager.new + backup = Backup::Manager.new(progress) backup.pack backup.cleanup backup.remove_old @@ -27,9 +26,8 @@ namespace :gitlab do desc 'GitLab | Restore a previously created backup' task restore: :gitlab_environment do warn_user_is_not_gitlab - configure_cron_mode - backup = Backup::Manager.new + backup = Backup::Manager.new(progress) backup.unpack unless backup.skipped?('db') @@ -49,9 +47,9 @@ namespace :gitlab do # Drop all tables Load the schema to ensure we don't have any newer tables # hanging out from a failed upgrade - $progress.puts 'Cleaning the database ... '.color(:blue) + progress.puts 'Cleaning the database ... '.color(:blue) Rake::Task['gitlab:db:drop_tables'].invoke - $progress.puts 'done'.color(:green) + progress.puts 'done'.color(:green) Rake::Task['gitlab:backup:db:restore'].invoke rescue Gitlab::TaskAbortedByUserError puts "Quitting...".color(:red) @@ -74,173 +72,173 @@ namespace :gitlab do namespace :repo do task create: :gitlab_environment do - $progress.puts "Dumping repositories ...".color(:blue) + progress.puts "Dumping repositories ...".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("repositories") - $progress.puts "[SKIPPED]".color(:cyan) + progress.puts "[SKIPPED]".color(:cyan) else - Backup::Repository.new.dump - $progress.puts "done".color(:green) + Backup::Repository.new(progress).dump + progress.puts "done".color(:green) end end task restore: :gitlab_environment do - $progress.puts "Restoring repositories ...".color(:blue) - Backup::Repository.new.restore - $progress.puts "done".color(:green) + progress.puts "Restoring repositories ...".color(:blue) + Backup::Repository.new(progress).restore + progress.puts "done".color(:green) end end namespace :db do task create: :gitlab_environment do - $progress.puts "Dumping database ... ".color(:blue) + progress.puts "Dumping database ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("db") - $progress.puts "[SKIPPED]".color(:cyan) + progress.puts "[SKIPPED]".color(:cyan) else - Backup::Database.new.dump - $progress.puts "done".color(:green) + Backup::Database.new(progress).dump + progress.puts "done".color(:green) end end task restore: :gitlab_environment do - $progress.puts "Restoring database ... ".color(:blue) - Backup::Database.new.restore - $progress.puts "done".color(:green) + progress.puts "Restoring database ... ".color(:blue) + Backup::Database.new(progress).restore + progress.puts "done".color(:green) end end namespace :builds do task create: :gitlab_environment do - $progress.puts "Dumping builds ... ".color(:blue) + progress.puts "Dumping builds ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("builds") - $progress.puts "[SKIPPED]".color(:cyan) + progress.puts "[SKIPPED]".color(:cyan) else - Backup::Builds.new.dump - $progress.puts "done".color(:green) + Backup::Builds.new(progress).dump + progress.puts "done".color(:green) end end task restore: :gitlab_environment do - $progress.puts "Restoring builds ... ".color(:blue) - Backup::Builds.new.restore - $progress.puts "done".color(:green) + progress.puts "Restoring builds ... ".color(:blue) + Backup::Builds.new(progress).restore + progress.puts "done".color(:green) end end namespace :uploads do task create: :gitlab_environment do - $progress.puts "Dumping uploads ... ".color(:blue) + progress.puts "Dumping uploads ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("uploads") - $progress.puts "[SKIPPED]".color(:cyan) + progress.puts "[SKIPPED]".color(:cyan) else - Backup::Uploads.new.dump - $progress.puts "done".color(:green) + Backup::Uploads.new(progress).dump + progress.puts "done".color(:green) end end task restore: :gitlab_environment do - $progress.puts "Restoring uploads ... ".color(:blue) - Backup::Uploads.new.restore - $progress.puts "done".color(:green) + progress.puts "Restoring uploads ... ".color(:blue) + Backup::Uploads.new(progress).restore + progress.puts "done".color(:green) end end namespace :artifacts do task create: :gitlab_environment do - $progress.puts "Dumping artifacts ... ".color(:blue) + progress.puts "Dumping artifacts ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("artifacts") - $progress.puts "[SKIPPED]".color(:cyan) + progress.puts "[SKIPPED]".color(:cyan) else - Backup::Artifacts.new.dump - $progress.puts "done".color(:green) + Backup::Artifacts.new(progress).dump + progress.puts "done".color(:green) end end task restore: :gitlab_environment do - $progress.puts "Restoring artifacts ... ".color(:blue) - Backup::Artifacts.new.restore - $progress.puts "done".color(:green) + progress.puts "Restoring artifacts ... ".color(:blue) + Backup::Artifacts.new(progress).restore + progress.puts "done".color(:green) end end namespace :pages do task create: :gitlab_environment do - $progress.puts "Dumping pages ... ".color(:blue) + progress.puts "Dumping pages ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("pages") - $progress.puts "[SKIPPED]".color(:cyan) + progress.puts "[SKIPPED]".color(:cyan) else - Backup::Pages.new.dump - $progress.puts "done".color(:green) + Backup::Pages.new(progress).dump + progress.puts "done".color(:green) end end task restore: :gitlab_environment do - $progress.puts "Restoring pages ... ".color(:blue) - Backup::Pages.new.restore - $progress.puts "done".color(:green) + progress.puts "Restoring pages ... ".color(:blue) + Backup::Pages.new(progress).restore + progress.puts "done".color(:green) end end namespace :lfs do task create: :gitlab_environment do - $progress.puts "Dumping lfs objects ... ".color(:blue) + progress.puts "Dumping lfs objects ... ".color(:blue) if ENV["SKIP"] && ENV["SKIP"].include?("lfs") - $progress.puts "[SKIPPED]".color(:cyan) + progress.puts "[SKIPPED]".color(:cyan) else - Backup::Lfs.new.dump - $progress.puts "done".color(:green) + Backup::Lfs.new(progress).dump + progress.puts "done".color(:green) end end task restore: :gitlab_environment do - $progress.puts "Restoring lfs objects ... ".color(:blue) - Backup::Lfs.new.restore - $progress.puts "done".color(:green) + progress.puts "Restoring lfs objects ... ".color(:blue) + Backup::Lfs.new(progress).restore + progress.puts "done".color(:green) end end namespace :registry do task create: :gitlab_environment do - $progress.puts "Dumping container registry images ... ".color(:blue) + progress.puts "Dumping container registry images ... ".color(:blue) if Gitlab.config.registry.enabled if ENV["SKIP"] && ENV["SKIP"].include?("registry") - $progress.puts "[SKIPPED]".color(:cyan) + progress.puts "[SKIPPED]".color(:cyan) else - Backup::Registry.new.dump - $progress.puts "done".color(:green) + Backup::Registry.new(progress).dump + progress.puts "done".color(:green) end else - $progress.puts "[DISABLED]".color(:cyan) + progress.puts "[DISABLED]".color(:cyan) end end task restore: :gitlab_environment do - $progress.puts "Restoring container registry images ... ".color(:blue) + progress.puts "Restoring container registry images ... ".color(:blue) if Gitlab.config.registry.enabled - Backup::Registry.new.restore - $progress.puts "done".color(:green) + Backup::Registry.new(progress).restore + progress.puts "done".color(:green) else - $progress.puts "[DISABLED]".color(:cyan) + progress.puts "[DISABLED]".color(:cyan) end end end - def configure_cron_mode + def progress if ENV['CRON'] # We need an object we can say 'puts' and 'print' to; let's use a # StringIO. require 'stringio' - $progress = StringIO.new + StringIO.new else - $progress = $stdout + $stdout end end end # namespace end: backup diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake index 47ed522aec3..289aa5d9060 100644 --- a/lib/tasks/gitlab/info.rake +++ b/lib/tasks/gitlab/info.rake @@ -47,7 +47,7 @@ namespace :gitlab do puts "" puts "GitLab information".color(:yellow) puts "Version:\t#{Gitlab::VERSION}" - puts "Revision:\t#{Gitlab::REVISION}" + puts "Revision:\t#{Gitlab.revision}" puts "Directory:\t#{Rails.root}" puts "DB Adapter:\t#{database_adapter}" puts "URL:\t\t#{Gitlab.config.gitlab.url}" diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake index c6204f89de4..9b05876034c 100644 --- a/lib/tasks/migrate/add_limits_mysql.rake +++ b/lib/tasks/migrate/add_limits_mysql.rake @@ -2,6 +2,7 @@ require Rails.root.join('db/migrate/limits_to_mysql') require Rails.root.join('db/migrate/markdown_cache_limits_to_mysql') require Rails.root.join('db/migrate/merge_request_diff_file_limits_to_mysql') require Rails.root.join('db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql') +require Rails.root.join('db/migrate/gpg_keys_limits_to_mysql') desc "GitLab | Add limits to strings in mysql database" task add_limits_mysql: :environment do @@ -10,4 +11,5 @@ task add_limits_mysql: :environment do MarkdownCacheLimitsToMysql.new.up MergeRequestDiffFileLimitsToMysql.new.up LimitsCiBuildTraceChunksRawDataForMysql.new.up + IncreaseMysqlTextLimitForGpgKeys.new.up end diff --git a/lib/tasks/migrate/composite_primary_keys.rake b/lib/tasks/migrate/composite_primary_keys.rake new file mode 100644 index 00000000000..eb112434dd9 --- /dev/null +++ b/lib/tasks/migrate/composite_primary_keys.rake @@ -0,0 +1,15 @@ +namespace :gitlab do + namespace :db do + desc 'GitLab | Adds primary keys to tables that only have composite unique keys' + task composite_primary_keys_add: :environment do + require Rails.root.join('db/optional_migrations/composite_primary_keys') + CompositePrimaryKeysMigration.new.up + end + + desc 'GitLab | Removes previously added composite primary keys' + task composite_primary_keys_drop: :environment do + require Rails.root.join('db/optional_migrations/composite_primary_keys') + CompositePrimaryKeysMigration.new.down + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 14228b18332..608d2a584ba 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-05-16 18:52+0200\n" -"PO-Revision-Date: 2018-05-16 18:52+0200\n" +"POT-Creation-Date: 2018-05-21 12:38-0700\n" +"PO-Revision-Date: 2018-05-21 12:38-0700\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "Language: \n" @@ -82,6 +82,9 @@ msgstr[1] "" msgid "%{loadingIcon} Started" msgstr "" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{nip_domain} can be used as an alternative to a custom domain." msgstr "" @@ -1447,7 +1450,7 @@ msgstr "" msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images." msgstr "" -msgid "ContainerRegistry|You can also %{deploy_token} for read-only access to the registry images." +msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access to the registry images." msgstr "" msgid "Continuous Integration and Deployment" @@ -2487,6 +2490,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock to current projects" msgstr "" @@ -3407,6 +3413,9 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Resolve conflicts on source branch" +msgstr "" + msgid "Resolve discussion" msgstr "" @@ -4278,6 +4287,9 @@ msgstr "" msgid "Toggle Sidebar" msgstr "" +msgid "Toggle discussion" +msgstr "" + msgid "Toggle sidebar" msgstr "" @@ -4599,12 +4611,21 @@ msgstr "" msgid "You can only edit files when you are on a branch" 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 "" + msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "" diff --git a/package.json b/package.json index 59b4988b264..13be495b702 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { - "@gitlab-org/gitlab-svgs": "^1.22.0", + "@gitlab-org/gitlab-svgs": "^1.23.0", "autosize": "^4.0.0", "axios": "^0.17.1", "babel-core": "^6.26.3", @@ -25,8 +25,9 @@ "babel-preset-latest": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "blackst0ne-mermaid": "^7.1.0-fixed", - "bootstrap-sass": "^3.3.6", + "bootstrap": "4.1", "brace-expansion": "^1.1.8", + "cache-loader": "^1.2.2", "chart.js": "1.0.2", "classlist-polyfill": "^1.2.0", "clipboard": "^1.7.1", @@ -65,6 +66,7 @@ "monaco-editor": "0.10.0", "mousetrap": "^1.4.6", "pikaday": "^1.6.1", + "popper.js": "^1.14.3", "prismjs": "^1.6.0", "raphael": "^2.2.7", "raven-js": "^3.22.1", @@ -73,6 +75,7 @@ "select2": "3.5.2-browserify", "sha1": "^1.1.1", "sql.js": "^0.4.0", + "stickyfilljs": "^2.0.5", "style-loader": "^0.21.0", "svg4everybody": "2.1.9", "three": "^0.84.0", @@ -83,7 +86,7 @@ "url-loader": "^1.0.1", "visibilityjs": "^1.2.4", "vue": "^2.5.16", - "vue-loader": "^14.1.1", + "vue-loader": "^15.2.0", "vue-resource": "^1.5.0", "vue-router": "^3.0.1", "vue-template-compiler": "^2.5.16", diff --git a/qa/qa/page/menu/main.rb b/qa/qa/page/menu/main.rb index d3562effaab..644fedecc90 100644 --- a/qa/qa/page/menu/main.rb +++ b/qa/qa/page/menu/main.rb @@ -10,7 +10,7 @@ module QA view 'app/views/layouts/header/_default.html.haml' do element :navbar element :user_avatar - element :user_menu, '.dropdown-menu-nav' + element :user_menu, '.dropdown-menu' end view 'app/views/layouts/nav/_dashboard.html.haml' do @@ -70,7 +70,7 @@ module QA within_top_menu do click_element :user_avatar - page.within('.dropdown-menu-nav') do + page.within('.dropdown-menu') do yield end end diff --git a/scripts/no-ee-check b/scripts/no-ee-check new file mode 100755 index 00000000000..29d319dc822 --- /dev/null +++ b/scripts/no-ee-check @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +ee_path = File.join(File.expand_path(__dir__), '../ee') + +if Dir.exist?(ee_path) + puts 'The repository contains /ee directory. There should be no /ee directory in CE repo.' + exit 1 +end diff --git a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb index 2d7647a6e12..397cc79bde4 100644 --- a/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb +++ b/spec/controllers/projects/merge_requests/conflicts_controller_spec.rb @@ -5,7 +5,7 @@ describe Projects::MergeRequests::ConflictsController do let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request_with_conflicts) do - create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr| + create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr| mr.mark_as_unmergeable end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index c8cc6b374f6..d3042be9e8b 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -7,7 +7,7 @@ describe Projects::MergeRequestsController do let(:user) { project.owner } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } let(:merge_request_with_conflicts) do - create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project) do |mr| + create(:merge_request, source_branch: 'conflict-resolvable', target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr| mr.mark_as_unmergeable end end diff --git a/spec/controllers/projects/prometheus/metrics_controller_spec.rb b/spec/controllers/projects/prometheus/metrics_controller_spec.rb index b2b245dba90..871dcf5c796 100644 --- a/spec/controllers/projects/prometheus/metrics_controller_spec.rb +++ b/spec/controllers/projects/prometheus/metrics_controller_spec.rb @@ -12,44 +12,67 @@ describe Projects::Prometheus::MetricsController do end describe 'GET #active_common' do - before do - allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter) - end + context 'when prometheus_adapter can query' do + before do + allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter) + end - context 'when prometheus metrics are enabled' do - context 'when data is not present' do - before do - allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({}) - end + context 'when prometheus metrics are enabled' do + context 'when data is not present' do + before do + allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({}) + end - it 'returns no content response' do - get :active_common, project_params(format: :json) + it 'returns no content response' do + get :active_common, project_params(format: :json) - expect(response).to have_gitlab_http_status(204) + expect(response).to have_gitlab_http_status(204) + end end - end - context 'when data is available' do - let(:sample_response) { { some_data: 1 } } + context 'when data is available' do + let(:sample_response) { { some_data: 1 } } + + before do + allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return(sample_response) + end - before do - allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return(sample_response) + it 'returns no content response' do + get :active_common, project_params(format: :json) + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to eq(sample_response.deep_stringify_keys) + end end - it 'returns no content response' do - get :active_common, project_params(format: :json) + context 'when requesting non json response' do + it 'returns not found response' do + get :active_common, project_params - expect(response).to have_gitlab_http_status(200) - expect(json_response).to eq(sample_response.deep_stringify_keys) + expect(response).to have_gitlab_http_status(404) + end end end + end - context 'when requesting non json response' do - it 'returns not found response' do - get :active_common, project_params + context 'when prometheus_adapter cannot query' do + it 'renders 404' do + prometheus_adapter = double('prometheus_adapter', can_query?: false) - expect(response).to have_gitlab_http_status(404) - end + allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter) + allow(prometheus_adapter).to receive(:query).with(:matched_metrics).and_return({}) + + get :active_common, project_params(format: :json) + + expect(response).to have_gitlab_http_status(404) + end + end + + context 'when prometheus_adapter is disabled' do + it 'renders 404' do + get :active_common, project_params(format: :json) + + expect(response).to have_gitlab_http_status(404) end end end diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb index 766cd4b0090..d8fcdebfc6d 100644 --- a/spec/features/admin/admin_abuse_reports_spec.rb +++ b/spec/features/admin/admin_abuse_reports_spec.rb @@ -45,7 +45,7 @@ describe "Admin::AbuseReports", :js do visit admin_abuse_reports_path expect(page).to have_selector('.pagination') - expect(page).to have_selector('.pagination .page', count: (report_count.to_f / AbuseReport.default_per_page).ceil) + expect(page).to have_selector('.pagination .js-pagination-page', count: (report_count.to_f / AbuseReport.default_per_page).ceil) end end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 6d8350e99f1..328e8f25f89 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -34,7 +34,7 @@ describe "Admin::Projects" do expect(page).to have_content(project.name) expect(page).to have_content(archived_project.name) - expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') + expect(page).to have_xpath("//span[@class='badge badge-warning']", text: 'archived') end it 'renders only archived projects', :js do diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb index 380cd5d7703..2503fc9067d 100644 --- a/spec/features/admin/admin_requests_profiles_spec.rb +++ b/spec/features/admin/admin_requests_profiles_spec.rb @@ -33,12 +33,12 @@ describe 'Admin::RequestsProfilesController' do visit admin_requests_profiles_path - within('.panel', text: '/gitlab-org/gitlab-ce') do + within('.card', text: '/gitlab-org/gitlab-ce') do expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile1)}']", text: time1.to_s(:long)) expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile2)}']", text: time2.to_s(:long)) end - within('.panel', text: '/gitlab-com/infrastructure') do + within('.card', text: '/gitlab-com/infrastructure') do expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile3)}']", text: time3.to_s(:long)) end end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 3465ccfc423..c33014cbb31 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -68,7 +68,7 @@ describe "Admin Runners" do visit admin_runners_path within "#runner_#{runner.id}" do - expect(page).to have_selector '.label', text: 'group' + expect(page).to have_selector '.badge', text: 'group' expect(page).to have_text 'n/a' end end @@ -81,7 +81,7 @@ describe "Admin Runners" do visit admin_runners_path within "#runner_#{runner.id}" do - expect(page).to have_selector '.label', text: 'shared' + expect(page).to have_selector '.badge', text: 'shared' expect(page).to have_text 'n/a' end end @@ -95,7 +95,7 @@ describe "Admin Runners" do visit admin_runners_path within "#runner_#{runner.id}" do - expect(page).to have_selector '.label', text: 'specific' + expect(page).to have_selector '.badge', text: 'specific' expect(page).to have_text '1' end end diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index f2f9b734c39..dc025d82937 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -152,7 +152,7 @@ feature 'Admin updates settings' do scenario 'Change CI/CD settings' do page.within('.as-ci-cd') do - check 'Enabled Auto DevOps (Beta) for projects by default' + check 'Enabled Auto DevOps for projects by default' fill_in 'Auto devops domain', with: 'domain.com' click_button 'Save changes' end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 8fc57f4b4c3..9e3221577c7 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -283,14 +283,14 @@ describe "Admin::Users" do end it "lists group projects" do - within(:css, '.append-bottom-default + .panel') do + within(:css, '.append-bottom-default + .card') do expect(page).to have_content 'Group projects' expect(page).to have_link group.name, href: admin_group_path(group) end end it 'allows navigation to the group details' do - within(:css, '.append-bottom-default + .panel') do + within(:css, '.append-bottom-default + .card') do click_link group.name end within(:css, 'h3.page-title') do @@ -300,7 +300,7 @@ describe "Admin::Users" do end it 'shows the group access level' do - within(:css, '.append-bottom-default + .panel') do + within(:css, '.append-bottom-default + .card') do expect(page).to have_content 'Developer' end end diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb index 18901a954cc..7a14a441088 100644 --- a/spec/features/boards/add_issues_modal_spec.rb +++ b/spec/features/boards/add_issues_modal_spec.rb @@ -81,7 +81,7 @@ describe 'Issue Boards add issue modal', :js do expect(page).to have_content('2') end - expect(page).to have_selector('.card', count: 2) + expect(page).to have_selector('.board-card', count: 2) end end @@ -89,7 +89,7 @@ describe 'Issue Boards add issue modal', :js do page.within('.add-issues-modal') do click_link 'Selected issues' - expect(page).not_to have_selector('.card') + expect(page).not_to have_selector('.board-card') end end @@ -122,7 +122,7 @@ describe 'Issue Boards add issue modal', :js do wait_for_requests - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -133,7 +133,7 @@ describe 'Issue Boards add issue modal', :js do wait_for_requests - expect(page).not_to have_selector('.card') + expect(page).not_to have_selector('.board-card') expect(page).not_to have_content("You haven't added any issues to your project yet") end end @@ -142,7 +142,7 @@ describe 'Issue Boards add issue modal', :js do context 'selecing issues' do it 'selects single issue' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click page.within('.nav-links') do expect(page).to have_content('Selected issues 1') @@ -152,7 +152,7 @@ describe 'Issue Boards add issue modal', :js do it 'changes button text' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click expect(first('.add-issues-footer .btn')).to have_content('Add 1 issue') end @@ -160,7 +160,7 @@ describe 'Issue Boards add issue modal', :js do it 'changes button text with plural' do page.within('.add-issues-modal') do - all('.card .card-number').each do |el| + all('.board-card .board-card-number').each do |el| el.click end @@ -170,11 +170,11 @@ describe 'Issue Boards add issue modal', :js do it 'shows only selected issues on selected tab' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click click_link 'Selected issues' - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -200,7 +200,7 @@ describe 'Issue Boards add issue modal', :js do it 'selects all that arent already selected' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click expect(page).to have_selector('.is-active', count: 1) @@ -212,11 +212,11 @@ describe 'Issue Boards add issue modal', :js do it 'unselects from selected tab' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click click_link 'Selected issues' - first('.card .card-number').click + first('.board-card .board-card-number').click expect(page).not_to have_selector('.is-active') end @@ -226,19 +226,19 @@ describe 'Issue Boards add issue modal', :js do context 'adding issues' do it 'adds to board' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click click_button 'Add 1 issue' end page.within(find('.board:nth-child(2)')) do - expect(page).to have_selector('.card') + expect(page).to have_selector('.board-card') end end it 'adds to second list' do page.within('.add-issues-modal') do - first('.card .card-number').click + first('.board-card .board-card-number').click click_button planning.title @@ -248,7 +248,7 @@ describe 'Issue Boards add issue modal', :js do end page.within(find('.board:nth-child(3)')) do - expect(page).to have_selector('.card') + expect(page).to have_selector('.board-card') end end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 52ff47d57f9..e414345ac23 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -92,9 +92,9 @@ describe 'Issue Boards', :js do wait_for_requests expect(page).to have_selector('.board', count: 4) - expect(find('.board:nth-child(2)')).to have_selector('.card') - expect(find('.board:nth-child(3)')).to have_selector('.card') - expect(find('.board:nth-child(4)')).to have_selector('.card') + expect(find('.board:nth-child(2)')).to have_selector('.board-card') + expect(find('.board:nth-child(3)')).to have_selector('.board-card') + expect(find('.board:nth-child(4)')).to have_selector('.board-card') end it 'shows description tooltip on list title' do @@ -120,9 +120,9 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 0) - expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0) - expect(find('.board:nth-child(4)')).to have_selector('.card', count: 1) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 0) + expect(find('.board:nth-child(3)')).to have_selector('.board-card', count: 0) + expect(find('.board:nth-child(4)')).to have_selector('.board-card', count: 1) end it 'search list' do @@ -131,9 +131,9 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 1) - expect(find('.board:nth-child(3)')).to have_selector('.card', count: 0) - expect(find('.board:nth-child(4)')).to have_selector('.card', count: 0) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 1) + expect(find('.board:nth-child(3)')).to have_selector('.board-card', count: 0) + expect(find('.board:nth-child(4)')).to have_selector('.board-card', count: 0) end it 'allows user to delete board' do @@ -171,21 +171,21 @@ describe 'Issue Boards', :js do page.within(find('.board:nth-child(2)')) do expect(page.find('.board-header')).to have_content('58') - expect(page).to have_selector('.card', count: 20) + expect(page).to have_selector('.board-card', count: 20) expect(page).to have_content('Showing 20 of 58 issues') find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") wait_for_requests - expect(page).to have_selector('.card', count: 40) + expect(page).to have_selector('.board-card', count: 40) expect(page).to have_content('Showing 40 of 58 issues') find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") wait_for_requests - expect(page).to have_selector('.card', count: 58) + expect(page).to have_selector('.board-card', count: 58) expect(page).to have_content('Showing all issues') end end @@ -204,7 +204,7 @@ describe 'Issue Boards', :js do wait_for_board_cards(4, 2) expect(find('.board:nth-child(2)')).not_to have_content(issue9.title) - expect(find('.board:nth-child(4)')).to have_selector('.card', count: 2) + expect(find('.board:nth-child(4)')).to have_selector('.board-card', count: 2) expect(find('.board:nth-child(4)')).to have_content(issue9.title) expect(find('.board:nth-child(4)')).not_to have_content(planning.title) end @@ -242,7 +242,7 @@ describe 'Issue Boards', :js do wait_for_board_cards(4, 1) expect(find('.board:nth-child(3)')).to have_content(issue6.title) - expect(find('.board:nth-child(3)').all('.card').last).to have_content(development.title) + expect(find('.board:nth-child(3)').all('.board-card').last).to have_content(development.title) end it 'issue moves between lists' do @@ -253,7 +253,7 @@ describe 'Issue Boards', :js do wait_for_board_cards(4, 1) expect(find('.board:nth-child(2)')).to have_content(issue7.title) - expect(find('.board:nth-child(2)').all('.card').first).to have_content(planning.title) + expect(find('.board:nth-child(2)').all('.board-card').first).to have_content(planning.title) end it 'issue moves from closed' do @@ -335,7 +335,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(page).to have_css('#js-add-list.open') + expect(page).to have_css('#js-add-list.show') end it 'creates new list from a new label' do @@ -425,12 +425,12 @@ describe 'Issue Boards', :js do page.within(find('.board:nth-child(2)')) do expect(page.find('.board-header')).to have_content('1') - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end page.within(find('.board:nth-child(3)')) do expect(page.find('.board-header')).to have_content('0') - expect(page).to have_selector('.card', count: 0) + expect(page).to have_selector('.board-card', count: 0) end end @@ -460,19 +460,19 @@ describe 'Issue Boards', :js do page.within(find('.board:nth-child(2)')) do expect(page.find('.board-header')).to have_content('51') - expect(page).to have_selector('.card', count: 20) + expect(page).to have_selector('.board-card', count: 20) expect(page).to have_content('Showing 20 of 51 issues') find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") - expect(page).to have_selector('.card', count: 40) + expect(page).to have_selector('.board-card', count: 40) expect(page).to have_content('Showing 40 of 51 issues') find('.board .board-list') evaluate_script("document.querySelectorAll('.board .board-list')[1].scrollTop = document.querySelectorAll('.board .board-list')[1].scrollHeight") - expect(page).to have_selector('.card', count: 51) + expect(page).to have_selector('.board-card', count: 51) expect(page).to have_content('Showing all issues') end end @@ -494,8 +494,8 @@ describe 'Issue Boards', :js do it 'filters by clicking label button on issue' do page.within(find('.board:nth-child(2)')) do - expect(page).to have_selector('.card', count: 8) - expect(find('.card', match: :first)).to have_content(bug.title) + expect(page).to have_selector('.board-card', count: 8) + expect(find('.board-card', match: :first)).to have_content(bug.title) click_button(bug.title) wait_for_requests end @@ -512,13 +512,13 @@ describe 'Issue Boards', :js do it 'removes label filter by clicking label button on issue' do page.within(find('.board:nth-child(2)')) do - page.within(find('.card', match: :first)) do + page.within(find('.board-card', match: :first)) do click_button(bug.title) end wait_for_requests - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end wait_for_requests @@ -589,7 +589,7 @@ describe 'Issue Boards', :js do def wait_for_board_cards(board_number, expected_cards) page.within(find(".board:nth-child(#{board_number})")) do expect(page.find('.board-header')).to have_content(expected_cards.to_s) - expect(page).to have_selector('.card', count: expected_cards) + expect(page).to have_selector('.board-card', count: expected_cards) end end diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb index 5abd02dbb48..193b1dfabbd 100644 --- a/spec/features/boards/issue_ordering_spec.rb +++ b/spec/features/boards/issue_ordering_spec.rb @@ -30,7 +30,7 @@ describe 'Issue Boards', :js do it 'has un-ordered issue as last issue' do page.within(find('.board:nth-child(2)')) do - expect(all('.card').last).to have_content(issue4.title) + expect(all('.board-card').last).to have_content(issue4.title) end end @@ -40,7 +40,7 @@ describe 'Issue Boards', :js do wait_for_requests page.within(find('.board:nth-child(2)')) do - expect(first('.card')).to have_content(issue4.title) + expect(first('.board-card')).to have_content(issue4.title) end end end @@ -58,7 +58,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(first('.card')).to have_content(issue2.title) + expect(first('.board-card')).to have_content(issue2.title) end it 'moves from middle to bottom' do @@ -66,7 +66,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(all('.card').last).to have_content(issue2.title) + expect(all('.board-card').last).to have_content(issue2.title) end it 'moves from top to bottom' do @@ -74,7 +74,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(all('.card').last).to have_content(issue3.title) + expect(all('.board-card').last).to have_content(issue3.title) end it 'moves from bottom to top' do @@ -82,7 +82,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(first('.card')).to have_content(issue1.title) + expect(first('.board-card')).to have_content(issue1.title) end it 'moves from top to middle' do @@ -90,7 +90,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(first('.card')).to have_content(issue2.title) + expect(first('.board-card')).to have_content(issue2.title) end it 'moves from bottom to middle' do @@ -98,7 +98,7 @@ describe 'Issue Boards', :js do wait_for_requests - expect(all('.card').last).to have_content(issue2.title) + expect(all('.board-card').last).to have_content(issue2.title) end end @@ -121,11 +121,11 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2) - expect(all('.board')[2]).to have_selector('.card', count: 4) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 2) + expect(all('.board')[2]).to have_selector('.board-card', count: 4) page.within(all('.board')[2]) do - expect(first('.card')).to have_content(issue3.title) + expect(first('.board-card')).to have_content(issue3.title) end end @@ -134,11 +134,11 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2) - expect(all('.board')[2]).to have_selector('.card', count: 4) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 2) + expect(all('.board')[2]).to have_selector('.board-card', count: 4) page.within(all('.board')[2]) do - expect(all('.card').last).to have_content(issue3.title) + expect(all('.board-card').last).to have_content(issue3.title) end end @@ -147,11 +147,11 @@ describe 'Issue Boards', :js do wait_for_requests - expect(find('.board:nth-child(2)')).to have_selector('.card', count: 2) - expect(all('.board')[2]).to have_selector('.card', count: 4) + expect(find('.board:nth-child(2)')).to have_selector('.board-card', count: 2) + expect(all('.board')[2]).to have_selector('.board-card', count: 4) page.within(all('.board')[2]) do - expect(all('.card')[1]).to have_content(issue3.title) + expect(all('.board-card')[1]).to have_content(issue3.title) end end end diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index 5907bb0840f..be9c6a51c29 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -38,7 +38,7 @@ describe 'Issue Boards add issue modal filtering', :js do page.within('.add-issues-modal') do wait_for_requests - expect(page).to have_selector('.card', count: 0) + expect(page).to have_selector('.board-card', count: 0) click_button 'Cancel' end @@ -48,7 +48,7 @@ describe 'Issue Boards add issue modal filtering', :js do page.within('.add-issues-modal') do wait_for_requests - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -62,13 +62,13 @@ describe 'Issue Boards add issue modal filtering', :js do page.within('.add-issues-modal') do wait_for_requests - expect(page).to have_selector('.card', count: 0) + expect(page).to have_selector('.board-card', count: 0) find('.clear-search').click wait_for_requests - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -90,7 +90,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: user2.name) - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end end @@ -113,7 +113,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: 'none') - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -126,7 +126,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: user2.name) - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end end @@ -148,7 +148,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: 'upcoming') - expect(page).to have_selector('.card', count: 0) + expect(page).to have_selector('.board-card', count: 0) end end @@ -161,7 +161,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: milestone.name) - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end end @@ -183,7 +183,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: 'none') - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -196,7 +196,7 @@ describe 'Issue Boards add issue modal filtering', :js do wait_for_requests expect(page).to have_selector('.js-visual-token', text: label.title) - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end end diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index e880f0096c1..7a95f5cf871 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -64,7 +64,7 @@ describe 'Issue Boards new issue', :js do expect(page).to have_content('1') end - page.within(first('.card')) do + page.within(first('.board-card')) do issue = project.issues.find_by_title('bug') expect(page).to have_content(issue.to_reference) diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 4d31123a699..a03aa681827 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -15,7 +15,7 @@ describe 'Issue Boards', :js do let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) } let(:board) { create(:board, project: project) } let!(:list) { create(:list, board: board, label: development, position: 0) } - let(:card) { find('.board:nth-child(2)').first('.card') } + let(:card) { find('.board:nth-child(2)').first('.board-card') } around do |example| Timecop.freeze { example.run } @@ -75,7 +75,7 @@ describe 'Issue Boards', :js do wait_for_requests page.within(find('.board:nth-child(2)')) do - expect(page).to have_selector('.card', count: 1) + expect(page).to have_selector('.board-card', count: 1) end end @@ -86,11 +86,11 @@ describe 'Issue Boards', :js do visit project_board_path(project, board) wait_for_requests - click_card(find('.board:nth-child(1)').first('.card')) + click_card(find('.board:nth-child(1)').first('.board-card')) expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board' - click_card(find('.board:nth-child(3)').first('.card')) + click_card(find('.board:nth-child(3)').first('.board-card')) expect(find('.issue-boards-sidebar')).not_to have_button 'Remove from board' end @@ -117,7 +117,7 @@ describe 'Issue Boards', :js do end it 'removes the assignee' do - card_two = find('.board:nth-child(2)').find('.card:nth-child(2)') + card_two = find('.board:nth-child(2)').find('.board-card:nth-child(2)') click_card(card_two) page.within('.assignee') do @@ -171,7 +171,7 @@ describe 'Issue Boards', :js do end page.within(find('.board:nth-child(2)')) do - find('.card:nth-child(2)').click + find('.board-card:nth-child(2)').click end page.within('.assignee') do @@ -246,7 +246,7 @@ describe 'Issue Boards', :js do wait_for_requests page.within('.value') do - expect(page).to have_selector('.label', count: 2) + expect(page).to have_selector('.badge', count: 2) expect(page).to have_content(development.title) expect(page).to have_content(stretch.title) end @@ -268,12 +268,12 @@ describe 'Issue Boards', :js do find('.dropdown-menu-close-icon').click page.within('.value') do - expect(page).to have_selector('.label', count: 3) + expect(page).to have_selector('.badge', count: 3) expect(page).to have_content(bug.title) end end - expect(card).to have_selector('.label', count: 3) + expect(card).to have_selector('.badge', count: 3) expect(card).to have_content(bug.title) end @@ -293,13 +293,13 @@ describe 'Issue Boards', :js do find('.dropdown-menu-close-icon').click page.within('.value') do - expect(page).to have_selector('.label', count: 4) + expect(page).to have_selector('.badge', count: 4) expect(page).to have_content(bug.title) expect(page).to have_content(regression.title) end end - expect(card).to have_selector('.label', count: 4) + expect(card).to have_selector('.badge', count: 4) expect(card).to have_content(bug.title) expect(card).to have_content(regression.title) end @@ -321,12 +321,12 @@ describe 'Issue Boards', :js do find('.dropdown-menu-close-icon').click page.within('.value') do - expect(page).to have_selector('.label', count: 1) + expect(page).to have_selector('.badge', count: 1) expect(page).not_to have_content(stretch.title) end end - expect(card).to have_selector('.label', count: 1) + expect(card).to have_selector('.badge', count: 1) expect(card).not_to have_content(stretch.title) end diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb index 5fdb8044db2..271c610dcc8 100644 --- a/spec/features/boards/sub_group_project_spec.rb +++ b/spec/features/boards/sub_group_project_spec.rb @@ -20,7 +20,7 @@ describe 'Sub-group project issue boards', :js do end it 'creates new label from sidebar' do - find('.card').click + find('.board-card').click page.within '.labels' do click_link 'Edit' diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index 94133c62b5c..5ed20b02a6e 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -246,7 +246,7 @@ feature 'Dashboard Todos' do it 'is has the right number of pages' do visit dashboard_todos_path - expect(page).to have_selector('.gl-pagination .page', count: 2) + expect(page).to have_selector('.gl-pagination .js-pagination-page', count: 2) end describe 'mark all as done', :js do diff --git a/spec/features/explore/new_menu_spec.rb b/spec/features/explore/new_menu_spec.rb index 8d5233d0c0f..2516db5422f 100644 --- a/spec/features/explore/new_menu_spec.rb +++ b/spec/features/explore/new_menu_spec.rb @@ -66,7 +66,7 @@ feature 'Top Plus Menu', :js do page.within '.header-content' do find('.header-new-dropdown-toggle').click - expect(page).to have_selector('.header-new.dropdown.open', count: 1) + expect(page).to have_selector('.header-new.dropdown.show', count: 1) find('.header-new-project-snippet a').click end @@ -88,7 +88,7 @@ feature 'Top Plus Menu', :js do page.within '.header-content' do find('.header-new-dropdown-toggle').click - expect(page).to have_selector('.header-new.dropdown.open', count: 1) + expect(page).to have_selector('.header-new.dropdown.show', count: 1) find('.header-new-group-project a').click end @@ -156,7 +156,7 @@ feature 'Top Plus Menu', :js do def click_topmenuitem(item_name) page.within '.header-content' do find('.header-new-dropdown-toggle').click - expect(page).to have_selector('.header-new.dropdown.open', count: 1) + expect(page).to have_selector('.header-new.dropdown.show', count: 1) click_link item_name end end diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb index 31fbbcf562c..e7efdf7dfef 100644 --- a/spec/features/groups/members/search_members_spec.rb +++ b/spec/features/groups/members/search_members_spec.rb @@ -22,7 +22,7 @@ describe 'Search group member' do find('.member-search-btn').click end - group_members_list = find(".panel .content-list") + group_members_list = find(".card .content-list") expect(group_members_list).to have_content(member.name) expect(group_members_list).not_to have_content(user.name) end diff --git a/spec/features/groups/settings/group_badges_spec.rb b/spec/features/groups/settings/group_badges_spec.rb index 92217294446..a99da4a119b 100644 --- a/spec/features/groups/settings/group_badges_spec.rb +++ b/spec/features/groups/settings/group_badges_spec.rb @@ -21,7 +21,7 @@ feature 'Group Badges' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 expect(rows[0]).to have_content badge_1.link_url expect(rows[1]).to have_content badge_2.link_url @@ -48,7 +48,7 @@ feature 'Group Badges' do click_button 'Add badge' wait_for_requests - within '.panel-body' do + within '.card-body' do expect(find('a')[:href]).to eq badge_link_url expect(find('a img')[:src]).to eq badge_image_url end @@ -60,7 +60,7 @@ feature 'Group Badges' do it 'form is shown when clicking edit button in list' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 rows[1].find('[aria-label="Edit"]').click @@ -74,7 +74,7 @@ feature 'Group Badges' do it 'updates a badge when submitting the edit form' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 rows[1].find('[aria-label="Edit"]').click within 'form' do @@ -85,7 +85,7 @@ feature 'Group Badges' do wait_for_requests end - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 expect(rows[1]).to have_content badge_link_url end @@ -99,7 +99,7 @@ feature 'Group Badges' do it 'shows a modal when deleting a badge' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 click_delete_button(rows[1]) @@ -109,14 +109,14 @@ feature 'Group Badges' do it 'deletes a badge when confirming the modal' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 click_delete_button(rows[1]) find('.modal .btn-danger').click wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 1 expect(rows[0]).to have_content badge_1.link_url end diff --git a/spec/features/groups/user_browse_projects_group_page_spec.rb b/spec/features/groups/user_browse_projects_group_page_spec.rb index e81c3180e78..916363c41dd 100644 --- a/spec/features/groups/user_browse_projects_group_page_spec.rb +++ b/spec/features/groups/user_browse_projects_group_page_spec.rb @@ -21,7 +21,7 @@ describe 'User browse group projects page' do visit projects_group_path(group) expect(page).to have_link project.name - expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') + expect(page).to have_xpath("//span[@class='badge badge-warning']", text: 'archived') end end end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index 08ba91a2682..483122ae463 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -248,7 +248,7 @@ describe 'Filter issues', :js do context 'issue label clicked' do it 'filters and displays in search bar' do - find('.issues-list .issue .issue-main-info .issuable-info a .label', text: multiple_words_label.title).click + find('.issues-list .issue .issue-main-info .issuable-info a .badge', text: multiple_words_label.title).click expect_issues_list_count(1) expect_tokens([label_token("\"#{multiple_words_label.title}\"")]) diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index 539d7e9ff01..3dfcbc2fcb8 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -139,8 +139,8 @@ describe 'User creates branch and merge request on issue page', :js do end it 'disables the create branch button' do - expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hide)') - expect(page).to have_css('.create-mr-dropdown-wrap .available.hide', visible: false) + expect(page).to have_css('.create-mr-dropdown-wrap .unavailable:not(.hidden)') + expect(page).to have_css('.create-mr-dropdown-wrap .available.hidden', visible: false) expect(page).to have_content /1 Related Merge Request/ end end diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb index ae41f611ddc..4700ada1aae 100644 --- a/spec/features/labels_hierarchy_spec.rb +++ b/spec/features/labels_hierarchy_spec.rb @@ -34,7 +34,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - expect(page).to have_selector('span.label', text: label.title) + expect(page).to have_selector('span.badge', text: label.title) end end @@ -45,7 +45,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - expect(page).not_to have_selector('span.label', text: child_group_label.title) + expect(page).not_to have_selector('span.badge', text: child_group_label.title) end end @@ -57,7 +57,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests if board - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue.title) end else @@ -96,11 +96,11 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests if board - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue.title) end - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue_2.title) end else @@ -118,11 +118,11 @@ feature 'Labels Hierarchy', :js, :nested_groups do select_label_on_dropdown(group_label_3.title) if board - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).not_to have_selector('a', text: labeled_issue_2.title) end - expect(page).to have_selector('.card-title') do |card| + expect(page).to have_selector('.board-card-title') do |card| expect(card).to have_selector('a', text: labeled_issue_3.title) end else @@ -159,9 +159,9 @@ feature 'Labels Hierarchy', :js, :nested_groups do find('.btn-create').click expect(page.find('.issue-details h2.title')).to have_content('new created issue') - expect(page).to have_selector('span.label', text: grandparent_group_label.title) - expect(page).to have_selector('span.label', text: parent_group_label.title) - expect(page).to have_selector('span.label', text: project_label_1.title) + expect(page).to have_selector('span.badge', text: grandparent_group_label.title) + expect(page).to have_selector('span.badge', text: parent_group_label.title) + expect(page).to have_selector('span.badge', text: project_label_1.title) end end @@ -188,7 +188,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - find('.card').click + find('.board-card').click end it_behaves_like 'assigning labels from sidebar' @@ -204,7 +204,7 @@ feature 'Labels Hierarchy', :js, :nested_groups do wait_for_requests - find('.card').click + find('.board-card').click end it_behaves_like 'assigning labels from sidebar' diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb index 19995559fae..59aa90fc86f 100644 --- a/spec/features/merge_request/user_resolves_conflicts_spec.rb +++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb @@ -10,7 +10,7 @@ describe 'Merge request > User resolves conflicts', :js do end def create_merge_request(source_branch) - create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project) do |mr| + create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', source_project: project, merge_status: :unchecked) do |mr| mr.mark_as_unmergeable end end @@ -27,7 +27,7 @@ describe 'Merge request > User resolves conflicts', :js do end end - find_button('Commit conflict resolution').send_keys(:return) + find_button('Commit to source branch').send_keys(:return) expect(page).to have_content('All merge conflicts were resolved') merge_request.reload_diff @@ -71,7 +71,7 @@ describe 'Merge request > User resolves conflicts', :js do execute_script('ace.edit($(".files-wrapper .diff-file pre")[1]).setValue("Gregor Samsa woke from troubled dreams");') end - find_button('Commit conflict resolution').send_keys(:return) + find_button('Commit to source branch').send_keys(:return) expect(page).to have_content('All merge conflicts were resolved') merge_request.reload_diff @@ -145,7 +145,7 @@ describe 'Merge request > User resolves conflicts', :js do execute_script('ace.edit($(".files-wrapper .diff-file pre")[0]).setValue("Gregor Samsa woke from troubled dreams");') end - click_button 'Commit conflict resolution' + click_button 'Commit to source branch' expect(page).to have_content('All merge conflicts were resolved') diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb new file mode 100644 index 00000000000..c40c720d168 --- /dev/null +++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb @@ -0,0 +1,24 @@ +require 'rails_helper' + +describe 'Merge request > User sees Check out branch modal', :js do + let(:project) { create(:project, :public, :repository) } + let(:user) { project.creator } + let(:merge_request) { create(:merge_request, source_project: project) } + + before do + sign_in(user) + visit project_merge_request_path(project, merge_request) + wait_for_requests + click_button('Check out branch') + end + + it 'shows the check out branch modal' do + expect(page).to have_content('Check out, review, and merge locally') + end + + it 'closes the check out branch model with Escape keypress' do + find('#modal_merge_info').send_keys(:escape) + + expect(page).not_to have_content('Check out, review, and merge locally') + end +end diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb index 42c279af117..ed6e29335d1 100644 --- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb +++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb @@ -4,6 +4,12 @@ describe 'Merge request > User selects branches for new MR', :js do let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } + def select_source_branch(branch_name) + find('.js-source-branch', match: :first).click + find('.js-source-branch-dropdown .dropdown-input-field').native.send_keys branch_name + find('.js-source-branch-dropdown .dropdown-content a', text: branch_name, match: :first).click + end + before do project.add_master(user) sign_in(user) @@ -43,8 +49,7 @@ describe 'Merge request > User selects branches for new MR', :js do it 'generates a diff for an orphaned branch' do visit project_new_merge_request_path(project) - find('.js-source-branch', match: :first).click - find('.js-source-branch-dropdown .dropdown-content a', text: 'orphaned-branch', match: :first).click + select_source_branch('orphaned-branch') click_button "Compare branches" click_link "Changes" @@ -169,4 +174,31 @@ describe 'Merge request > User selects branches for new MR', :js do end end end + + context 'with special characters in branch names' do + it 'escapes quotes in branch names' do + special_branch_name = '"with-quotes"' + CreateBranchService.new(project, user) + .execute(special_branch_name, 'add-pdf-file') + + visit project_new_merge_request_path(project) + select_source_branch(special_branch_name) + + source_branch_input = find('[name="merge_request[source_branch]"]', visible: false) + expect(source_branch_input.value).to eq special_branch_name + end + + it 'does not escape unicode in branch names' do + special_branch_name = 'ʕ•ᴥ•ʔ' + CreateBranchService.new(project, user) + .execute(special_branch_name, 'add-pdf-file') + + visit project_new_merge_request_path(project) + select_source_branch(special_branch_name) + + click_button "Compare branches" + + expect(page).to have_button("Submit merge request") + end + end end diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb index 6c51e4bbe26..b0db6870ddf 100644 --- a/spec/features/milestone_spec.rb +++ b/spec/features/milestone_spec.rb @@ -119,7 +119,7 @@ feature 'Milestone' do find('.milestone-deprecation-message .js-popover-link').click - expect(page).to have_selector('.milestone-deprecation-message .popover') + expect(page).to have_selector('.popover') end end end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index ac82f869f0f..e7b305925f7 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -14,6 +14,8 @@ feature 'File blob', :js do context 'Ruby file' do before do visit_blob('files/ruby/popen.rb') + + wait_for_requests end it 'displays the blob' do @@ -48,6 +50,8 @@ feature 'File blob', :js do context 'visiting directly' do before do visit_blob('files/markdown/ruby-style-guide.md') + + wait_for_requests end it 'displays the blob using the rich viewer' do @@ -159,6 +163,8 @@ feature 'File blob', :js do project.update_attribute(:lfs_enabled, true) visit_blob('files/lfs/file.md') + + wait_for_requests end it 'displays an error' do @@ -207,6 +213,8 @@ feature 'File blob', :js do context 'when LFS is disabled on the project' do before do visit_blob('files/lfs/file.md') + + wait_for_requests end it 'displays the blob' do @@ -242,6 +250,8 @@ feature 'File blob', :js do ).execute visit_blob('files/test.pdf') + + wait_for_requests end it 'displays the blob' do @@ -268,6 +278,8 @@ feature 'File blob', :js do project.update_attribute(:lfs_enabled, true) visit_blob('files/lfs/lfs_object.iso') + + wait_for_requests end it 'displays the blob' do @@ -290,6 +302,8 @@ feature 'File blob', :js do context 'when LFS is disabled on the project' do before do visit_blob('files/lfs/lfs_object.iso') + + wait_for_requests end it 'displays the blob' do @@ -313,6 +327,8 @@ feature 'File blob', :js do context 'ZIP file' do before do visit_blob('Gemfile.zip') + + wait_for_requests end it 'displays the blob' do @@ -347,6 +363,8 @@ feature 'File blob', :js do ).execute visit_blob('files/empty.md') + + wait_for_requests end it 'displays an error' do diff --git a/spec/features/projects/issues/user_sorts_issues_spec.rb b/spec/features/projects/issues/user_sorts_issues_spec.rb index c3d63000dac..db5936a30cb 100644 --- a/spec/features/projects/issues/user_sorts_issues_spec.rb +++ b/spec/features/projects/issues/user_sorts_issues_spec.rb @@ -18,7 +18,7 @@ describe "User sorts issues" do it "sorts by popularity" do find("button.dropdown-toggle").click - page.within(".content ul.dropdown-menu.dropdown-menu-align-right li") do + page.within(".content ul.dropdown-menu.dropdown-menu-right li") do click_link("Popularity") end diff --git a/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb index d8d9f7e2a8c..305658f1b5d 100644 --- a/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb +++ b/spec/features/projects/merge_requests/user_sorts_merge_requests_spec.rb @@ -18,7 +18,7 @@ describe 'User sorts merge requests' do it 'keeps the sort option' do find('button.dropdown-toggle').click - page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link('Last updated') end @@ -43,7 +43,7 @@ describe 'User sorts merge requests' do it 'sorts by popularity' do find('button.dropdown-toggle').click - page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link('Popularity') end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index a29c21f6fef..af2a9567a47 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -165,7 +165,7 @@ describe 'Pipeline', :js do end it 'shows Pipeline tab as active' do - expect(page).to have_css('.js-pipeline-tab-link.active') + expect(page).to have_css('.js-pipeline-tab-link .active') end context 'without permission to access builds' do @@ -271,7 +271,7 @@ describe 'Pipeline', :js do end it 'shows Jobs tab as active' do - expect(page).to have_css('li.js-builds-tab-link.active') + expect(page).to have_css('li.js-builds-tab-link .active') end end diff --git a/spec/features/projects/settings/project_badges_spec.rb b/spec/features/projects/settings/project_badges_spec.rb index cc3551a4c21..4893bef8884 100644 --- a/spec/features/projects/settings/project_badges_spec.rb +++ b/spec/features/projects/settings/project_badges_spec.rb @@ -22,7 +22,7 @@ feature 'Project Badges' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 expect(rows[0]).to have_content group_badge.link_url expect(rows[1]).to have_content project_badge.link_url @@ -49,7 +49,7 @@ feature 'Project Badges' do click_button 'Add badge' wait_for_requests - within '.panel-body' do + within '.card-body' do expect(find('a')[:href]).to eq badge_link_url expect(find('a img')[:src]).to eq badge_image_url end @@ -61,7 +61,7 @@ feature 'Project Badges' do it 'form is shown when clicking edit button in list' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 rows[1].find('[aria-label="Edit"]').click @@ -75,7 +75,7 @@ feature 'Project Badges' do it 'updates a badge when submitting the edit form' do page.within '.badge-settings' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 rows[1].find('[aria-label="Edit"]').click within 'form' do @@ -86,7 +86,7 @@ feature 'Project Badges' do wait_for_requests end - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 expect(rows[1]).to have_content badge_link_url end @@ -100,7 +100,7 @@ feature 'Project Badges' do it 'shows a modal when deleting a badge' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 click_delete_button(rows[1]) @@ -110,14 +110,14 @@ feature 'Project Badges' do it 'deletes a badge when confirming the modal' do wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 2 click_delete_button(rows[1]) find('.modal .btn-danger').click wait_for_requests - rows = all('.panel-body > div') + rows = all('.card-body > div') expect(rows.length).to eq 1 expect(rows[0]).to have_content group_badge.link_url end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 43cabd3b9f2..0c28a853b54 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -68,8 +68,10 @@ feature 'Protected Branches', :js do within form do find(".js-allowed-to-merge").click + wait_for_requests click_link 'No one' find(".js-allowed-to-push").click + wait_for_requests click_link 'Developers + Masters' end diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb index 6088b831c14..db141ef7096 100644 --- a/spec/features/signed_commits_spec.rb +++ b/spec/features/signed_commits_spec.rb @@ -96,10 +96,11 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed commit by bette cartwright')) do click_on 'Unverified' - within '.popover' do - expect(page).to have_content 'This commit was signed with an unverified signature.' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content 'This commit was signed with an unverified signature.' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" end end @@ -110,12 +111,13 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed and authored commit by bette cartwright, different email')) do click_on 'Unverified' - within '.popover' do - expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.' - expect(page).to have_content 'Bette Cartwright' - expect(page).to have_content '@bette.cartwright' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content 'This commit was signed with a verified signature, but the committer email is not verified to belong to the same user.' + expect(page).to have_content 'Bette Cartwright' + expect(page).to have_content '@bette.cartwright' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" end end @@ -126,12 +128,13 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed commit by bette cartwright')) do click_on 'Unverified' - within '.popover' do - expect(page).to have_content "This commit was signed with a different user's verified signature." - expect(page).to have_content 'Bette Cartwright' - expect(page).to have_content '@bette.cartwright' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content "This commit was signed with a different user's verified signature." + expect(page).to have_content 'Bette Cartwright' + expect(page).to have_content '@bette.cartwright' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User2.primary_keyid}" end end @@ -142,12 +145,13 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do click_on 'Verified' - within '.popover' do - expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.' - expect(page).to have_content 'Nannie Bernhard' - expect(page).to have_content '@nannie.bernhard' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.' + expect(page).to have_content 'Nannie Bernhard' + expect(page).to have_content '@nannie.bernhard' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}" end end @@ -167,12 +171,13 @@ describe 'GPG signed commits', :js do within(find('.commit', text: 'signed and authored commit by nannie bernhard')) do click_on 'Verified' - within '.popover' do - expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.' - expect(page).to have_content 'Nannie Bernhard' - expect(page).to have_content 'nannie.bernhard@example.com' - expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}" - end + end + + within '.popover' do + expect(page).to have_content 'This commit was signed with a verified signature and the committer email is verified to belong to the same user.' + expect(page).to have_content 'Nannie Bernhard' + expect(page).to have_content 'nannie.bernhard@example.com' + expect(page).to have_content "GPG Key ID: #{GpgHelpers::User1.primary_keyid}" end end end diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb index 19784120108..6be2606fd0d 100644 --- a/spec/features/triggers_spec.rb +++ b/spec/features/triggers_spec.rb @@ -23,7 +23,7 @@ feature 'Triggers', :js do click_button 'Add trigger' # See if input has error due to empty value - expect(page.find('form.gl-show-field-errors .gl-field-error')['style']).to eq 'display: block;' + expect(page.find('form.gl-show-field-errors .gl-field-error')).to be_visible end scenario 'adds new trigger with description' do diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 6f968a2c590..1f8d31a5c88 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -394,7 +394,7 @@ feature 'Login' do end def ensure_one_active_tab - expect(page).to have_selector('ul.new-session-tabs > li.active', count: 1) + expect(page).to have_selector('ul.new-session-tabs > li > a.active', count: 1) end def ensure_one_active_pane diff --git a/spec/fixtures/api/schemas/public_api/v4/projects.json b/spec/fixtures/api/schemas/public_api/v4/projects.json index d89eeea89a5..17ad8d8c48d 100644 --- a/spec/fixtures/api/schemas/public_api/v4/projects.json +++ b/spec/fixtures/api/schemas/public_api/v4/projects.json @@ -20,6 +20,7 @@ "ssh_url_to_repo": { "type": "string" }, "http_url_to_repo": { "type": "string" }, "web_url": { "type": "string" }, + "readme_url": { "type": ["string", "null"] }, "avatar_url": { "type": ["string", "null"] }, "star_count": { "type": "integer" }, "forks_count": { "type": "integer" }, diff --git a/spec/fixtures/emails/valid_new_issue_with_quote.eml b/spec/fixtures/emails/valid_new_issue_with_quote.eml new file mode 100644 index 00000000000..0caf8ed4e9e --- /dev/null +++ b/spec/fixtures/emails/valid_new_issue_with_quote.eml @@ -0,0 +1,25 @@ +Return-Path: <jake@adventuretime.ooo> +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog <jake@adventuretime.ooo> +To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo +Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com> +Subject: New Issue by email +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +The reply by email functionality should be extended to allow creating a new issue by email. +even when the email is forwarded to the project which may include lines that begin with ">" + +there should be a quote below this line: + +> this is a quote
\ No newline at end of file diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 8de615ad8c2..a3e010c3206 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -258,13 +258,19 @@ describe BlobHelper do it 'returns full IDE path' do Rails.application.routes.default_url_options[:script_name] = nil - expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master/") + expect(helper.ide_edit_path(project, "master", "")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master") + end + + it 'returns full IDE path with second -' do + Rails.application.routes.default_url_options[:script_name] = nil + + expect(helper.ide_edit_path(project, "testing/slashes", "readme.md")).to eq("/-/ide/project/#{project.namespace.path}/#{project.path}/edit/testing/slashes/-/readme.md") end it 'returns IDE path without relative_url_root' do Rails.application.routes.default_url_options[:script_name] = "/gitlab" - expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master/") + expect(helper.ide_edit_path(project, "master", "")).to eq("/gitlab/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master") end end end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 7b59fde999d..cddb49b320f 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -45,22 +45,22 @@ describe IssuablesHelper do it 'returns "Open" when state is :opened' do expect(helper.issuables_state_counter_text(:issues, :opened, true)) - .to eq('<span>Open</span> <span class="badge">42</span>') + .to eq('<span>Open</span> <span class="badge badge-pill">42</span>') end it 'returns "Closed" when state is :closed' do expect(helper.issuables_state_counter_text(:issues, :closed, true)) - .to eq('<span>Closed</span> <span class="badge">42</span>') + .to eq('<span>Closed</span> <span class="badge badge-pill">42</span>') end it 'returns "Merged" when state is :merged' do expect(helper.issuables_state_counter_text(:merge_requests, :merged, true)) - .to eq('<span>Merged</span> <span class="badge">42</span>') + .to eq('<span>Merged</span> <span class="badge badge-pill">42</span>') end it 'returns "All" when state is :all' do expect(helper.issuables_state_counter_text(:merge_requests, :all, true)) - .to eq('<span>All</span> <span class="badge">42</span>') + .to eq('<span>All</span> <span class="badge badge-pill">42</span>') end end end diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 3d7ccf432be..e8435116221 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -341,4 +341,25 @@ describe('Api', () => { .catch(done.fail); }); }); + + describe('commitPipelines', () => { + it('fetches pipelines for a given commit', done => { + const projectId = 'example/foobar'; + const commitSha = 'abc123def'; + const expectedUrl = `${dummyUrlRoot}/${projectId}/commit/${commitSha}/pipelines`; + mock.onGet(expectedUrl).reply(200, [ + { + name: 'test', + }, + ]); + + Api.commitPipelines(projectId, commitSha) + .then(({ data }) => { + expect(data.length).toBe(1); + expect(data[0].name).toBe('test'); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js index 9439c578973..02e59ae0843 100644 --- a/spec/javascripts/badges/components/badge_list_spec.js +++ b/spec/javascripts/badges/components/badge_list_spec.js @@ -33,7 +33,7 @@ describe('BadgeList component', () => { }); it('renders a header with the badge count', () => { - const header = vm.$el.querySelector('.panel-heading'); + const header = vm.$el.querySelector('.card-header'); expect(header).toHaveText(new RegExp(`Your badges\\s+${numberOfDummyBadges}`)); }); diff --git a/spec/javascripts/badges/components/badge_settings_spec.js b/spec/javascripts/badges/components/badge_settings_spec.js index 3db02982ad4..59367c85125 100644 --- a/spec/javascripts/badges/components/badge_settings_spec.js +++ b/spec/javascripts/badges/components/badge_settings_spec.js @@ -60,7 +60,7 @@ describe('BadgeSettings component', () => { }); it('displays badge list', () => { - const badgeListElement = vm.$el.querySelector('.panel'); + const badgeListElement = vm.$el.querySelector('.card'); expect(badgeListElement).not.toBe(null); expect(badgeListElement).toBeVisible(); expect(badgeListElement).toContainText('Your badges'); @@ -87,7 +87,7 @@ describe('BadgeSettings component', () => { }); it('displays no badge list', () => { - const badgeListElement = vm.$el.querySelector('.panel'); + const badgeListElement = vm.$el.querySelector('.card'); expect(badgeListElement).toBeHidden(); }); }); diff --git a/spec/javascripts/behaviors/secret_values_spec.js b/spec/javascripts/behaviors/secret_values_spec.js index 38d9bba6868..95122fcf30f 100644 --- a/spec/javascripts/behaviors/secret_values_spec.js +++ b/spec/javascripts/behaviors/secret_values_spec.js @@ -9,7 +9,7 @@ function generateValueMarkup( <div class="${placeholderClass}"> *** </div> - <div class="hide ${valueClass}"> + <div class="hidden ${valueClass}"> ${secret} </div> `; diff --git a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js index aa87956109f..cb0f2ba686d 100644 --- a/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js +++ b/spec/javascripts/blob/balsamiq/balsamiq_viewer_spec.js @@ -257,9 +257,9 @@ describe('BalsamiqViewer', () => { name = 'name'; resource = 'resource'; template = ` - <div class="panel panel-default"> - <div class="panel-heading">name</div> - <div class="panel-body"> + <div class="card"> + <div class="card-header">name</div> + <div class="card-body"> <img class="img-thumbnail" src="data:image/png;base64,image"/> </div> </div> diff --git a/spec/javascripts/blob/sketch/index_spec.js b/spec/javascripts/blob/sketch/index_spec.js index 79f40559817..e062a068a92 100644 --- a/spec/javascripts/blob/sketch/index_spec.js +++ b/spec/javascripts/blob/sketch/index_spec.js @@ -82,7 +82,7 @@ describe('Sketch viewer', () => { const img = document.querySelector('#js-sketch-viewer img'); expect(img).not.toBeNull(); - expect(img.classList.contains('img-responsive')).toBeTruthy(); + expect(img.classList.contains('img-fluid')).toBeTruthy(); }); it('renders link to image', () => { diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js index 03df6c06691..c06b2f60813 100644 --- a/spec/javascripts/boards/board_list_spec.js +++ b/spec/javascripts/boards/board_list_spec.js @@ -83,13 +83,13 @@ describe('Board list component', () => { it('renders issues', () => { expect( - component.$el.querySelectorAll('.card').length, + component.$el.querySelectorAll('.board-card').length, ).toBe(1); }); it('sets data attribute with issue id', () => { expect( - component.$el.querySelector('.card').getAttribute('data-issue-id'), + component.$el.querySelector('.board-card').getAttribute('data-issue-id'), ).toBe('1'); }); diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index be1ea0b57b4..abeef272c68 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -70,19 +70,19 @@ describe('Issue card component', () => { it('renders issue title', () => { expect( - component.$el.querySelector('.card-title').textContent, + component.$el.querySelector('.board-card-title').textContent, ).toContain(issue.title); }); it('includes issue base in link', () => { expect( - component.$el.querySelector('.card-title a').getAttribute('href'), + component.$el.querySelector('.board-card-title a').getAttribute('href'), ).toContain('/test'); }); it('includes issue title on link', () => { expect( - component.$el.querySelector('.card-title a').getAttribute('title'), + component.$el.querySelector('.board-card-title a').getAttribute('title'), ).toBe(issue.title); }); @@ -105,14 +105,14 @@ describe('Issue card component', () => { it('renders issue ID with #', () => { expect( - component.$el.querySelector('.card-number').textContent, + component.$el.querySelector('.board-card-number').textContent, ).toContain(`#${issue.id}`); }); describe('assignee', () => { it('does not render assignee', () => { expect( - component.$el.querySelector('.card-assignee .avatar'), + component.$el.querySelector('.board-card-assignee .avatar'), ).toBeNull(); }); @@ -125,25 +125,25 @@ describe('Issue card component', () => { it('renders assignee', () => { expect( - component.$el.querySelector('.card-assignee .avatar'), + component.$el.querySelector('.board-card-assignee .avatar'), ).not.toBeNull(); }); it('sets title', () => { expect( - component.$el.querySelector('.card-assignee img').getAttribute('data-original-title'), + component.$el.querySelector('.board-card-assignee img').getAttribute('data-original-title'), ).toContain(`Assigned to ${user.name}`); }); it('sets users path', () => { expect( - component.$el.querySelector('.card-assignee a').getAttribute('href'), + component.$el.querySelector('.board-card-assignee a').getAttribute('href'), ).toBe('/test'); }); it('renders avatar', () => { expect( - component.$el.querySelector('.card-assignee img'), + component.$el.querySelector('.board-card-assignee img'), ).not.toBeNull(); }); }); @@ -161,10 +161,10 @@ describe('Issue card component', () => { it('displays defaults avatar if users avatar is null', () => { expect( - component.$el.querySelector('.card-assignee img'), + component.$el.querySelector('.board-card-assignee img'), ).not.toBeNull(); expect( - component.$el.querySelector('.card-assignee img').getAttribute('src'), + component.$el.querySelector('.board-card-assignee img').getAttribute('src'), ).toBe('default_avatar'); }); }); @@ -197,7 +197,7 @@ describe('Issue card component', () => { }); it('renders all four assignees', () => { - expect(component.$el.querySelectorAll('.card-assignee .avatar').length).toEqual(4); + expect(component.$el.querySelectorAll('.board-card-assignee .avatar').length).toEqual(4); }); describe('more than four assignees', () => { @@ -213,11 +213,11 @@ describe('Issue card component', () => { }); it('renders more avatar counter', () => { - expect(component.$el.querySelector('.card-assignee .avatar-counter').innerText).toEqual('+2'); + expect(component.$el.querySelector('.board-card-assignee .avatar-counter').innerText).toEqual('+2'); }); it('renders three assignees', () => { - expect(component.$el.querySelectorAll('.card-assignee .avatar').length).toEqual(3); + expect(component.$el.querySelectorAll('.board-card-assignee .avatar').length).toEqual(3); }); it('renders 99+ avatar counter', (done) => { @@ -232,7 +232,7 @@ describe('Issue card component', () => { } Vue.nextTick(() => { - expect(component.$el.querySelector('.card-assignee .avatar-counter').innerText).toEqual('99+'); + expect(component.$el.querySelector('.board-card-assignee .avatar-counter').innerText).toEqual('99+'); done(); }); }); @@ -248,13 +248,13 @@ describe('Issue card component', () => { it('renders list label', () => { expect( - component.$el.querySelectorAll('.label').length, + component.$el.querySelectorAll('.badge').length, ).toBe(2); }); it('renders label', () => { const nodes = []; - component.$el.querySelectorAll('.label').forEach((label) => { + component.$el.querySelectorAll('.badge').forEach((label) => { nodes.push(label.title); }); @@ -265,13 +265,13 @@ describe('Issue card component', () => { it('sets label description as title', () => { expect( - component.$el.querySelector('.label').getAttribute('title'), + component.$el.querySelector('.badge').getAttribute('title'), ).toContain(label1.description); }); it('sets background color of button', () => { const nodes = []; - component.$el.querySelectorAll('.label').forEach((label) => { + component.$el.querySelectorAll('.badge').forEach((label) => { nodes.push(label.style.backgroundColor); }); @@ -288,7 +288,7 @@ describe('Issue card component', () => { Vue.nextTick() .then(() => { expect( - component.$el.querySelectorAll('.label').length, + component.$el.querySelectorAll('.badge').length, ).toBe(2); expect( component.$el.textContent, diff --git a/spec/javascripts/fixtures/event_filter.html.haml b/spec/javascripts/fixtures/event_filter.html.haml index 5477c6075f0..aa7af61c7eb 100644 --- a/spec/javascripts/fixtures/event_filter.html.haml +++ b/spec/javascripts/fixtures/event_filter.html.haml @@ -1,4 +1,4 @@ -%ul.nav-links.event-filter.scrolling-tabs +%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs %li.active %a.event-filter-link{ id: "all_event_filter", title: "Filter by all", href: "/dashboard/activity"} %span diff --git a/spec/javascripts/fixtures/issue_sidebar_label.html.haml b/spec/javascripts/fixtures/issue_sidebar_label.html.haml index 397bdc85c67..06ce248dc9c 100644 --- a/spec/javascripts/fixtures/issue_sidebar_label.html.haml +++ b/spec/javascripts/fixtures/issue_sidebar_label.html.haml @@ -1,7 +1,7 @@ .block.labels .sidebar-collapsed-icon.js-sidebar-labels-tooltip .title.hide-collapsed - %a.edit-link.pull-right{ href: "#" } + %a.edit-link.float-right{ href: "#" } Edit .selectbox.hide-collapsed{ style: "display: none;" } .dropdown diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml index c38fe8b1f25..632606e0536 100644 --- a/spec/javascripts/fixtures/linked_tabs.html.haml +++ b/spec/javascripts/fixtures/linked_tabs.html.haml @@ -1,9 +1,9 @@ -%ul.nav-links.new-session-tabs.linked-tabs - %li - %a{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } } +%ul.nav.nav-tabs.new-session-tabs.linked-tabs + %li.nav-item + %a.nav-link{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } } Tab 1 - %li - %a{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } } + %li.nav-item + %a.nav-link{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } } Tab 2 .tab-content diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 7f9c4811fba..175f386b60e 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -70,9 +70,9 @@ describe('glDropdown', function describeDropdown() { it('should open on click', () => { initDropDown.call(this, false); - expect(this.dropdownContainerElement).not.toHaveClass('open'); + expect(this.dropdownContainerElement).not.toHaveClass('show'); this.dropdownButtonElement.click(); - expect(this.dropdownContainerElement).toHaveClass('open'); + expect(this.dropdownContainerElement).toHaveClass('show'); }); it('escapes HTML as text', () => { @@ -134,12 +134,12 @@ describe('glDropdown', function describeDropdown() { }); it('should click the selected item on ENTER keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('open'); + expect(this.dropdownContainerElement).toHaveClass('show'); const randomIndex = Math.floor(Math.random() * (this.projectsData.length - 1)) + 0; navigateWithKeys('down', randomIndex, () => { const visitUrl = spyOnDependency(GLDropdown, 'visitUrl').and.stub(); navigateWithKeys('enter', null, () => { - expect(this.dropdownContainerElement).not.toHaveClass('open'); + expect(this.dropdownContainerElement).not.toHaveClass('show'); const link = $(`${ITEM_SELECTOR}:eq(${randomIndex}) a`, this.$dropdownMenuElement); expect(link).toHaveClass('is-active'); const linkedLocation = link.attr('href'); @@ -149,13 +149,13 @@ describe('glDropdown', function describeDropdown() { }); it('should close on ESC keypress', () => { - expect(this.dropdownContainerElement).toHaveClass('open'); + expect(this.dropdownContainerElement).toHaveClass('show'); this.dropdownContainerElement.trigger({ type: 'keyup', which: ARROW_KEYS.ESC, keyCode: ARROW_KEYS.ESC }); - expect(this.dropdownContainerElement).not.toHaveClass('open'); + expect(this.dropdownContainerElement).not.toHaveClass('show'); }); }); diff --git a/spec/javascripts/helpers/vuex_action_helper.js b/spec/javascripts/helpers/vuex_action_helper.js index 83f29d1b0c2..d6ab0aeeed7 100644 --- a/spec/javascripts/helpers/vuex_action_helper.js +++ b/spec/javascripts/helpers/vuex_action_helper.js @@ -55,7 +55,7 @@ export default (action, payload, state, expectedMutations, expectedActions, done }; // call the action with mocked store and arguments - action({ commit, state, dispatch }, payload); + action({ commit, state, dispatch, rootState: state }, payload); // check if no mutations should have been dispatched if (expectedMutations.length === 0) { diff --git a/spec/javascripts/ide/components/ide_spec.js b/spec/javascripts/ide/components/ide_spec.js index 6f580e1f7af..045a60e56a0 100644 --- a/spec/javascripts/ide/components/ide_spec.js +++ b/spec/javascripts/ide/components/ide_spec.js @@ -107,5 +107,11 @@ describe('ide component', () => { vm.mousetrapStopCallback(null, vm.$el.querySelector('.dropdown-input-field'), 't'), ).toBe(true); }); + + it('stops callback in monaco editor', () => { + setFixtures('<div class="inputarea"></div>'); + + expect(vm.mousetrapStopCallback(null, document.querySelector('.inputarea'), 't')).toBe(true); + }); }); }); diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js index ff500acd849..d3f80e6f9c0 100644 --- a/spec/javascripts/ide/components/repo_editor_spec.js +++ b/spec/javascripts/ide/components/repo_editor_spec.js @@ -346,4 +346,24 @@ describe('RepoEditor', () => { }); }); }); + + it('calls removePendingTab when old file is pending', done => { + spyOnProperty(vm, 'shouldHideEditor').and.returnValue(true); + spyOn(vm, 'removePendingTab'); + + vm.file.pending = true; + + vm + .$nextTick() + .then(() => { + vm.file = file('testing'); + + return vm.$nextTick(); + }) + .then(() => { + expect(vm.removePendingTab).toHaveBeenCalled(); + }) + .then(done) + .catch(done.fail); + }); }); diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js index c059862b9d1..c68ae050641 100644 --- a/spec/javascripts/ide/mock_data.js +++ b/spec/javascripts/ide/mock_data.js @@ -1,4 +1,3 @@ -// eslint-disable-next-line import/prefer-default-export export const projectData = { id: 1, name: 'abcproject', @@ -14,3 +13,83 @@ export const projectData = { mergeRequests: {}, merge_requests_enabled: true, }; + +export const pipelines = [ + { + id: 1, + ref: 'master', + sha: '123', + status: 'failed', + }, + { + id: 2, + ref: 'master', + sha: '213', + status: 'success', + }, +]; + +export const jobs = [ + { + id: 1, + name: 'test', + status: 'failed', + stage: 'test', + duration: 1, + }, + { + id: 2, + name: 'test 2', + status: 'failed', + stage: 'test', + duration: 1, + }, + { + id: 3, + name: 'test 3', + status: 'failed', + stage: 'test', + duration: 1, + }, + { + id: 4, + name: 'test 3', + status: 'failed', + stage: 'build', + duration: 1, + }, +]; + +export const fullPipelinesResponse = { + data: { + count: { + all: 2, + }, + pipelines: [ + { + id: '51', + commit: { + id: 'xxxxxxxxxxxxxxxxxxxx', + }, + details: { + status: { + icon: 'status_failed', + text: 'failed', + }, + }, + }, + { + id: '50', + commit: { + id: 'abc123def456ghi789jkl', + }, + details: { + status: { + icon: 'status_passed', + text: 'passed', + }, + }, + }, + ], + }, +}; diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index ebd08d95810..8e078ae7138 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -1,14 +1,33 @@ -import { - refreshLastCommitData, -} from '~/ide/stores/actions'; +import Visibility from 'visibilityjs'; +import MockAdapter from 'axios-mock-adapter'; +import { refreshLastCommitData, pollSuccessCallBack } from '~/ide/stores/actions'; import store from '~/ide/stores'; import service from '~/ide/services'; +import axios from '~/lib/utils/axios_utils'; +import { fullPipelinesResponse } from '../../mock_data'; import { resetStore } from '../../helpers'; import testAction from '../../../helpers/vuex_action_helper'; describe('IDE store project actions', () => { + const setProjectState = () => { + store.state.currentProjectId = 'abc/def'; + store.state.currentBranchId = 'master'; + store.state.projects['abc/def'] = { + id: 4, + path_with_namespace: 'abc/def', + branches: { + master: { + commit: { + id: 'abc123def456ghi789jkl', + title: 'example', + }, + }, + }, + }; + }; + beforeEach(() => { - store.state.projects.abcproject = {}; + store.state.projects['abc/def'] = {}; }); afterEach(() => { @@ -17,18 +36,16 @@ describe('IDE store project actions', () => { describe('refreshLastCommitData', () => { beforeEach(() => { - store.state.currentProjectId = 'abcproject'; + store.state.currentProjectId = 'abc/def'; store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { + store.state.projects['abc/def'] = { + id: 4, branches: { master: { commit: null, }, }, }; - }); - - it('calls the service', done => { spyOn(service, 'getBranchData').and.returnValue( Promise.resolve({ data: { @@ -36,14 +53,16 @@ describe('IDE store project actions', () => { }, }), ); + }); + it('calls the service', done => { store .dispatch('refreshLastCommitData', { projectId: store.state.currentProjectId, branchId: store.state.currentBranchId, }) .then(() => { - expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master'); + expect(service.getBranchData).toHaveBeenCalledWith('abc/def', 'master'); done(); }) @@ -53,16 +72,118 @@ describe('IDE store project actions', () => { it('commits getBranchData', done => { testAction( refreshLastCommitData, - {}, - {}, - [{ - type: 'SET_BRANCH_COMMIT', - payload: { - projectId: 'abcproject', - branchId: 'master', - commit: { id: '123' }, + { + projectId: store.state.currentProjectId, + branchId: store.state.currentBranchId, + }, + store.state, + [ + { + type: 'SET_BRANCH_COMMIT', + payload: { + projectId: 'abc/def', + branchId: 'master', + commit: { id: '123' }, + }, + }, + ], // mutations + [ + { + type: 'getLastCommitPipeline', + payload: { + projectId: 'abc/def', + projectIdNumber: store.state.projects['abc/def'].id, + branchId: 'master', + }, + }, + ], // action + done, + ); + }); + }); + + describe('pipelinePoll', () => { + let mock; + + beforeEach(() => { + setProjectState(); + jasmine.clock().install(); + mock = new MockAdapter(axios); + mock + .onGet('/abc/def/commit/abc123def456ghi789jkl/pipelines') + .reply(200, { data: { foo: 'bar' } }, { 'poll-interval': '10000' }); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + mock.restore(); + store.dispatch('stopPipelinePolling'); + }); + + it('calls service periodically', done => { + spyOn(axios, 'get').and.callThrough(); + spyOn(Visibility, 'hidden').and.returnValue(false); + + store + .dispatch('pipelinePoll') + .then(() => { + jasmine.clock().tick(1000); + + expect(axios.get).toHaveBeenCalled(); + expect(axios.get.calls.count()).toBe(1); + }) + .then(() => new Promise(resolve => requestAnimationFrame(resolve))) + .then(() => { + jasmine.clock().tick(10000); + expect(axios.get.calls.count()).toBe(2); + }) + .then(() => new Promise(resolve => requestAnimationFrame(resolve))) + .then(() => { + jasmine.clock().tick(10000); + expect(axios.get.calls.count()).toBe(3); + }) + .then(() => new Promise(resolve => requestAnimationFrame(resolve))) + .then(() => { + jasmine.clock().tick(10000); + expect(axios.get.calls.count()).toBe(4); + }) + + .then(done) + .catch(done.fail); + }); + }); + + describe('pollSuccessCallBack', () => { + beforeEach(() => { + setProjectState(); + }); + + it('commits correct pipeline', done => { + testAction( + pollSuccessCallBack, + fullPipelinesResponse, + store.state, + [ + { + type: 'SET_LAST_COMMIT_PIPELINE', + payload: { + projectId: 'abc/def', + branchId: 'master', + pipeline: { + id: '50', + commit: { + id: 'abc123def456ghi789jkl', + }, + details: { + status: { + icon: 'status_passed', + text: 'passed', + }, + }, + }, + }, }, - }], // mutations + ], // mutations [], // action done, ); diff --git a/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js new file mode 100644 index 00000000000..85fbcf8084b --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pipelines/actions_spec.js @@ -0,0 +1,289 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import actions, { + requestLatestPipeline, + receiveLatestPipelineError, + receiveLatestPipelineSuccess, + fetchLatestPipeline, + requestJobs, + receiveJobsError, + receiveJobsSuccess, + fetchJobs, +} from '~/ide/stores/modules/pipelines/actions'; +import state from '~/ide/stores/modules/pipelines/state'; +import * as types from '~/ide/stores/modules/pipelines/mutation_types'; +import testAction from '../../../../helpers/vuex_action_helper'; +import { pipelines, jobs } from '../../../mock_data'; + +describe('IDE pipelines actions', () => { + let mockedState; + let mock; + + beforeEach(() => { + mockedState = state(); + mock = new MockAdapter(axios); + + gon.api_version = 'v4'; + mockedState.currentProjectId = 'test/project'; + }); + + afterEach(() => { + mock.restore(); + }); + + describe('requestLatestPipeline', () => { + it('commits request', done => { + testAction( + requestLatestPipeline, + null, + mockedState, + [{ type: types.REQUEST_LATEST_PIPELINE }], + [], + done, + ); + }); + }); + + describe('receiveLatestPipelineError', () => { + it('commits error', done => { + testAction( + receiveLatestPipelineError, + null, + mockedState, + [{ type: types.RECEIVE_LASTEST_PIPELINE_ERROR }], + [], + done, + ); + }); + + it('creates flash message', () => { + const flashSpy = spyOnDependency(actions, 'flash'); + + receiveLatestPipelineError({ commit() {} }); + + expect(flashSpy).toHaveBeenCalled(); + }); + }); + + describe('receiveLatestPipelineSuccess', () => { + it('commits pipeline', done => { + testAction( + receiveLatestPipelineSuccess, + pipelines[0], + mockedState, + [{ type: types.RECEIVE_LASTEST_PIPELINE_SUCCESS, payload: pipelines[0] }], + [], + done, + ); + }); + }); + + describe('fetchLatestPipeline', () => { + describe('success', () => { + beforeEach(() => { + mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(200, pipelines); + }); + + it('dispatches request', done => { + testAction( + fetchLatestPipeline, + '123', + mockedState, + [], + [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineSuccess' }], + done, + ); + }); + + it('dispatches success with latest pipeline', done => { + testAction( + fetchLatestPipeline, + '123', + mockedState, + [], + [ + { type: 'requestLatestPipeline' }, + { type: 'receiveLatestPipelineSuccess', payload: pipelines[0] }, + ], + done, + ); + }); + + it('calls axios with correct params', () => { + const apiSpy = spyOn(axios, 'get').and.callThrough(); + + fetchLatestPipeline({ dispatch() {}, rootState: state }, '123'); + + expect(apiSpy).toHaveBeenCalledWith(jasmine.anything(), { + params: { + sha: '123', + per_page: '1', + }, + }); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500); + }); + + it('dispatches error', done => { + testAction( + fetchLatestPipeline, + '123', + mockedState, + [], + [{ type: 'requestLatestPipeline' }, { type: 'receiveLatestPipelineError' }], + done, + ); + }); + }); + }); + + describe('requestJobs', () => { + it('commits request', done => { + testAction(requestJobs, null, mockedState, [{ type: types.REQUEST_JOBS }], [], done); + }); + }); + + describe('receiveJobsError', () => { + it('commits error', done => { + testAction( + receiveJobsError, + null, + mockedState, + [{ type: types.RECEIVE_JOBS_ERROR }], + [], + done, + ); + }); + + it('creates flash message', () => { + const flashSpy = spyOnDependency(actions, 'flash'); + + receiveJobsError({ commit() {} }); + + expect(flashSpy).toHaveBeenCalled(); + }); + }); + + describe('receiveJobsSuccess', () => { + it('commits jobs', done => { + testAction( + receiveJobsSuccess, + jobs, + mockedState, + [{ type: types.RECEIVE_JOBS_SUCCESS, payload: jobs }], + [], + done, + ); + }); + }); + + describe('fetchJobs', () => { + let page = ''; + + beforeEach(() => { + mockedState.latestPipeline = pipelines[0]; + }); + + describe('success', () => { + beforeEach(() => { + mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines\/(.*)\/jobs/).replyOnce(() => [ + 200, + jobs, + { + 'x-next-page': page, + }, + ]); + }); + + it('dispatches request', done => { + testAction( + fetchJobs, + null, + mockedState, + [], + [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess' }], + done, + ); + }); + + it('dispatches success with latest pipeline', done => { + testAction( + fetchJobs, + null, + mockedState, + [], + [{ type: 'requestJobs' }, { type: 'receiveJobsSuccess', payload: jobs }], + done, + ); + }); + + it('dispatches twice for both pages', done => { + page = '2'; + + testAction( + fetchJobs, + null, + mockedState, + [], + [ + { type: 'requestJobs' }, + { type: 'receiveJobsSuccess', payload: jobs }, + { type: 'fetchJobs', payload: '2' }, + { type: 'requestJobs' }, + { type: 'receiveJobsSuccess', payload: jobs }, + ], + done, + ); + }); + + it('calls axios with correct URL', () => { + const apiSpy = spyOn(axios, 'get').and.callThrough(); + + fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }); + + expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { + params: { page: '1' }, + }); + }); + + it('calls axios with page next page', () => { + const apiSpy = spyOn(axios, 'get').and.callThrough(); + + fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }); + + expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { + params: { page: '1' }, + }); + + page = '2'; + + fetchJobs({ dispatch() {}, state: mockedState, rootState: mockedState }, page); + + expect(apiSpy).toHaveBeenCalledWith('/api/v4/projects/test%2Fproject/pipelines/1/jobs', { + params: { page: '2' }, + }); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(/\/api\/v4\/projects\/(.*)\/pipelines(.*)/).replyOnce(500); + }); + + it('dispatches error', done => { + testAction( + fetchJobs, + null, + mockedState, + [], + [{ type: 'requestJobs' }, { type: 'receiveJobsError' }], + done, + ); + }); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js new file mode 100644 index 00000000000..b2a7e8a9025 --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js @@ -0,0 +1,71 @@ +import * as getters from '~/ide/stores/modules/pipelines/getters'; +import state from '~/ide/stores/modules/pipelines/state'; + +describe('IDE pipeline getters', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe('hasLatestPipeline', () => { + it('returns false when loading is true', () => { + mockedState.isLoadingPipeline = true; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns false when pipelines is null', () => { + mockedState.latestPipeline = null; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns false when loading is true & pipelines is null', () => { + mockedState.latestPipeline = null; + mockedState.isLoadingPipeline = true; + + expect(getters.hasLatestPipeline(mockedState)).toBe(false); + }); + + it('returns true when loading is false & pipelines is an object', () => { + mockedState.latestPipeline = { + id: 1, + }; + mockedState.isLoadingPipeline = false; + + expect(getters.hasLatestPipeline(mockedState)).toBe(true); + }); + }); + + describe('failedJobs', () => { + it('returns array of failed jobs', () => { + mockedState.stages = [ + { + title: 'test', + jobs: [{ id: 1, status: 'failed' }, { id: 2, status: 'success' }], + }, + { + title: 'build', + jobs: [{ id: 3, status: 'failed' }, { id: 4, status: 'failed' }], + }, + ]; + + expect(getters.failedJobs(mockedState).length).toBe(3); + expect(getters.failedJobs(mockedState)).toEqual([ + { + id: 1, + status: jasmine.anything(), + }, + { + id: 3, + status: jasmine.anything(), + }, + { + id: 4, + status: jasmine.anything(), + }, + ]); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js new file mode 100644 index 00000000000..8262e916243 --- /dev/null +++ b/spec/javascripts/ide/stores/modules/pipelines/mutations_spec.js @@ -0,0 +1,120 @@ +import mutations from '~/ide/stores/modules/pipelines/mutations'; +import state from '~/ide/stores/modules/pipelines/state'; +import * as types from '~/ide/stores/modules/pipelines/mutation_types'; +import { pipelines, jobs } from '../../../mock_data'; + +describe('IDE pipelines mutations', () => { + let mockedState; + + beforeEach(() => { + mockedState = state(); + }); + + describe(types.REQUEST_LATEST_PIPELINE, () => { + it('sets loading to true', () => { + mutations[types.REQUEST_LATEST_PIPELINE](mockedState); + + expect(mockedState.isLoadingPipeline).toBe(true); + }); + }); + + describe(types.RECEIVE_LASTEST_PIPELINE_ERROR, () => { + it('sets loading to false', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_ERROR](mockedState); + + expect(mockedState.isLoadingPipeline).toBe(false); + }); + }); + + describe(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, () => { + it('sets loading to false on success', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]); + + expect(mockedState.isLoadingPipeline).toBe(false); + }); + + it('sets latestPipeline', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, pipelines[0]); + + expect(mockedState.latestPipeline).toEqual({ + id: pipelines[0].id, + status: pipelines[0].status, + }); + }); + + it('does not set latest pipeline if pipeline is null', () => { + mutations[types.RECEIVE_LASTEST_PIPELINE_SUCCESS](mockedState, null); + + expect(mockedState.latestPipeline).toEqual(null); + }); + }); + + describe(types.REQUEST_JOBS, () => { + it('sets jobs loading to true', () => { + mutations[types.REQUEST_JOBS](mockedState); + + expect(mockedState.isLoadingJobs).toBe(true); + }); + }); + + describe(types.RECEIVE_JOBS_ERROR, () => { + it('sets jobs loading to false', () => { + mutations[types.RECEIVE_JOBS_ERROR](mockedState); + + expect(mockedState.isLoadingJobs).toBe(false); + }); + }); + + describe(types.RECEIVE_JOBS_SUCCESS, () => { + it('sets jobs loading to false on success', () => { + mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + + expect(mockedState.isLoadingJobs).toBe(false); + }); + + it('sets stages', () => { + mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + + expect(mockedState.stages.length).toBe(2); + expect(mockedState.stages).toEqual([ + { + title: 'test', + jobs: jasmine.anything(), + }, + { + title: 'build', + jobs: jasmine.anything(), + }, + ]); + }); + + it('sets jobs in stages', () => { + mutations[types.RECEIVE_JOBS_SUCCESS](mockedState, jobs); + + expect(mockedState.stages[0].jobs.length).toBe(3); + expect(mockedState.stages[1].jobs.length).toBe(1); + expect(mockedState.stages).toEqual([ + { + title: jasmine.anything(), + jobs: jobs.filter(job => job.stage === 'test').map(job => ({ + id: job.id, + name: job.name, + status: job.status, + stage: job.stage, + duration: job.duration, + })), + }, + { + title: jasmine.anything(), + jobs: jobs.filter(job => job.stage === 'build').map(job => ({ + id: job.id, + name: job.name, + status: job.status, + stage: job.stage, + duration: job.duration, + })), + }, + ]); + }); + }); +}); diff --git a/spec/javascripts/ide/stores/mutations/branch_spec.js b/spec/javascripts/ide/stores/mutations/branch_spec.js index 29eb859ddaf..f2f1f2a9a2e 100644 --- a/spec/javascripts/ide/stores/mutations/branch_spec.js +++ b/spec/javascripts/ide/stores/mutations/branch_spec.js @@ -37,4 +37,40 @@ describe('Multi-file store branch mutations', () => { expect(localState.projects.Example.branches.master.commit.title).toBe('Example commit'); }); }); + + describe('SET_LAST_COMMIT_PIPELINE', () => { + it('sets the pipeline for the last commit on current project', () => { + localState.projects = { + Example: { + branches: { + master: { + commit: {}, + }, + }, + }, + }; + + mutations.SET_LAST_COMMIT_PIPELINE(localState, { + projectId: 'Example', + branchId: 'master', + pipeline: { + id: '50', + details: { + status: { + icon: 'status_passed', + text: 'passed', + }, + }, + }, + }); + + expect(localState.projects.Example.branches.master.commit.pipeline.id).toBe('50'); + expect(localState.projects.Example.branches.master.commit.pipeline.details.status.text).toBe( + 'passed', + ); + expect(localState.projects.Example.branches.master.commit.pipeline.details.status.icon).toBe( + 'status_passed', + ); + }); + }); }); diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js index 0952356c2f4..648fb3e9bd3 100644 --- a/spec/javascripts/notes_spec.js +++ b/spec/javascripts/notes_spec.js @@ -974,7 +974,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; ).toBeFalsy(); expect( $tempNoteHeader - .find('.hidden-xs') + .find('.d-none.d-sm-block') .text() .trim(), ).toEqual(currentUserFullname); @@ -1020,7 +1020,7 @@ import timeoutPromise from './helpers/set_timeout_promise_helper'; const $tempNoteHeader = $tempNote.find('.note-header'); expect( $tempNoteHeader - .find('.hidden-xs') + .find('.d-none.d-sm-block') .text() .trim(), ).toEqual('Foo <script>alert("XSS")</script>'); diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js index 71f77e5f42e..1e41a7bfe7c 100644 --- a/spec/javascripts/pipelines/empty_state_spec.js +++ b/spec/javascripts/pipelines/empty_state_spec.js @@ -29,7 +29,7 @@ describe('Pipelines Empty State', () => { expect( component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '), - ).toContain('Continous Integration can help catch bugs by running your tests automatically,'); + ).toContain('Continuous Integration can help catch bugs by running your tests automatically,'); expect( component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '), diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js index a5a200973d7..03ead6cd8ba 100644 --- a/spec/javascripts/pipelines/mock_data.js +++ b/spec/javascripts/pipelines/mock_data.js @@ -217,7 +217,7 @@ export const pipelineWithStages = { browse_path: '/gitlab-org/gitlab-ee/-/jobs/62411442/artifacts/browse', }, { - name: 'codequality', + name: 'code_quality', expired: false, expire_at: '2018-04-18T14:16:24.484Z', path: '/gitlab-org/gitlab-ee/-/jobs/62411441/artifacts/download', diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js index 05ca4cb9044..68043b91bd0 100644 --- a/spec/javascripts/pipelines/pipelines_table_row_spec.js +++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js @@ -182,7 +182,16 @@ describe('Pipelines Table Row', () => { }); component.$el.querySelector('.js-pipelines-cancel-button').click(); - expect(component.isCancelling).toEqual(true); + }); + + it('renders a loading icon when `cancelingPipeline` matches pipeline id', done => { + component.cancelingPipeline = pipeline.id; + component.$nextTick() + .then(() => { + expect(component.isCancelling).toEqual(true); + }) + .then(done) + .catch(done.fail); }); }); }); diff --git a/spec/javascripts/projects_dropdown/components/search_spec.js b/spec/javascripts/projects_dropdown/components/search_spec.js index 601264258c2..427f5024e3a 100644 --- a/spec/javascripts/projects_dropdown/components/search_spec.js +++ b/spec/javascripts/projects_dropdown/components/search_spec.js @@ -92,7 +92,6 @@ describe('SearchComponent', () => { const inputEl = vm.$el.querySelector('input.form-control'); expect(vm.$el.classList.contains('search-input-container')).toBeTruthy(); - expect(vm.$el.classList.contains('hidden-xs')).toBeTruthy(); expect(inputEl).not.toBe(null); expect(inputEl.getAttribute('placeholder')).toBe('Search your projects'); expect(vm.$el.querySelector('.search-icon')).toBeDefined(); diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js index 00847df4b60..8f35b9ca437 100644 --- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js +++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js @@ -14,7 +14,9 @@ describe('SidebarMoveIssue', function () { this.$content = $(` <div class="dropdown"> <div class="js-toggle"></div> - <div class="dropdown-content"></div> + <div class="dropdown-menu"> + <div class="dropdown-content"></div> + </div> <div class="js-confirm-button"></div> </div> `); diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js index db27aa144d6..00f4f2d7c39 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_author_spec.js @@ -1,12 +1,12 @@ import Vue from 'vue'; -import authorComponent from '~/vue_merge_request_widget/components/mr_widget_author.vue'; +import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -describe('MRWidgetAuthor', () => { +describe('MrWidgetAuthor', () => { let vm; beforeEach(() => { - const Component = Vue.extend(authorComponent); + const Component = Vue.extend(MrWidgetAuthor); vm = mountComponent(Component, { author: { diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb index a547988d631..c73c6023b60 100644 --- a/spec/lib/api/helpers/pagination_spec.rb +++ b/spec/lib/api/helpers/pagination_spec.rb @@ -7,7 +7,203 @@ describe API::Helpers::Pagination do Class.new.include(described_class).new end - describe '#paginate' do + describe '#paginate (keyset pagination)' do + let(:value) { spy('return value') } + + before do + allow(value).to receive(:to_query).and_return(value) + + allow(subject).to receive(:header).and_return(value) + allow(subject).to receive(:params).and_return(value) + allow(subject).to receive(:request).and_return(value) + end + + context 'when resource can be paginated' do + let!(:projects) do + [ + create(:project, name: 'One'), + create(:project, name: 'Two'), + create(:project, name: 'Three') + ].sort_by { |e| -e.id } # sort by id desc (this is the default sort order for the API) + end + + describe 'first page' do + before do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2 }) + end + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 2 + end + + it 'returns the first two records (by id desc)' do + expect(subject.paginate(resource)).to eq(projects[0..1]) + end + + it 'adds appropriate headers' do + expect_header('X-Per-Page', '2') + expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[1].id}&pagination=keyset&per_page=2") + + expect_header('Link', anything) do |_key, val| + expect(val).to include('rel="next"') + end + + subject.paginate(resource) + end + end + + describe 'second page' do + before do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2, ks_prev_id: projects[1].id }) + end + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 1 + end + + it 'returns the third record' do + expect(subject.paginate(resource)).to eq(projects[2..2]) + end + + it 'adds appropriate headers' do + expect_header('X-Per-Page', '2') + expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[2].id}&pagination=keyset&per_page=2") + + expect_header('Link', anything) do |_key, val| + expect(val).to include('rel="next"') + end + + subject.paginate(resource) + end + end + + describe 'third page' do + before do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2, ks_prev_id: projects[2].id }) + end + + it 'returns appropriate amount of resources' do + expect(subject.paginate(resource).count).to eq 0 + end + + it 'adds appropriate headers' do + expect_header('X-Per-Page', '2') + expect(subject).not_to receive(:header).with('Link') + + subject.paginate(resource) + end + end + + context 'if order' do + context 'is not present' do + before do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2 }) + end + + it 'is not present it adds default order(:id) desc' do + resource.order_values = [] + + paginated_relation = subject.paginate(resource) + + expect(resource.order_values).to be_empty + expect(paginated_relation.order_values).to be_present + expect(paginated_relation.order_values.size).to eq(1) + expect(paginated_relation.order_values.first).to be_descending + expect(paginated_relation.order_values.first.expr.name).to eq :id + end + end + + context 'is present' do + let(:resource) { Project.all.order(name: :desc) } + let!(:projects) do + [ + create(:project, name: 'One'), + create(:project, name: 'Two'), + create(:project, name: 'Three'), + create(:project, name: 'Three'), # Note the duplicate name + create(:project, name: 'Four'), + create(:project, name: 'Five'), + create(:project, name: 'Six') + ] + + # if we sort this by name descending, id descending, this yields: + # { + # 2 => "Two", + # 4 => "Three", + # 3 => "Three", + # 7 => "Six", + # 1 => "One", + # 5 => "Four", + # 6 => "Five" + # } + # + # (key is the id) + end + + it 'it also orders by primary key' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2 }) + paginated_relation = subject.paginate(resource) + + expect(paginated_relation.order_values).to be_present + expect(paginated_relation.order_values.size).to eq(2) + expect(paginated_relation.order_values.first).to be_descending + expect(paginated_relation.order_values.first.expr.name).to eq :name + expect(paginated_relation.order_values.second).to be_descending + expect(paginated_relation.order_values.second.expr.name).to eq :id + end + + it 'it returns the right records (first page)' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', per_page: 2 }) + result = subject.paginate(resource) + + expect(result.first).to eq(projects[1]) + expect(result.second).to eq(projects[3]) + end + + it 'it returns the right records (second page)' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) + result = subject.paginate(resource) + + expect(result.first).to eq(projects[2]) + expect(result.second).to eq(projects[6]) + end + + it 'it returns the right records (third page), note increased per_page' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', ks_prev_id: projects[6].id, ks_prev_name: projects[6].name, per_page: 5 }) + result = subject.paginate(resource) + + expect(result.size).to eq(3) + expect(result.first).to eq(projects[0]) + expect(result.second).to eq(projects[4]) + expect(result.last).to eq(projects[5]) + end + + it 'it returns the right link to the next page' do + allow(subject).to receive(:params) + .and_return({ pagination: 'keyset', ks_prev_id: projects[3].id, ks_prev_name: projects[3].name, per_page: 2 }) + expect_header('X-Per-Page', '2') + expect_header('X-Next-Page', "#{value}?ks_prev_id=#{projects[6].id}&ks_prev_name=#{projects[6].name}&pagination=keyset&per_page=2") + + expect_header('Link', anything) do |_key, val| + expect(val).to include('rel="next"') + end + + subject.paginate(resource) + end + end + end + end + end + + describe '#paginate (default offset-based pagination)' do let(:value) { spy('return value') } before do @@ -146,14 +342,14 @@ describe API::Helpers::Pagination do end end end + end - def expect_header(*args, &block) - expect(subject).to receive(:header).with(*args, &block) - end + def expect_header(*args, &block) + expect(subject).to receive(:header).with(*args, &block) + end - def expect_message(method) - expect(subject).to receive(method) - .at_least(:once).and_return(value) - end + def expect_message(method) + expect(subject).to receive(method) + .at_least(:once).and_return(value) end end diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb index b918301f1cb..66af7f81535 100644 --- a/spec/lib/api/helpers/related_resources_helpers_spec.rb +++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb @@ -9,9 +9,9 @@ describe API::Helpers::RelatedResourcesHelpers do let(:path) { '/api/v4/awesome_endpoint' } subject(:url) { helpers.expose_url(path) } - def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil) + def stub_default_url_options(protocol: 'http', host: 'example.com', port: nil, script_name: '') expect(Gitlab::Application.routes).to receive(:default_url_options) - .and_return(protocol: protocol, host: host, port: port) + .and_return(protocol: protocol, host: host, port: port, script_name: script_name) end it 'respects the protocol if it is HTTP' do @@ -37,5 +37,17 @@ describe API::Helpers::RelatedResourcesHelpers do is_expected.to start_with('http://example.com:8080/') end + + it 'includes the relative_url before the path if it is set' do + stub_default_url_options(script_name: '/gitlab') + + is_expected.to start_with('http://example.com/gitlab/api/v4') + end + + it 'includes the path after the host' do + stub_default_url_options + + is_expected.to start_with('http://example.com/api/v4') + end end end diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index 84688845fa5..23c04a1a101 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -5,6 +5,8 @@ describe Backup::Manager do let(:progress) { StringIO.new } + subject { described_class.new(progress) } + before do allow(progress).to receive(:puts) allow(progress).to receive(:print) diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index b1ea9c0b622..a44243ac82d 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe Backup::Repository do let(:progress) { StringIO.new } let!(:project) { create(:project, :wiki_repo) } + subject { described_class.new(progress) } before do allow(progress).to receive(:puts) @@ -24,14 +25,12 @@ describe Backup::Repository do end it 'does not raise error' do - expect { described_class.new.dump }.not_to raise_error + expect { subject.dump }.not_to raise_error end end end describe '#restore' do - subject { described_class.new } - let(:timestamp) { Time.utc(2017, 3, 22) } let(:temp_dirs) do Gitlab.config.repositories.storages.map do |name, storage| @@ -49,14 +48,14 @@ describe Backup::Repository do describe 'command failure' do before do - allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1]) + allow_any_instance_of(Gitlab::Shell).to receive(:create_repository).and_return(false) end context 'hashed storage' do it 'shows the appropriate error' do subject.restore - expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} (#{project.disk_path}) - error") + expect(progress).to have_received(:puts).with("[Failed] restoring #{project.full_path} repository") end end @@ -66,33 +65,10 @@ describe Backup::Repository do it 'shows the appropriate error' do subject.restore - expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error") + expect(progress).to have_received(:puts).with("[Failed] restoring #{project.full_path} repository") end end end - - describe 'folders without permissions' do - before do - allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES) - end - - it 'shows error message' do - expect(subject).to receive(:access_denied_error) - subject.restore - end - end - - describe 'folder that is a mountpoint' do - before do - allow(FileUtils).to receive(:mv).and_raise(Errno::EBUSY) - end - - it 'shows error message' do - expect(subject).to receive(:resource_busy_error).and_call_original - - expect { subject.restore }.to raise_error(/is a mountpoint/) - end - end end describe '#empty_repo?' do @@ -102,20 +78,20 @@ describe Backup::Repository do it 'invalidates the emptiness cache' do expect(wiki.repository).to receive(:expire_emptiness_caches).once - described_class.new.send(:empty_repo?, wiki) + subject.send(:empty_repo?, wiki) end context 'wiki repo has content' do let!(:wiki_page) { create(:wiki_page, wiki: wiki) } it 'returns true, regardless of bad cache value' do - expect(described_class.new.send(:empty_repo?, wiki)).to be(false) + expect(subject.send(:empty_repo?, wiki)).to be(false) end end context 'wiki repo does not have content' do it 'returns true, regardless of bad cache value' do - expect(described_class.new.send(:empty_repo?, wiki)).to be_truthy + expect(subject.send(:empty_repo?, wiki)).to be_truthy end end end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 392905076dc..b30f3661e70 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -59,7 +59,7 @@ describe Banzai::Filter::LabelReferenceFilter do describe 'label span element' do it 'includes default classes' do doc = reference_filter("Label #{reference}") - expect(doc.css('a span').first.attr('class')).to eq 'label color-label has-tooltip' + expect(doc.css('a span').first.attr('class')).to eq 'badge color-label has-tooltip' end it 'includes a style attribute' do diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb index 4ddcbd7eb66..19028495f52 100644 --- a/spec/lib/gitlab/current_settings_spec.rb +++ b/spec/lib/gitlab/current_settings_spec.rb @@ -1,17 +1,15 @@ require 'spec_helper' describe Gitlab::CurrentSettings do - include StubENV - before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') end - describe '#current_application_settings' do + describe '#current_application_settings', :use_clean_rails_memory_store_caching do it 'allows keys to be called directly' do db_settings = create(:application_setting, - home_page_url: 'http://mydomain.com', - signup_enabled: false) + home_page_url: 'http://mydomain.com', + signup_enabled: false) expect(described_class.home_page_url).to eq(db_settings.home_page_url) expect(described_class.signup_enabled?).to be_falsey @@ -19,46 +17,54 @@ describe Gitlab::CurrentSettings do expect(described_class.metrics_sample_interval).to be(15) end - context 'with DB available' do + context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do before do - # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues - # during the initialization phase of the test suite, so instead let's mock the internals of it - allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true) - allow(ActiveRecord::Base.connection).to receive(:table_exists?).and_call_original - allow(ActiveRecord::Base.connection).to receive(:table_exists?).with('application_settings').and_return(true) + stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') end - it 'attempts to use cached values first' do - expect(ApplicationSetting).to receive(:cached) + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) expect(described_class.current_application_settings).to be_a(ApplicationSetting) + expect(described_class.current_application_settings).not_to be_persisted end + end - it 'falls back to DB if Redis returns an empty value' do - expect(ApplicationSetting).to receive(:cached).and_return(nil) - expect(ApplicationSetting).to receive(:last).and_call_original.twice - - expect(described_class.current_application_settings).to be_a(ApplicationSetting) + context 'with DB unavailable' do + before do + # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues + # during the initialization phase of the test suite, so instead let's mock the internals of it + allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false) end - it 'falls back to DB if Redis fails' do - db_settings = ApplicationSetting.create!(ApplicationSetting.defaults) + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError) - expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError) + expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) + end + end - expect(described_class.current_application_settings).to eq(db_settings) + context 'with DB available' do + # This method returns the ::ApplicationSetting.defaults hash + # but with respect of custom attribute accessors of ApplicationSetting model + def settings_from_defaults + ar_wrapped_defaults = ::ApplicationSetting.build_from_defaults.attributes + ar_wrapped_defaults.slice(*::ApplicationSetting.defaults.keys) end - it 'creates default ApplicationSettings if none are present' do - expect(ApplicationSetting).to receive(:cached).and_raise(::Redis::BaseError) - expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(Redis::BaseError) + before do + # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(true)` causes issues + # during the initialization phase of the test suite, so instead let's mock the internals of it + allow(ActiveRecord::Base.connection).to receive(:active?).and_return(true) + allow(ActiveRecord::Base.connection).to receive(:cached_table_exists?).with('application_settings').and_return(true) + end + it 'creates default ApplicationSettings if none are present' do settings = described_class.current_application_settings expect(settings).to be_a(ApplicationSetting) expect(settings).to be_persisted - expect(settings).to have_attributes(ApplicationSetting.defaults) + expect(settings).to have_attributes(settings_from_defaults) end context 'with migrations pending' do @@ -69,7 +75,7 @@ describe Gitlab::CurrentSettings do it 'returns an in-memory ApplicationSetting object' do settings = described_class.current_application_settings - expect(settings).to be_a(OpenStruct) + expect(settings).to be_a(Gitlab::FakeApplicationSettings) expect(settings.sign_in_enabled?).to eq(settings.sign_in_enabled) expect(settings.sign_up_enabled?).to eq(settings.sign_up_enabled) end @@ -81,7 +87,7 @@ describe Gitlab::CurrentSettings do settings = described_class.current_application_settings app_defaults = ApplicationSetting.last - expect(settings).to be_a(OpenStruct) + expect(settings).to be_a(Gitlab::FakeApplicationSettings) expect(settings.home_page_url).to eq(db_settings.home_page_url) expect(settings.signup_enabled?).to be_falsey expect(settings.signup_enabled).to be_falsey @@ -91,34 +97,29 @@ describe Gitlab::CurrentSettings do settings.each { |key, _| expect(settings[key]).to eq(app_defaults[key]) } end end - end - - context 'with DB unavailable' do - before do - # For some reason, `allow(described_class).to receive(:connect_to_db?).and_return(false)` causes issues - # during the initialization phase of the test suite, so instead let's mock the internals of it - allow(ActiveRecord::Base.connection).to receive(:active?).and_return(false) - end - it 'returns an in-memory ApplicationSetting object' do - expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).not_to receive(:last) + context 'when ApplicationSettings.current is present' do + it 'returns the existing application settings' do + expect(ApplicationSetting).to receive(:current).and_return(:current_settings) - expect(described_class.current_application_settings).to be_a(OpenStruct) + expect(described_class.current_application_settings).to eq(:current_settings) + end end - end - context 'when ENV["IN_MEMORY_APPLICATION_SETTINGS"] is true' do - before do - stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'true') + context 'when the application_settings table does not exists' do + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::StatementInvalid) + + expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) + end end - it 'returns an in-memory ApplicationSetting object' do - expect(ApplicationSetting).not_to receive(:current) - expect(ApplicationSetting).not_to receive(:last) + context 'when the application_settings table is not fully migrated' do + it 'returns an in-memory ApplicationSetting object' do + expect(ApplicationSetting).to receive(:create_from_defaults).and_raise(ActiveRecord::UnknownAttributeError) - expect(described_class.current_application_settings).to be_a(ApplicationSetting) - expect(described_class.current_application_settings).not_to be_persisted + expect(described_class.current_application_settings).to be_a(Gitlab::FakeApplicationSettings) + end end end end diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb index 452249210b0..154ab4b3856 100644 --- a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -46,6 +46,20 @@ describe Gitlab::Email::Handler::CreateIssueHandler do expect(issue.description).to eq('') end end + + context "when there are quotes in email" do + let(:email_raw) { fixture_file("emails/valid_new_issue_with_quote.eml") } + + it "creates a new issue" do + expect { receiver.execute }.to change { project.issues.count }.by(1) + issue = project.issues.last + + expect(issue.author).to eq(user) + expect(issue.title).to eq('New Issue by email') + expect(issue.description).to include('reply by email') + expect(issue.description).to include('> this is a quote') + end + end end context "something is wrong" do diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index e21a998adfe..0989188f7ee 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -3,8 +3,8 @@ require "spec_helper" # Inspired in great part by Discourse's Email::Receiver describe Gitlab::Email::ReplyParser do describe '#execute' do - def test_parse_body(mail_string) - described_class.new(Mail::Message.new(mail_string)).execute + def test_parse_body(mail_string, params = {}) + described_class.new(Mail::Message.new(mail_string), params).execute end it "returns an empty string if the message is blank" do @@ -212,5 +212,19 @@ describe Gitlab::Email::ReplyParser do it "does not wrap links with no href in unnecessary brackets" do expect(test_parse_body(fixture_file("emails/html_empty_link.eml"))).to eq("no brackets!") end + + it "does not trim reply if trim_reply option is false" do + expect(test_parse_body(fixture_file("emails/valid_new_issue_with_quote.eml"), { trim_reply: false })) + .to eq( + <<-BODY.strip_heredoc.chomp + The reply by email functionality should be extended to allow creating a new issue by email. + even when the email is forwarded to the project which may include lines that begin with ">" + + there should be a quote below this line: + + > this is a quote + BODY + ) + end end end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 6d63749296e..4f64f2bd6b4 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6760,26 +6760,6 @@ "wiki_page_events": true }, { - "id": 92, - "title": "Gemnasium", - "project_id": 5, - "created_at": "2016-06-14T15:01:51.202Z", - "updated_at": "2016-06-14T15:01:51.202Z", - "active": false, - "properties": {}, - "template": false, - "push_events": true, - "issues_events": true, - "merge_requests_events": true, - "tag_push_events": true, - "note_events": true, - "job_events": true, - "type": "GemnasiumService", - "category": "common", - "default": false, - "wiki_page_events": true - }, - { "id": 91, "title": "Flowdock", "project_id": 5, diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb index da146e24893..d63f448883b 100644 --- a/spec/lib/gitlab_spec.rb +++ b/spec/lib/gitlab_spec.rb @@ -8,6 +8,66 @@ describe Gitlab do expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__))) end end + describe '.revision' do + let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] } + + around do |example| + described_class.instance_variable_set(:@_revision, nil) + example.run + described_class.instance_variable_set(:@_revision, nil) + end + + context 'when a REVISION file exists' do + before do + expect(File).to receive(:exist?) + .with(described_class.root.join('REVISION')) + .and_return(true) + end + + it 'returns the actual Git revision' do + expect(File).to receive(:read) + .with(described_class.root.join('REVISION')) + .and_return("abc123\n") + + expect(described_class.revision).to eq('abc123') + end + + it 'memoizes the revision' do + expect(File).to receive(:read) + .once + .with(described_class.root.join('REVISION')) + .and_return("abc123\n") + + 2.times { described_class.revision } + end + end + + context 'when no REVISION file exist' do + context 'when the Git command succeeds' do + before do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with(cmd) + .and_return(Gitlab::Popen::Result.new(cmd, 'abc123', '', double(success?: true))) + end + + it 'returns the actual Git revision' do + expect(described_class.revision).to eq('abc123') + end + end + + context 'when the Git command fails' do + before do + expect(Gitlab::Popen).to receive(:popen_with_detail) + .with(cmd) + .and_return(Gitlab::Popen::Result.new(cmd, '', 'fatal: Not a git repository', double('Process::Status', success?: false))) + end + + it 'returns "Unknown"' do + expect(described_class.revision).to eq('Unknown') + end + end + end + end describe '.com?' do it 'is true when on GitLab.com' do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 8a52c151cc4..f310a6854d5 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -390,6 +390,46 @@ describe Notify do end end + describe 'that are unmergeable' do + set(:merge_request) do + create(:merge_request, :conflict, + source_project: project, + target_project: project, + author: current_user, + assignee: assignee, + description: 'Awesome description') + end + + subject { described_class.merge_request_unmergeable_email(recipient.id, merge_request.id) } + + it_behaves_like 'a multiple recipients email' + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { merge_request } + end + it_behaves_like 'it should show Gmail Actions View Merge request link' + it_behaves_like 'an unsubscribeable thread' + + it 'is sent as the merge request author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(merge_request.author.name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'has the correct subject and body' do + reasons = %w[foo bar] + + allow_any_instance_of(MergeRequestPresenter).to receive(:unmergeable_reasons).and_return(reasons) + aggregate_failures do + is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_body_text(project_merge_request_path(project, merge_request)) + is_expected.to have_body_text('reasons:') + reasons.each do |reason| + is_expected.to have_body_text(reason) + end + end + end + end + shared_examples 'a push to an existing merge request' do let(:push_user) { create(:user) } diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index 5489c17bd82..77b07cf1ac9 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -3,34 +3,11 @@ require 'rails_helper' describe Appearance do subject { build(:appearance) } - it { is_expected.to be_valid } + it { include(CacheableAttributes) } + it { expect(described_class.current_without_cache).to eq(described_class.first) } it { is_expected.to have_many(:uploads) } - describe '.current', :use_clean_rails_memory_store_caching do - let!(:appearance) { create(:appearance) } - - it 'returns the current appearance row' do - expect(described_class.current).to eq(appearance) - end - - it 'caches the result' do - expect(described_class).to receive(:first).once - - 2.times { described_class.current } - end - end - - describe '#flush_redis_cache' do - it 'flushes the cache in Redis' do - appearance = create(:appearance) - - expect(Rails.cache).to receive(:delete).with(described_class::CACHE_KEY) - - appearance.flush_redis_cache - end - end - describe '#single_appearance_row' do it 'adds an error when more than 1 row exists' do create(:appearance) diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 10d6109cae7..7e47043a1cb 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -3,6 +3,9 @@ require 'spec_helper' describe ApplicationSetting do let(:setting) { described_class.create_from_defaults } + it { include(CacheableAttributes) } + it { expect(described_class.current_without_cache).to eq(described_class.last) } + it { expect(setting).to be_valid } it { expect(setting.uuid).to be_present } it { expect(setting).to have_db_column(:auto_devops_enabled) } @@ -318,33 +321,6 @@ describe ApplicationSetting do end end - describe '.current' do - context 'redis unavailable' do - it 'returns an ApplicationSetting' do - allow(Rails.cache).to receive(:fetch).and_call_original - allow(described_class).to receive(:last).and_return(:last) - expect(Rails.cache).to receive(:fetch).with(ApplicationSetting::CACHE_KEY).and_raise(ArgumentError) - - expect(described_class.current).to eq(:last) - end - end - - context 'when an ApplicationSetting is not yet present' do - it 'does not cache nil object' do - # when missing settings a nil object is returned, but not cached - allow(described_class).to receive(:last).and_return(nil).twice - expect(described_class.current).to be_nil - - # when the settings are set the method returns a valid object - allow(described_class).to receive(:last).and_return(:last) - expect(described_class.current).to eq(:last) - - # subsequent calls get everything from cache - expect(described_class.current).to eq(:last) - end - end - end - context 'restrict creating duplicates' do before do described_class.create_from_defaults diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 67919a0d6b4..c89655f4058 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1546,7 +1546,7 @@ describe Ci::Build do { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true }, { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, - { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }, + { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true }, { key: 'CI_JOB_NAME', value: 'test', public: true }, { key: 'CI_JOB_STAGE', value: 'test', public: true }, { key: 'CI_COMMIT_SHA', value: build.sha, public: true }, diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb index 407e2fc598a..d2302583ac8 100644 --- a/spec/models/clusters/applications/prometheus_spec.rb +++ b/spec/models/clusters/applications/prometheus_spec.rb @@ -79,7 +79,7 @@ describe Clusters::Applications::Prometheus do end it 'creates proper url' do - expect(subject.prometheus_client.url).to eq('http://example.com/api/v1/proxy/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80') + expect(subject.prometheus_client.url).to eq('http://example.com/api/v1/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80/proxy') end it 'copies options and headers from kube client to proxy client' do diff --git a/spec/models/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb new file mode 100644 index 00000000000..49e4b23ebc7 --- /dev/null +++ b/spec/models/concerns/cacheable_attributes_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' + +describe CacheableAttributes do + let(:minimal_test_class) do + Class.new do + include ActiveModel::Model + extend ActiveModel::Callbacks + define_model_callbacks :commit + include CacheableAttributes + + def self.name + 'TestClass' + end + + def self.first + @_first ||= new('foo' => 'a') + end + + def self.last + @_last ||= new('foo' => 'a', 'bar' => 'b') + end + + attr_accessor :attributes + + def initialize(attrs = {}) + @attributes = attrs + end + end + end + + shared_context 'with defaults' do + before do + minimal_test_class.define_singleton_method(:defaults) do + { foo: 'a', bar: 'b', baz: 'c' } + end + end + end + + describe '.current_without_cache' do + it 'defaults to last' do + expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.last) + end + + it 'can be overriden' do + minimal_test_class.define_singleton_method(:current_without_cache) do + first + end + + expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.first) + end + end + + describe '.cache_key' do + it 'excludes cache attributes' do + expect(minimal_test_class.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Gitlab.migrations_hash}:json") + end + end + + describe '.defaults' do + it 'defaults to {}' do + expect(minimal_test_class.defaults).to eq({}) + end + + context 'with defaults defined' do + include_context 'with defaults' + + it 'can be overriden' do + expect(minimal_test_class.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' }) + end + end + end + + describe '.build_from_defaults' do + include_context 'with defaults' + + context 'without any attributes given' do + it 'intializes a new object with the defaults' do + expect(minimal_test_class.build_from_defaults).not_to be_persisted + end + end + + context 'without attributes given' do + it 'intializes a new object with the given attributes merged into the defaults' do + expect(minimal_test_class.build_from_defaults(foo: 'd').attributes[:foo]).to eq('d') + end + end + end + + describe '.current', :use_clean_rails_memory_store_caching do + context 'redis unavailable' do + it 'returns an uncached record' do + allow(minimal_test_class).to receive(:last).and_return(:last) + expect(Rails.cache).to receive(:read).and_raise(Redis::BaseError) + + expect(minimal_test_class.current).to eq(:last) + end + end + + context 'when a record is not yet present' do + it 'does not cache nil object' do + # when missing settings a nil object is returned, but not cached + allow(minimal_test_class).to receive(:last).twice.and_return(nil) + + expect(minimal_test_class.current).to be_nil + expect(Rails.cache.exist?(minimal_test_class.cache_key)).to be(false) + end + + it 'cache non-nil object' do + # when the settings are set the method returns a valid object + allow(minimal_test_class).to receive(:last).and_call_original + + expect(minimal_test_class.current).to eq(minimal_test_class.last) + expect(Rails.cache.exist?(minimal_test_class.cache_key)).to be(true) + + # subsequent calls retrieve the record from the cache + last_record = minimal_test_class.last + expect(minimal_test_class).not_to receive(:last) + expect(minimal_test_class.current.attributes).to eq(last_record.attributes) + end + end + end + + describe '.cached', :use_clean_rails_memory_store_caching do + context 'when cache is cold' do + it 'returns nil' do + expect(minimal_test_class.cached).to be_nil + end + end + + context 'when cached settings do not include the latest defaults' do + before do + Rails.cache.write(minimal_test_class.cache_key, { bar: 'b', baz: 'c' }.to_json) + minimal_test_class.define_singleton_method(:defaults) do + { foo: 'a', bar: 'b', baz: 'c' } + end + end + + it 'includes attributes from defaults' do + expect(minimal_test_class.cached.attributes[:foo]).to eq(minimal_test_class.defaults[:foo]) + end + end + end + + describe '#cache!', :use_clean_rails_memory_store_caching do + let(:appearance_record) { create(:appearance) } + + it 'caches the attributes' do + appearance_record.cache! + + expect(Rails.cache.read(Appearance.cache_key)).to eq(appearance_record.attributes.to_json) + end + end +end diff --git a/spec/models/concerns/resolvable_note_spec.rb b/spec/models/concerns/resolvable_note_spec.rb index 91591017587..fcb5250278e 100644 --- a/spec/models/concerns/resolvable_note_spec.rb +++ b/spec/models/concerns/resolvable_note_spec.rb @@ -326,4 +326,12 @@ describe Note, ResolvableNote do end end end + + describe "#potentially_resolvable?" do + it 'returns false if noteable could not be found' do + allow(subject).to receive(:noteable).and_return(nil) + + expect(subject.potentially_resolvable?).to be_falsey + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c2ab1259ebf..aa968dda41f 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1246,38 +1246,50 @@ describe MergeRequest do describe '#check_if_can_be_merged' do let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) } - subject { create(:merge_request, source_project: project, merge_status: :unchecked) } + shared_examples 'checking if can be merged' do + context 'when it is not broken and has no conflicts' do + before do + allow(subject).to receive(:broken?) { false } + allow(project.repository).to receive(:can_be_merged?).and_return(true) + end - context 'when it is not broken and has no conflicts' do - before do - allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?).and_return(true) + it 'is marked as mergeable' do + expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') + end end - it 'is marked as mergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') - end - end + context 'when broken' do + before do + allow(subject).to receive(:broken?) { true } + end - context 'when broken' do - before do - allow(subject).to receive(:broken?) { true } + it 'becomes unmergeable' do + expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') + end end - it 'becomes unmergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') + context 'when it has conflicts' do + before do + allow(subject).to receive(:broken?) { false } + allow(project.repository).to receive(:can_be_merged?).and_return(false) + end + + it 'becomes unmergeable' do + expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') + end end end - context 'when it has conflicts' do - before do - allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?).and_return(false) - end + context 'when merge_status is unchecked' do + subject { create(:merge_request, source_project: project, merge_status: :unchecked) } - it 'becomes unmergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') - end + it_behaves_like 'checking if can be merged' + end + + context 'when merge_status is unchecked' do + subject { create(:merge_request, source_project: project, merge_status: :cannot_be_merged_recheck) } + + it_behaves_like 'checking if can be merged' end end @@ -2065,6 +2077,53 @@ describe MergeRequest do expect(subject.merge_jid).to be_nil end end + + describe 'transition to cannot_be_merged' do + let(:notification_service) { double(:notification_service) } + let(:todo_service) { double(:todo_service) } + + subject { create(:merge_request, merge_status: :unchecked) } + + before do + allow(NotificationService).to receive(:new).and_return(notification_service) + allow(TodoService).to receive(:new).and_return(todo_service) + end + + it 'notifies, but does not notify again if rechecking still results in cannot_be_merged' do + expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once + expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once + + subject.mark_as_unmergeable + subject.mark_as_unchecked + subject.mark_as_unmergeable + end + + it 'notifies whenever merge request is newly unmergeable' do + expect(notification_service).to receive(:merge_request_unmergeable).with(subject).twice + expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).twice + + subject.mark_as_unmergeable + subject.mark_as_unchecked + subject.mark_as_mergeable + subject.mark_as_unchecked + subject.mark_as_unmergeable + end + end + + describe 'check_state?' do + it 'indicates whether MR is still checking for mergeability' do + state_machine = described_class.state_machines[:merge_status] + check_states = [:unchecked, :cannot_be_merged_recheck] + + check_states.each do |merge_status| + expect(state_machine.check_state?(merge_status)).to be true + end + + (state_machine.states.map(&:name) - check_states).each do |merge_status| + expect(state_machine.check_state?(merge_status)).to be false + end + end + end end describe '#should_be_rebased?' do @@ -2196,4 +2255,39 @@ describe MergeRequest do expect(merge_request.can_allow_maintainer_to_push?(user)).to be_truthy end end + + describe '#merge_participants' do + it 'contains author' do + expect(subject.merge_participants).to eq([subject.author]) + end + + describe 'when merge_when_pipeline_succeeds? is true' do + describe 'when merge user is author' do + let(:user) { create(:user) } + subject do + create(:merge_request, + merge_when_pipeline_succeeds: true, + merge_user: user, + author: user) + end + + it 'contains author only' do + expect(subject.merge_participants).to eq([subject.author]) + end + end + + describe 'when merge user and author are different users' do + let(:merge_user) { create(:user) } + subject do + create(:merge_request, + merge_when_pipeline_succeeds: true, + merge_user: merge_user) + end + + it 'contains author and merge user' do + expect(subject.merge_participants).to eq([subject.author, merge_user]) + end + end + end + end end diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 4c61bc0af95..6e417323e40 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -26,24 +26,49 @@ describe GemnasiumService do end end + describe "deprecated?" do + let(:project) { create(:project, :repository) } + let(:gemnasium_service) { described_class.new } + + before do + allow(gemnasium_service).to receive_messages( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret', + api_key: 'GemnasiumUserApiKey' + ) + end + + it "is true" do + expect(gemnasium_service.deprecated?).to be true + end + + it "can't create a new service" do + expect(gemnasium_service.save).to be false + expect(gemnasium_service.errors[:base].first) + .to eq('Gemnasium has been acquired by GitLab in January 2018. Since May 15, 2018, the service provided by Gemnasium is no longer available.') + end + end + describe "Execute" do let(:user) { create(:user) } let(:project) { create(:project, :repository) } + let(:gemnasium_service) { described_class.new } + let(:sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } before do - @gemnasium_service = described_class.new - allow(@gemnasium_service).to receive_messages( + allow(gemnasium_service).to receive_messages( project_id: project.id, project: project, service_hook: true, token: 'verySecret', api_key: 'GemnasiumUserApiKey' ) - @sample_data = Gitlab::DataBuilder::Push.build_sample(project, user) end it "calls Gemnasium service" do expect(Gemnasium::GitlabService).to receive(:execute).with(an_instance_of(Hash)).once - @gemnasium_service.execute(@sample_data) + gemnasium_service.execute(sample_data) end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 39625b559eb..af2240f4f89 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -469,6 +469,34 @@ describe Project do end end + describe "#readme_url" do + let(:project) { create(:project, :repository, path: "somewhere") } + + context 'with a non-existing repository' do + it 'returns nil' do + allow(project.repository).to receive(:tree).with(:head).and_return(nil) + + expect(project.readme_url).to be_nil + end + end + + context 'with an existing repository' do + context 'when no README exists' do + it 'returns nil' do + allow_any_instance_of(Tree).to receive(:readme).and_return(nil) + + expect(project.readme_url).to be_nil + end + end + + context 'when a README exists' do + it 'returns the README' do + expect(project.readme_url).to eql("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere/blob/master/README.md") + end + end + end + end + describe "#new_issuable_address" do let(:project) { create(:project, path: "somewhere") } let(:user) { create(:user) } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 6bc148a1392..0ccf55bd895 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2031,27 +2031,27 @@ describe Repository do describe '#xcode_project?' do before do - allow(repository).to receive(:tree).with(:head).and_return(double(:tree, blobs: [blob])) + allow(repository).to receive(:tree).with(:head).and_return(double(:tree, trees: [tree])) end - context 'when the root contains a *.xcodeproj file' do - let(:blob) { double(:blob, path: 'Foo.xcodeproj') } + context 'when the root contains a *.xcodeproj directory' do + let(:tree) { double(:tree, path: 'Foo.xcodeproj') } it 'returns true' do expect(repository.xcode_project?).to be_truthy end end - context 'when the root contains a *.xcworkspace file' do - let(:blob) { double(:blob, path: 'Foo.xcworkspace') } + context 'when the root contains a *.xcworkspace directory' do + let(:tree) { double(:tree, path: 'Foo.xcworkspace') } it 'returns true' do expect(repository.xcode_project?).to be_truthy end end - context 'when the root contains no XCode config file' do - let(:blob) { double(:blob, path: 'subdir/Foo.xcworkspace') } + context 'when the root contains no Xcode config directory' do + let(:tree) { double(:tree, path: 'Foo') } it 'returns false' do expect(repository.xcode_project?).to be_falsey diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 684fa030baf..6a2f4a39f09 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -393,24 +393,6 @@ describe User do end describe 'after commit hook' do - describe '.update_invalid_gpg_signatures' do - let(:user) do - create(:user, email: 'tula.torphy@abshire.ca').tap do |user| - user.skip_reconfirmation! - end - end - - it 'does nothing when the name is updated' do - expect(user).not_to receive(:update_invalid_gpg_signatures) - user.update_attributes!(name: 'Bette') - end - - it 'synchronizes the gpg keys when the email is updated' do - expect(user).to receive(:update_invalid_gpg_signatures).at_most(:twice) - user.update_attributes!(email: 'shawnee.ritchie@denesik.com') - end - end - describe '#update_emails_with_primary_email' do before do @user = create(:user, email: 'primary@example.com').tap do |user| @@ -450,6 +432,76 @@ describe User do expect(@user.emails.first.confirmed_at).not_to eq nil end end + + describe '#update_notification_email' do + # Regression: https://gitlab.com/gitlab-org/gitlab-ce/issues/22846 + context 'when changing :email' do + let(:user) { create(:user) } + let(:new_email) { 'new-email@example.com' } + + it 'sets :unconfirmed_email' do + expect do + user.tap { |u| u.update!(email: new_email) }.reload + end.to change(user, :unconfirmed_email).to(new_email) + end + + it 'does not change :notification_email' do + expect do + user.tap { |u| u.update!(email: new_email) }.reload + end.not_to change(user, :notification_email) + end + + it 'updates :notification_email to the new email once confirmed' do + user.update!(email: new_email) + + expect do + user.tap(&:confirm).reload + end.to change(user, :notification_email).to eq(new_email) + end + + context 'and :notification_email is set to a secondary email' do + let!(:email_attrs) { attributes_for(:email, :confirmed, user: user) } + let(:secondary) { create(:email, :confirmed, email: 'secondary@example.com', user: user) } + + before do + user.emails.create(email_attrs) + user.tap { |u| u.update!(notification_email: email_attrs[:email]) }.reload + end + + it 'does not change :notification_email to :email' do + expect do + user.tap { |u| u.update!(email: new_email) }.reload + end.not_to change(user, :notification_email) + end + + it 'does not change :notification_email to :email once confirmed' do + user.update!(email: new_email) + + expect do + user.tap(&:confirm).reload + end.not_to change(user, :notification_email) + end + end + end + end + + describe '#update_invalid_gpg_signatures' do + let(:user) do + create(:user, email: 'tula.torphy@abshire.ca').tap do |user| + user.skip_reconfirmation! + end + end + + it 'does nothing when the name is updated' do + expect(user).not_to receive(:update_invalid_gpg_signatures) + user.update_attributes!(name: 'Bette') + end + + it 'synchronizes the gpg keys when the email is updated' do + expect(user).to receive(:update_invalid_gpg_signatures).at_most(:twice) + user.update_attributes!(email: 'shawnee.ritchie@denesik.com') + end + end end describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index e3b37739e8e..d5fb4a7742c 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -70,6 +70,41 @@ describe MergeRequestPresenter do end end + describe "#unmergeable_reasons" do + let(:presenter) { described_class.new(resource, current_user: user) } + + before do + # Mergeable base state + allow(resource).to receive(:has_no_commits?).and_return(false) + allow(resource).to receive(:source_branch_exists?).and_return(true) + allow(resource).to receive(:target_branch_exists?).and_return(true) + allow(resource.project.repository).to receive(:can_be_merged?).and_return(true) + end + + it "handles mergeable request" do + expect(presenter.unmergeable_reasons).to eq([]) + end + + it "handles no commit" do + allow(resource).to receive(:has_no_commits?).and_return(true) + + expect(presenter.unmergeable_reasons).to eq(["no commits"]) + end + + it "handles branches missing" do + allow(resource).to receive(:source_branch_exists?).and_return(false) + allow(resource).to receive(:target_branch_exists?).and_return(false) + + expect(presenter.unmergeable_reasons).to eq(["source branch is missing", "target branch is missing"]) + end + + it "handles merge conflict" do + allow(resource.project.repository).to receive(:can_be_merged?).and_return(false) + + expect(presenter.unmergeable_reasons).to eq(["has merge conflicts"]) + end + end + describe '#conflict_resolution_path' do let(:project) { create :project } let(:user) { create :user } diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 53d48a91007..fdddca5d0ef 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -15,7 +15,7 @@ describe API::Environments do it 'returns project environments' do project_data_keys = %w( id description default_branch tag_list - ssh_url_to_repo http_url_to_repo web_url + ssh_url_to_repo http_url_to_repo web_url readme_url name name_with_namespace path path_with_namespace star_count forks_count diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 85a571b8f0e..9b7c3205c1f 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -18,7 +18,7 @@ describe API::Projects do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:project, :repository, namespace: user.namespace) } let(:project2) { create(:project, namespace: user.namespace) } let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') } let(:project_member) { create(:project_member, :developer, user: user3, project: project) } @@ -220,7 +220,7 @@ describe API::Projects do it 'returns a simplified version of all the projects' do expected_keys = %w( id description default_branch tag_list - ssh_url_to_repo http_url_to_repo web_url + ssh_url_to_repo http_url_to_repo web_url readme_url name name_with_namespace path path_with_namespace star_count forks_count @@ -854,6 +854,7 @@ describe API::Projects do expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds) expect(json_response['only_allow_merge_if_all_discussions_are_resolved']).to eq(project.only_allow_merge_if_all_discussions_are_resolved) expect(json_response['merge_method']).to eq(project.merge_method.to_s) + expect(json_response['readme_url']).to eq(project.readme_url) end it 'returns a project by path name' do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index e8196980a8c..05637eb0729 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -488,10 +488,6 @@ describe API::Users do describe "PUT /users/:id" do let!(:admin_user) { create(:admin) } - before do - admin - end - it "updates user with new bio" do put api("/users/#{user.id}", admin), { bio: 'new test bio' } @@ -525,27 +521,28 @@ describe API::Users do expect(json_response['avatar_url']).to include(user.avatar_path) end - it 'updates user with his own email' do - put api("/users/#{user.id}", admin), email: user.email - - expect(response).to have_gitlab_http_status(200) - expect(json_response['email']).to eq(user.email) - expect(user.reload.email).to eq(user.email) - end - it 'updates user with a new email' do + old_email = user.email + old_notification_email = user.notification_email put api("/users/#{user.id}", admin), email: 'new@email.com' + user.reload + expect(response).to have_gitlab_http_status(200) - expect(user.reload.notification_email).to eq('new@email.com') + expect(user).to be_confirmed + expect(user.email).to eq(old_email) + expect(user.notification_email).to eq(old_notification_email) + expect(user.unconfirmed_email).to eq('new@email.com') end it 'skips reconfirmation when requested' do - put api("/users/#{user.id}", admin), { skip_reconfirmation: true } + put api("/users/#{user.id}", admin), email: 'new@email.com', skip_reconfirmation: true user.reload - expect(user.confirmed_at).to be_present + expect(response).to have_gitlab_http_status(200) + expect(user).to be_confirmed + expect(user.email).to eq('new@email.com') end it 'updates user with his own username' do diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index 4c25bd935c6..158ddf171bc 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -82,7 +82,7 @@ describe API::V3::Projects do it 'returns a simplified version of all the projects' do expected_keys = %w( id description default_branch tag_list - ssh_url_to_repo http_url_to_repo web_url + ssh_url_to_repo http_url_to_repo web_url readme_url name name_with_namespace path path_with_namespace star_count forks_count diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb index 7bbf34422b8..38b618191fb 100644 --- a/spec/requests/api/version_spec.rb +++ b/spec/requests/api/version_spec.rb @@ -18,7 +18,7 @@ describe API::Version do expect(response).to have_gitlab_http_status(200) expect(json_response['version']).to eq(Gitlab::VERSION) - expect(json_response['revision']).to eq(Gitlab::REVISION) + expect(json_response['revision']).to eq(Gitlab.revision) end end end diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb index 473dbcbb692..bca1e71bef2 100644 --- a/spec/services/clusters/applications/schedule_installation_service_spec.rb +++ b/spec/services/clusters/applications/schedule_installation_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Clusters::Applications::ScheduleInstallationService do def count_scheduled - application_class&.with_status(:scheduled)&.count || 0 + application&.class&.with_status(:scheduled)&.count || 0 end shared_examples 'a failing service' do @@ -10,45 +10,42 @@ describe Clusters::Applications::ScheduleInstallationService do expect(ClusterInstallAppWorker).not_to receive(:perform_async) count_before = count_scheduled - expect { service.execute }.to raise_error(StandardError) + expect { service.execute(application) }.to raise_error(StandardError) expect(count_scheduled).to eq(count_before) end end describe '#execute' do - let(:application_class) { Clusters::Applications::Helm } - let(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - let(:service) { described_class.new(project, nil, cluster: cluster, application_class: application_class) } + let(:project) { double(:project) } + let(:service) { described_class.new(project, nil) } - it 'creates a new application' do - allow(ClusterInstallAppWorker).to receive(:perform_async) + context 'when application is installable' do + let(:application) { create(:clusters_applications_helm, :installable) } - expect { service.execute }.to change { application_class.count }.by(1) - end - - it 'make the application scheduled' do - expect(ClusterInstallAppWorker).to receive(:perform_async).with(application_class.application_name, kind_of(Numeric)).once + it 'make the application scheduled' do + expect(ClusterInstallAppWorker).to receive(:perform_async).with(application.name, kind_of(Numeric)).once - expect { service.execute }.to change { application_class.with_status(:scheduled).count }.by(1) + expect { service.execute(application) }.to change { application.class.with_status(:scheduled).count }.by(1) + end end context 'when installation is already in progress' do let(:application) { create(:clusters_applications_helm, :installing) } - let(:cluster) { application.cluster } it_behaves_like 'a failing service' end - context 'when application_class is nil' do - let(:application_class) { nil } + context 'when application is nil' do + let(:application) { nil } it_behaves_like 'a failing service' end context 'when application cannot be persisted' do + let(:application) { create(:clusters_applications_helm) } + before do - expect_any_instance_of(application_class).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid) + expect(application).to receive(:make_scheduled!).once.and_raise(ActiveRecord::RecordInvalid) end it_behaves_like 'a failing service' diff --git a/spec/services/merge_requests/conflicts/list_service_spec.rb b/spec/services/merge_requests/conflicts/list_service_spec.rb index 837b8a56d12..d57852615d9 100644 --- a/spec/services/merge_requests/conflicts/list_service_spec.rb +++ b/spec/services/merge_requests/conflicts/list_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe MergeRequests::Conflicts::ListService do describe '#can_be_resolved_in_ui?' do def create_merge_request(source_branch) - create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start') do |mr| + create(:merge_request, source_branch: source_branch, target_branch: 'conflict-start', merge_status: :unchecked) do |mr| mr.mark_as_unmergeable end end diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index 240aa638f79..8838742a637 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -112,32 +112,6 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do service.trigger(unrelated_pipeline) end end - - context 'when the merge request is not mergeable' do - let(:mr_conflict) do - create(:merge_request, merge_when_pipeline_succeeds: true, merge_user: user, - source_branch: 'master', target_branch: 'feature-conflict', - source_project: project, target_project: project) - end - - let(:conflict_pipeline) do - create(:ci_pipeline, project: project, ref: mr_conflict.source_branch, - sha: mr_conflict.diff_head_sha, status: 'success', - head_pipeline_of: mr_conflict) - end - - it 'does not merge the merge request' do - expect(MergeWorker).not_to receive(:perform_async) - - service.trigger(conflict_pipeline) - end - - it 'creates todos for unmergeability' do - expect_any_instance_of(TodoService).to receive(:merge_request_became_unmergeable).with(mr_conflict) - - service.trigger(conflict_pipeline) - end - end end describe "#cancel" do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 5f28bc123f3..831678b949d 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1224,6 +1224,32 @@ describe NotificationService, :mailer do end end + describe '#merge_request_unmergeable' do + it "sends email to merge request author" do + notification.merge_request_unmergeable(merge_request) + + should_email(merge_request.author) + expect(email_recipients.size).to eq(1) + end + + describe 'when merge_when_pipeline_succeeds is true' do + before do + merge_request.update_attributes( + merge_when_pipeline_succeeds: true, + merge_user: create(:user) + ) + end + + it "sends email to merge request author and merge_user" do + notification.merge_request_unmergeable(merge_request) + + should_email(merge_request.author) + should_email(merge_request.merge_user) + expect(email_recipients.size).to eq(2) + end + end + end + describe '#closed_merge_request' do before do update_custom_notification(:close_merge_request, @u_guest_custom, resource: project) diff --git a/spec/services/projects/open_issues_count_service_spec.rb b/spec/services/projects/open_issues_count_service_spec.rb index f964f9972cd..06b470849b3 100644 --- a/spec/services/projects/open_issues_count_service_spec.rb +++ b/spec/services/projects/open_issues_count_service_spec.rb @@ -2,20 +2,53 @@ require 'spec_helper' describe Projects::OpenIssuesCountService do describe '#count' do - it 'returns the number of open issues' do - project = create(:project) - create(:issue, :opened, project: project) + let(:project) { create(:project) } - expect(described_class.new(project).count).to eq(1) + context 'when user is nil' do + it 'does not include confidential issues in the issue count' do + create(:issue, :opened, project: project) + create(:issue, :opened, confidential: true, project: project) + + expect(described_class.new(project).count).to eq(1) + end end - it 'does not include confidential issues in the issue count' do - project = create(:project) + context 'when user is provided' do + let(:user) { create(:user) } + + context 'when user can read confidential issues' do + before do + project.add_reporter(user) + end + + it 'returns the right count with confidential issues' do + create(:issue, :opened, project: project) + create(:issue, :opened, confidential: true, project: project) + + expect(described_class.new(project, user).count).to eq(2) + end + + it 'uses total_open_issues_count cache key' do + expect(described_class.new(project, user).cache_key_name).to eq('total_open_issues_count') + end + end + + context 'when user cannot read confidential issues' do + before do + project.add_guest(user) + end + + it 'does not include confidential issues' do + create(:issue, :opened, project: project) + create(:issue, :opened, confidential: true, project: project) - create(:issue, :opened, project: project) - create(:issue, :opened, confidential: true, project: project) + expect(described_class.new(project, user).count).to eq(1) + end - expect(described_class.new(project).count).to eq(1) + it 'uses public_open_issues_count cache key' do + expect(described_class.new(project, user).cache_key_name).to eq('public_open_issues_count') + end + end end end end diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 562b89e6767..9a51c873b30 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -721,17 +721,18 @@ describe TodoService do end describe '#merge_request_build_failed' do - it 'creates a pending todo for the merge request author' do - service.merge_request_build_failed(mr_unassigned) + let(:merge_participants) { [mr_unassigned.author, admin] } - should_create_todo(user: author, target: mr_unassigned, action: Todo::BUILD_FAILED) + before do + allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants) end - it 'creates a pending todo for merge_user' do - mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin) + it 'creates a pending todo for each merge_participant' do service.merge_request_build_failed(mr_unassigned) - should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::BUILD_FAILED) + merge_participants.each do |participant| + should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::BUILD_FAILED) + end end end @@ -747,11 +748,19 @@ describe TodoService do end describe '#merge_request_became_unmergeable' do - it 'creates a pending todo for a merge_user' do + let(:merge_participants) { [admin, create(:user)] } + + before do + allow(mr_unassigned).to receive(:merge_participants).and_return(merge_participants) + end + + it 'creates a pending todo for each merge_participant' do mr_unassigned.update(merge_when_pipeline_succeeds: true, merge_user: admin) service.merge_request_became_unmergeable(mr_unassigned) - should_create_todo(user: admin, author: admin, target: mr_unassigned, action: Todo::UNMERGEABLE) + merge_participants.each do |participant| + should_create_todo(user: participant, author: participant, target: mr_unassigned, action: Todo::UNMERGEABLE) + end end end diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb index 0388f110d71..a15189db35f 100644 --- a/spec/support/api/milestones_shared_examples.rb +++ b/spec/support/api/milestones_shared_examples.rb @@ -196,6 +196,12 @@ shared_examples_for 'group and project milestones' do |route_definition| expect(json_response['state']).to eq('closed') end + + it 'updates milestone with only start date' do + put api(resource_route, user), start_date: Date.tomorrow + + expect(response).to have_gitlab_http_status(200) + end end describe "GET #{route_definition}/:milestone_id/issues" do diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb index 507d0432d7f..b85fde222ea 100644 --- a/spec/support/helpers/board_helpers.rb +++ b/spec/support/helpers/board_helpers.rb @@ -1,7 +1,7 @@ module BoardHelpers def click_card(card) within card do - first('.card-number').click + first('.board-card-number').click end wait_for_sidebar diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb index 50457b64745..ad0053ec5cf 100644 --- a/spec/support/helpers/features/sorting_helpers.rb +++ b/spec/support/helpers/features/sorting_helpers.rb @@ -15,7 +15,7 @@ module Spec def sort_by(value) find('button.dropdown-toggle').click - page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link(value) end end diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 72e5c2d66dd..f7b71bf42e3 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -132,6 +132,14 @@ module LoginHelpers env['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym] end + def stub_omniauth_failure(strategy, message_key, exception = nil) + env = @request.env + + env['omniauth.error'] = exception + env['omniauth.error.type'] = message_key.to_sym + env['omniauth.error.strategy'] = strategy + end + def stub_omniauth_saml_config(messages) set_devise_mapping(context: Rails.application) Rails.application.routes.disable_clear_and_finalize = true diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb index 3b9eb84e824..9dc1f1de436 100644 --- a/spec/support/helpers/mobile_helpers.rb +++ b/spec/support/helpers/mobile_helpers.rb @@ -1,10 +1,10 @@ module MobileHelpers def resize_screen_xs - resize_window(767, 768) + resize_window(575, 768) end def resize_screen_sm - resize_window(900, 768) + resize_window(767, 768) end def restore_window_size diff --git a/spec/support/helpers/routes_helpers.rb b/spec/support/helpers/routes_helpers.rb new file mode 100644 index 00000000000..c4129606418 --- /dev/null +++ b/spec/support/helpers/routes_helpers.rb @@ -0,0 +1,7 @@ +module RoutesHelpers + def fake_routes(&block) + @routes = @routes.dup + @routes.formatter.clear + ActionDispatch::Routing::Mapper.new(@routes).instance_exec(&block) + end +end diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb index 577518d726c..9496a94d8f4 100644 --- a/spec/support/helpers/sorting_helper.rb +++ b/spec/support/helpers/sorting_helper.rb @@ -11,7 +11,7 @@ module SortingHelper def sorting_by(value) find('button.dropdown-toggle').click - page.within('.content ul.dropdown-menu.dropdown-menu-align-right li') do + page.within('.content ul.dropdown-menu.dropdown-menu-right li') do click_link value end end diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb index 17f319f49e9..5241c0fa6f1 100644 --- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb +++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb @@ -10,7 +10,7 @@ shared_examples "protected branches > access control > CE" do unless allowed_to_push_button.text == access_type_name allowed_to_push_button.click - within(".dropdown.open .dropdown-menu") { click_on access_type_name } + within(".dropdown.show .dropdown-menu") { click_on access_type_name } end end @@ -55,7 +55,7 @@ shared_examples "protected branches > access control > CE" do unless allowed_to_merge_button.text == access_type_name allowed_to_merge_button.click - within(".dropdown.open .dropdown-menu") { click_on access_type_name } + within(".dropdown.show .dropdown-menu") { click_on access_type_name } end end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index a2e5642a72c..c9252bebb2e 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -125,6 +125,16 @@ describe 'gitlab:app namespace rake task' do expect(Dir.entries(File.join(project.repository.path, 'custom_hooks'))).to include("dummy.txt") end end + + context 'specific backup tasks' do + let(:task_list) { %w(db repo uploads builds artifacts pages lfs registry) } + + it 'prints a progress message to stdout' do + task_list.each do |task| + expect { run_rake_task("gitlab:backup:#{task}:create") }.to output(/Dumping /).to_stdout + end + end + end end context 'tar creation' do diff --git a/spec/views/admin/dashboard/index.html.haml_spec.rb b/spec/views/admin/dashboard/index.html.haml_spec.rb index 099baacf019..59c777ea338 100644 --- a/spec/views/admin/dashboard/index.html.haml_spec.rb +++ b/spec/views/admin/dashboard/index.html.haml_spec.rb @@ -22,6 +22,6 @@ describe 'admin/dashboard/index.html.haml' do it "includes revision of GitLab" do render - expect(rendered).to have_content "#{Gitlab::VERSION} (#{Gitlab::REVISION})" + expect(rendered).to have_content "#{Gitlab::VERSION} (#{Gitlab.revision})" end end diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb index 0a78606171d..836d452304c 100644 --- a/spec/views/help/index.html.haml_spec.rb +++ b/spec/views/help/index.html.haml_spec.rb @@ -39,7 +39,7 @@ describe 'help/index' do def stub_version(version, revision) stub_const('Gitlab::VERSION', version) - stub_const('Gitlab::REVISION', revision) + allow(Gitlab).to receive(:revision).and_return(revision) end def stub_helpers diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb index 0fa19ac84bb..80137815d2b 100644 --- a/spec/workers/update_merge_requests_worker_spec.rb +++ b/spec/workers/update_merge_requests_worker_spec.rb @@ -18,8 +18,13 @@ describe UpdateMergeRequestsWorker do end it 'executes MergeRequests::RefreshService with expected values' do - expect(MergeRequests::RefreshService).to receive(:new).with(project, user).and_call_original - expect_any_instance_of(MergeRequests::RefreshService).to receive(:execute).with(oldrev, newrev, ref) + expect(MergeRequests::RefreshService).to receive(:new) + .with(project, user).and_wrap_original do |method, *args| + method.call(*args).tap do |refresh_service| + expect(refresh_service) + .to receive(:execute).with(oldrev, newrev, ref) + end + end perform end diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index a00c6e89a1d..589ebcf1414 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -77,7 +77,7 @@ test: only: - branches -codequality: +code_quality: image: docker:stable variables: DOCKER_DRIVER: overlay2 @@ -86,9 +86,9 @@ codequality: - docker:stable-dind script: - setup_docker - - codeclimate + - code_quality artifacts: - paths: [codeclimate.json] + paths: [gl-code-quality-report.json] performance: stage: performance @@ -136,7 +136,7 @@ dependency_scanning: artifacts: paths: [gl-dependency-scanning-report.json] -sast:container: +container_scanning: image: docker:stable variables: DOCKER_DRIVER: overlay2 @@ -145,9 +145,9 @@ sast:container: - docker:stable-dind script: - setup_docker - - sast_container + - container_scanning artifacts: - paths: [gl-sast-container-report.json] + paths: [gl-container-scanning-report.json] dast: stage: dast @@ -388,7 +388,7 @@ rollout 100%: # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - function sast_container() { + function container_scanning() { if [[ -n "$CI_REGISTRY_USER" ]]; then echo "Logging to GitLab Container Registry with CI credentials..." docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" @@ -406,10 +406,10 @@ rollout 100%: retries=0 echo "Waiting for clair daemon to start" while( ! wget -T 10 -q -O /dev/null http://docker:6060/v1/namespaces ) ; do sleep 1 ; echo -n "." ; if [ $retries -eq 10 ] ; then echo " Timeout, aborting." ; exit 1 ; fi ; retries=$(($retries+1)) ; done - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-container-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true + ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-container-scanning-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true } - function codeclimate() { + function code_quality() { docker run --env SOURCE_CODE="$PWD" \ --volume "$PWD":/code \ --volume /var/run/docker.sock:/var/run/docker.sock \ diff --git a/yarn.lock b/yarn.lock index f34bc81067d..af8785bbc66 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54,9 +54,9 @@ lodash "^4.2.0" to-fast-properties "^2.0.0" -"@gitlab-org/gitlab-svgs@^1.22.0": - version "1.22.0" - resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.22.0.tgz#9f2daefebcda911cba8341313c8c464c8087fe44" +"@gitlab-org/gitlab-svgs@^1.23.0": + version "1.23.0" + resolved "https://registry.yarnpkg.com/@gitlab-org/gitlab-svgs/-/gitlab-svgs-1.23.0.tgz#42047aeedcc06bc12d417ed1efadad1749af9670" "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" @@ -73,6 +73,20 @@ version "2.0.48" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-2.0.48.tgz#3e90d8cde2d29015e5583017f7830cb3975b2eef" +"@vue/component-compiler-utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-1.2.1.tgz#3d543baa75cfe5dab96e29415b78366450156ef6" + dependencies: + consolidate "^0.15.1" + hash-sum "^1.0.2" + lru-cache "^4.1.2" + merge-source-map "^1.1.0" + postcss "^6.0.20" + postcss-selector-parser "^3.1.1" + prettier "^1.11.1" + source-map "^0.5.6" + vue-template-es2015-compiler "^1.6.0" + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -1270,9 +1284,9 @@ boom@5.x.x: dependencies: hoek "4.x.x" -bootstrap-sass@^3.3.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.3.6.tgz#363b0d300e868d3e70134c1a742bb17288444fd1" +bootstrap@4.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.1.0.tgz#110b05c31a236d56dbc9adcda6dd16f53738a28a" boxen@^1.2.1: version "1.3.0" @@ -1467,6 +1481,15 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cache-loader@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cache-loader/-/cache-loader-1.2.2.tgz#6d5c38ded959a09cc5d58190ab5af6f73bd353f5" + dependencies: + loader-utils "^1.1.0" + mkdirp "^0.5.1" + neo-async "^2.5.0" + schema-utils "^0.4.2" + cacheable-request@^2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" @@ -1947,9 +1970,9 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" -consolidate@^0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.14.5.tgz#5a25047bc76f73072667c8cb52c989888f494c63" +consolidate@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7" dependencies: bluebird "^3.1.1" @@ -2021,18 +2044,6 @@ core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" -cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892" - dependencies: - is-directory "^0.3.1" - js-yaml "^3.4.3" - minimist "^1.2.0" - object-assign "^4.1.0" - os-homedir "^1.0.1" - parse-json "^2.2.0" - require-from-string "^1.1.0" - create-ecdh@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d" @@ -2660,7 +2671,7 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -dot-prop@^4.1.0: +dot-prop@^4.1.0, dot-prop@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" dependencies: @@ -4552,10 +4563,6 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-data-descriptor "^1.0.0" kind-of "^6.0.2" -is-directory@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" - is-dotfile@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" @@ -4989,13 +4996,6 @@ js-yaml@3.x, js-yaml@^3.5.1, js-yaml@^3.7.0: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.4.3: - version "3.11.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" @@ -5555,6 +5555,13 @@ lru-cache@^4.0.1, lru-cache@^4.1.1: pseudomap "^1.0.2" yallist "^2.1.2" +lru-cache@^4.1.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + lru-cache@~2.6.5: version "2.6.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.6.5.tgz#e56d6354148ede8d7707b58d143220fd08df0fd5" @@ -5697,6 +5704,12 @@ merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" +merge-source-map@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" + dependencies: + source-map "^0.6.1" + merge2@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.2.tgz#03212e3da8d86c4d8523cebd6318193414f94e34" @@ -6333,7 +6346,7 @@ os-browserify@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" -os-homedir@^1.0.0, os-homedir@^1.0.1: +os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -6651,6 +6664,10 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" +popper.js@^1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" + portfinder@^1.0.9: version "1.0.13" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" @@ -6724,29 +6741,6 @@ postcss-filter-plugins@^2.0.0: postcss "^5.0.4" uniqid "^4.0.0" -postcss-load-config@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-1.2.0.tgz#539e9afc9ddc8620121ebf9d8c3673e0ce50d28a" - dependencies: - cosmiconfig "^2.1.0" - object-assign "^4.1.0" - postcss-load-options "^1.2.0" - postcss-load-plugins "^2.3.0" - -postcss-load-options@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/postcss-load-options/-/postcss-load-options-1.2.0.tgz#b098b1559ddac2df04bc0bb375f99a5cfe2b6d8c" - dependencies: - cosmiconfig "^2.1.0" - object-assign "^4.1.0" - -postcss-load-plugins@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/postcss-load-plugins/-/postcss-load-plugins-2.3.0.tgz#745768116599aca2f009fad426b00175049d8d92" - dependencies: - cosmiconfig "^2.1.1" - object-assign "^4.1.0" - postcss-merge-idents@^2.1.5: version "2.1.7" resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" @@ -6886,6 +6880,14 @@ postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: indexes-of "^1.0.1" uniq "^1.0.1" +postcss-selector-parser@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" + dependencies: + dot-prop "^4.1.1" + indexes-of "^1.0.1" + uniq "^1.0.1" + postcss-svgo@^2.1.1: version "2.1.6" resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" @@ -6932,13 +6934,13 @@ postcss@^6.0.1, postcss@^6.0.14: source-map "^0.6.1" supports-color "^5.2.0" -postcss@^6.0.8: - version "6.0.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.21.tgz#8265662694eddf9e9a5960db6da33c39e4cd069d" +postcss@^6.0.20: + version "6.0.22" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3" dependencies: - chalk "^2.3.2" + chalk "^2.4.1" source-map "^0.6.1" - supports-color "^5.3.0" + supports-color "^5.4.0" prelude-ls@~1.1.2: version "1.1.2" @@ -6960,14 +6962,14 @@ prettier@1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.11.1.tgz#61e43fc4cd44e68f2b0dfc2c38cd4bb0fccdcc75" +prettier@^1.11.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" + prettier@^1.5.3: version "1.10.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.10.2.tgz#1af8356d1842276a99a5b5529c82dd9e9ad3cc93" -prettier@^1.7.0: - version "1.8.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8" - pretty-bytes@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9" @@ -7533,10 +7535,6 @@ require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" -require-from-string@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" - require-main-filename@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" @@ -7587,12 +7585,6 @@ resolve@^1.1.6, resolve@^1.2.0: dependencies: path-parse "^1.0.5" -resolve@^1.4.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" - 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" @@ -7710,7 +7702,7 @@ sax@~1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" -schema-utils@^0.4.0, schema-utils@^0.4.3, schema-utils@^0.4.4, schema-utils@^0.4.5: +schema-utils@^0.4.0, schema-utils@^0.4.2, schema-utils@^0.4.3, schema-utils@^0.4.4, schema-utils@^0.4.5: version "0.4.5" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" dependencies: @@ -8200,6 +8192,10 @@ statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" +stickyfilljs@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stickyfilljs/-/stickyfilljs-2.0.5.tgz#d229e372d2199ddf5d283bbe34ac1f7d2529c2fc" + stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" @@ -8362,7 +8358,7 @@ supports-color@^5.2.0: dependencies: has-flag "^3.0.0" -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" dependencies: @@ -8974,27 +8970,19 @@ vue-eslint-parser@^2.0.1: esquery "^1.0.0" lodash "^4.17.4" -vue-hot-reload-api@^2.2.0: +vue-hot-reload-api@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.0.tgz#97976142405d13d8efae154749e88c4e358cf926" -vue-loader@^14.1.1: - version "14.2.2" - resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-14.2.2.tgz#c8cf3c2e29b6fb2ee595248a2aa6005038a125b3" +vue-loader@^15.2.0: + version "15.2.0" + resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.2.0.tgz#5a8138e490a1040942d2f10ae68fa72b5a923364" dependencies: - consolidate "^0.14.0" + "@vue/component-compiler-utils" "^1.2.1" hash-sum "^1.0.2" loader-utils "^1.1.0" - lru-cache "^4.1.1" - postcss "^6.0.8" - postcss-load-config "^1.1.0" - postcss-selector-parser "^2.0.0" - prettier "^1.7.0" - resolve "^1.4.0" - source-map "^0.6.1" - vue-hot-reload-api "^2.2.0" - vue-style-loader "^4.0.1" - vue-template-es2015-compiler "^1.6.0" + vue-hot-reload-api "^2.3.0" + vue-style-loader "^4.1.0" vue-resource@^1.5.0: version "1.5.0" @@ -9006,7 +8994,7 @@ vue-router@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.0.1.tgz#d9b05ad9c7420ba0f626d6500d693e60092cc1e9" -vue-style-loader@^4.0.1: +vue-style-loader@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.0.tgz#7588bd778e2c9f8d87bfc3c5a4a039638da7a863" dependencies: |