Commit 1c636b80 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into per-project-pipeline-iid

parents 82a49d0f 60b14e52
This diff is collapsed.
...@@ -37,12 +37,14 @@ When removing columns, tables, indexes or other structures: ...@@ -37,12 +37,14 @@ When removing columns, tables, indexes or other structures:
- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html) - [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/doc_styleguide.html)
- [ ] API support added - [ ] API support added
- [ ] Tests added for this feature/bug - [ ] Tests added for this feature/bug
- Review - Conform by the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
- [ ] Has been reviewed by Backend - [ ] Has been reviewed by a Backend maintainer
- [ ] Has been reviewed by Database - [ ] Has been reviewed by a Database specialist
- [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html) - [ ] Conform by the [merge request performance guides](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides) - [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) - [ ] If you have multiple commits, please combine them into a few logically organized commits by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
- [ ] Internationalization required/considered - [ ] Internationalization required/considered
- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan - [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan
- [ ] End-to-end tests pass (`package-and-qa` manual pipeline job) - [ ] End-to-end tests pass (`package-and-qa` manual pipeline job)
/label ~database
...@@ -31,8 +31,8 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._ ...@@ -31,8 +31,8 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
- [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc) - [Subject labels (~wiki, ~"container registry", ~ldap, ~api, etc.)](#subject-labels-wiki-container-registry-ldap-api-etc)
- [Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)](#team-labels-cicd-discussion-quality-platform-etc) - [Team labels (~"CI/CD", ~Discussion, ~Quality, ~Platform, etc.)](#team-labels-cicd-discussion-quality-platform-etc)
- [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release) - [Milestone labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#milestone-labels-deliverable-stretch-next-patch-release)
- [Priority labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-priority-labels-p1-p2-p3-etc) - [Priority labels (~P1, ~P2, ~P3 , ~P4)](#bug-priority-labels-p1-p2-p3-p4)
- [Severity labels (~Deliverable, ~Stretch, ~"Next Patch Release")](#bug-severity-labels-s1-s2-s3-etc) - [Severity labels (~S1, ~S2, ~S3 , ~S4)](#bug-severity-labels-s1-s2-s3-s4)
- [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests) - [Label for community contributors (~"Accepting Merge Requests")](#label-for-community-contributors-accepting-merge-requests)
- [Implement design & UI elements](#implement-design--ui-elements) - [Implement design & UI elements](#implement-design--ui-elements)
- [Issue tracker](#issue-tracker) - [Issue tracker](#issue-tracker)
...@@ -211,9 +211,10 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable ...@@ -211,9 +211,10 @@ Each issue scheduled for the current milestone should be labeled ~Deliverable
or ~"Stretch". Any open issue for a previous milestone should be labeled or ~"Stretch". Any open issue for a previous milestone should be labeled
~"Next Patch Release", or otherwise rescheduled to a different milestone. ~"Next Patch Release", or otherwise rescheduled to a different milestone.
### Bug Priority labels (~P1, ~P2, ~P3 & etc.) ### Bug Priority labels (~P1, ~P2, ~P3, ~P4)
Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be. If there are multiple defects, the priority decides which defect has to be fixed immediately versus later. Bug Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be.
If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes. This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
| Label | Meaning | Estimate time to fix | Guidance | | Label | Meaning | Estimate time to fix | Guidance |
...@@ -223,16 +224,7 @@ This label documents the planned timeline & urgency which is used to measure aga ...@@ -223,16 +224,7 @@ This label documents the planned timeline & urgency which is used to measure aga
| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | | | ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) | |
| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented | | ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) | The issue is prominent but does not impact user workflow and a workaround is documented |
#### Specific Priority guidance ### Bug Severity labels (~S1, ~S2, ~S3, ~S4)
| Label | Availability / Performance |
|-------|--------------------------------------------------------------|
| ~P1 | |
| ~P2 | The issue is (almost) guaranteed to occur in the near future |
| ~P3 | The issue is likely to occur in the near future |
| ~P4 | The issue _may_ occur but it's not likely |
### Bug Severity labels (~S1, ~S2, ~S3 & etc.)
Severity labels help us clearly communicate the impact of a ~bug on users. Severity labels help us clearly communicate the impact of a ~bug on users.
...@@ -243,14 +235,14 @@ Severity labels help us clearly communicate the impact of a ~bug on users. ...@@ -243,14 +235,14 @@ Severity labels help us clearly communicate the impact of a ~bug on users.
| ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. | | ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. |
| ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. | | ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. |
#### Specific Severity guidance #### Severity impact guidance
| Label | Security Impact | | Label | Security Impact | Availability / Performance Impact |
|-------|-------------------------------------------------------------------| |-------|---------------------------------------------------------------------|--------------------------------------------------------------|
| ~S1 | >50% customers impacted (possible company extinction level event) | | ~S1 | >50% users impacted (possible company extinction level event) | |
| ~S2 | Multiple customers impacted (but not apocalyptic) | | ~S2 | Many users or multiple paid customers impacted (but not apocalyptic)| The issue is (almost) guaranteed to occur in the near future |
| ~S3 | A single customer impacted | | ~S3 | A few users or a single paid customer impacted | The issue is likely to occur in the near future |
| ~S4 | No customer impact, or expected impact within 30 days | | ~S4 | No paid users/customer impacted, or expected impact within 30 days | The issue _may_ occur but it's not likely |
### Label for community contributors (~"Accepting Merge Requests") ### Label for community contributors (~"Accepting Merge Requests")
...@@ -728,6 +720,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -728,6 +720,3 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html [polling-etag]: https://docs.gitlab.com/ce/development/polling.html
[testing]: doc/development/testing_guide/index.md [testing]: doc/development/testing_guide/index.md
[us-english]: https://en.wikipedia.org/wiki/American_English [us-english]: https://en.wikipedia.org/wiki/American_English
[^1]: Please note that specs other than JavaScript specs are considered backend
code.
...@@ -160,7 +160,7 @@ gem 'state_machines-activerecord', '~> 0.5.1' ...@@ -160,7 +160,7 @@ gem 'state_machines-activerecord', '~> 0.5.1'
gem 'acts-as-taggable-on', '~> 5.0' gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs # Background jobs
gem 'sidekiq', '~> 5.0' gem 'sidekiq', '~> 5.1'
gem 'sidekiq-cron', '~> 0.6.0' gem 'sidekiq-cron', '~> 0.6.0'
gem 'redis-namespace', '~> 1.5.2' gem 'redis-namespace', '~> 1.5.2'
gem 'sidekiq-limit_fetch', '~> 3.4', require: false gem 'sidekiq-limit_fetch', '~> 3.4', require: false
...@@ -325,8 +325,6 @@ group :development, :test do ...@@ -325,8 +325,6 @@ group :development, :test do
gem 'factory_bot_rails', '~> 4.8.2' gem 'factory_bot_rails', '~> 4.8.2'
gem 'rspec-rails', '~> 3.6.0' gem 'rspec-rails', '~> 3.6.0'
gem 'rspec-retry', '~> 0.4.5' gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
gem 'rspec_profiling', '~> 0.0.5' gem 'rspec_profiling', '~> 0.0.5'
gem 'rspec-set', '~> 0.1.3' gem 'rspec-set', '~> 0.1.3'
gem 'rspec-parameterized', require: false gem 'rspec-parameterized', require: false
...@@ -343,7 +341,6 @@ group :development, :test do ...@@ -343,7 +341,6 @@ group :development, :test do
gem 'spring', '~> 2.0.0' gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0'
gem 'gitlab-styles', '~> 2.3', require: false gem 'gitlab-styles', '~> 2.3', require: false
# Pin these dependencies, otherwise a new rule could break the CI pipelines # Pin these dependencies, otherwise a new rule could break the CI pipelines
......
...@@ -131,7 +131,6 @@ GEM ...@@ -131,7 +131,6 @@ GEM
coderay (1.1.1) coderay (1.1.1)
coercible (1.0.0) coercible (1.0.0)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
colorize (0.7.7)
commonmarker (0.17.8) commonmarker (0.17.8)
ruby-enum (~> 0.5) ruby-enum (~> 0.5)
concord (0.1.5) concord (0.1.5)
...@@ -288,7 +287,6 @@ GEM ...@@ -288,7 +287,6 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.99.0) gitaly-proto (0.99.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.10) grpc (~> 1.10)
...@@ -846,11 +844,11 @@ GEM ...@@ -846,11 +844,11 @@ GEM
rack rack
shoulda-matchers (3.1.2) shoulda-matchers (3.1.2)
activesupport (>= 4.0.0) activesupport (>= 4.0.0)
sidekiq (5.0.5) sidekiq (5.1.3)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0) connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0) rack-protection (>= 1.5.0)
redis (>= 3.3.4, < 5) redis (>= 3.3.5, < 5)
sidekiq-cron (0.6.0) sidekiq-cron (0.6.0)
rufus-scheduler (>= 3.3.0) rufus-scheduler (>= 3.3.0)
sidekiq (>= 4.2.1) sidekiq (>= 4.2.1)
...@@ -869,22 +867,10 @@ GEM ...@@ -869,22 +867,10 @@ GEM
simplecov-html (0.10.0) simplecov-html (0.10.0)
slack-notifier (1.5.1) slack-notifier (1.5.1)
slop (3.6.0) slop (3.6.0)
spinach (0.8.10)
colorize
gherkin-ruby (>= 0.3.2)
json
spinach-rails (0.2.1)
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
spinach-rerun-reporter (0.0.2)
spinach (~> 0.8)
spring (2.0.1) spring (2.0.1)
activesupport (>= 4.2) activesupport (>= 4.2)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.1.0)
spring (>= 0.9.1)
sprockets (3.7.1) sprockets (3.7.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
...@@ -1177,17 +1163,14 @@ DEPENDENCIES ...@@ -1177,17 +1163,14 @@ DEPENDENCIES
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6) sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2) shoulda-matchers (~> 3.1.2)
sidekiq (~> 5.0) sidekiq (~> 5.1)
sidekiq-cron (~> 0.6.0) sidekiq-cron (~> 0.6.0)
sidekiq-limit_fetch (~> 3.4) sidekiq-limit_fetch (~> 3.4)
simple_po_parser (~> 1.1.2) simple_po_parser (~> 1.1.2)
simplecov (~> 0.14.0) simplecov (~> 0.14.0)
slack-notifier (~> 1.5.1) slack-notifier (~> 1.5.1)
spinach-rails (~> 0.2.1)
spinach-rerun-reporter (~> 0.0.2)
spring (~> 2.0.0) spring (~> 2.0.0)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.1.0)
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sshkey (~> 1.9.0) sshkey (~> 1.9.0)
stackprof (~> 0.2.10) stackprof (~> 0.2.10)
......
...@@ -132,7 +132,6 @@ GEM ...@@ -132,7 +132,6 @@ GEM
coderay (1.1.2) coderay (1.1.2)
coercible (1.0.0) coercible (1.0.0)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
colorize (0.8.1)
commonmarker (0.17.9) commonmarker (0.17.9)
ruby-enum (~> 0.5) ruby-enum (~> 0.5)
concord (0.1.5) concord (0.1.5)
...@@ -289,7 +288,6 @@ GEM ...@@ -289,7 +288,6 @@ GEM
gettext_i18n_rails (>= 0.7.1) gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0) po_to_json (>= 1.0.0)
rails (>= 3.2.0) rails (>= 3.2.0)
gherkin-ruby (0.3.2)
gitaly-proto (0.99.0) gitaly-proto (0.99.0)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
grpc (~> 1.10) grpc (~> 1.10)
...@@ -871,22 +869,10 @@ GEM ...@@ -871,22 +869,10 @@ GEM
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.2) simplecov-html (0.10.2)
slack-notifier (1.5.1) slack-notifier (1.5.1)
spinach (0.8.10)
colorize
gherkin-ruby (>= 0.3.2)
json
spinach-rails (0.2.1)
capybara (>= 2.0.0)
railties (>= 3)
spinach (>= 0.4)
spinach-rerun-reporter (0.0.2)
spinach (~> 0.8)
spring (2.0.2) spring (2.0.2)
activesupport (>= 4.2) activesupport (>= 4.2)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.1.0)
spring (>= 0.9.1)
sprockets (3.7.1) sprockets (3.7.1)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
...@@ -1187,11 +1173,8 @@ DEPENDENCIES ...@@ -1187,11 +1173,8 @@ DEPENDENCIES
simple_po_parser (~> 1.1.2) simple_po_parser (~> 1.1.2)
simplecov (~> 0.14.0) simplecov (~> 0.14.0)
slack-notifier (~> 1.5.1) slack-notifier (~> 1.5.1)
spinach-rails (~> 0.2.1)
spinach-rerun-reporter (~> 0.0.2)
spring (~> 2.0.0) spring (~> 2.0.0)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.1.0)
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sshkey (~> 1.9.0) sshkey (~> 1.9.0)
stackprof (~> 0.2.10) stackprof (~> 0.2.10)
......
...@@ -4,9 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of ...@@ -4,9 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
---
All Documentation content that resides under the doc/ directory of this
repository is licensed under Creative Commons: CC BY-SA 4.0.
...@@ -73,6 +73,8 @@ GitLab Community Edition (CE) is available freely under the MIT Expat license. ...@@ -73,6 +73,8 @@ GitLab Community Edition (CE) is available freely under the MIT Expat license.
All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component. All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component.
All Documentation content that resides under the doc/ directory of this repository is licensed under Creative Commons: CC BY-SA 4.0.
## Install a development environment ## Install a development environment
To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
......
...@@ -179,7 +179,7 @@ ...@@ -179,7 +179,7 @@
role="row" role="row"
> >
<div <div
class="alert alert-danger alert-block append-bottom-0 table-section section-100" class="alert alert-danger alert-block append-bottom-0"
role="gridcell" role="gridcell"
> >
<div> <div>
......
...@@ -32,17 +32,17 @@ export default { ...@@ -32,17 +32,17 @@ export default {
<template> <template>
<div :class="className"> <div :class="className">
{{ actionText }} {{ actionText }}
<time-ago-tooltip
:time="editedAt"
tooltip-placement="bottom"
/>
<template v-if="editedBy"> <template v-if="editedBy">
by {{ s__('ByAuthor|by') }}
<a <a
:href="editedBy.path" :href="editedBy.path"
class="js-vue-author author_link"> class="js-vue-author author_link">
{{ editedBy.name }} {{ editedBy.name }}
</a> </a>
</template> </template>
<time-ago-tooltip
:time="editedAt"
tooltip-placement="bottom"
/>
</div> </div>
</template> </template>
...@@ -62,6 +62,21 @@ export default { ...@@ -62,6 +62,21 @@ export default {
<template> <template>
<div class="note-header-info"> <div class="note-header-info">
<div
v-if="includeToggle"
class="discussion-actions">
<button
@click="handleToggle"
class="note-action-button discussion-toggle-button js-vue-toggle-button"
type="button">
<i
:class="toggleChevronClass"
class="fa"
aria-hidden="true">
</i>
{{ __('Toggle discussion') }}
</button>
</div>
<a :href="author.path"> <a :href="author.path">
<span class="note-header-author-name">{{ author.name }}</span> <span class="note-header-author-name">{{ author.name }}</span>
<span class="note-headline-light"> <span class="note-headline-light">
...@@ -95,20 +110,5 @@ export default { ...@@ -95,20 +110,5 @@ export default {
</i> </i>
</span> </span>
</span> </span>
<div
v-if="includeToggle"
class="discussion-actions">
<button
@click="handleToggle"
class="note-action-button discussion-toggle-button js-vue-toggle-button"
type="button">
<i
:class="toggleChevronClass"
class="fa"
aria-hidden="true">
</i>
Toggle discussion
</button>
</div>
</div> </div>
</template> </template>
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */ /* eslint-disable func-names, space-before-function-paren, prefer-rest-params, max-len, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */
import $ from 'jquery'; import $ from 'jquery';
import _ from 'underscore'; import _ from 'underscore';
...@@ -13,17 +13,17 @@ import { dateTickFormat } from '~/lib/utils/tick_formats'; ...@@ -13,17 +13,17 @@ import { dateTickFormat } from '~/lib/utils/tick_formats';
const d3 = { extent, max, select, scaleTime, scaleLinear, axisLeft, axisBottom, area, brushX, timeParse }; const d3 = { extent, max, select, scaleTime, scaleLinear, axisLeft, axisBottom, area, brushX, timeParse };
const extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
const hasProp = {}.hasOwnProperty; const hasProp = {}.hasOwnProperty;
const extend = function(child, parent) { for (const key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
export const ContributorsGraph = (function() { export const ContributorsGraph = (function() {
function ContributorsGraph() {} function ContributorsGraph() {}
ContributorsGraph.prototype.MARGIN = { ContributorsGraph.prototype.MARGIN = {
top: 20, top: 20,
right: 20, right: 10,
bottom: 30, bottom: 30,
left: 50 left: 40
}; };
ContributorsGraph.prototype.x_domain = null; ContributorsGraph.prototype.x_domain = null;
...@@ -32,6 +32,12 @@ export const ContributorsGraph = (function() { ...@@ -32,6 +32,12 @@ export const ContributorsGraph = (function() {
ContributorsGraph.prototype.dates = []; ContributorsGraph.prototype.dates = [];
ContributorsGraph.prototype.determine_width = function(baseWidth, $parentElement) {
const parentPaddingWidth = parseFloat($parentElement.css('padding-left')) + parseFloat($parentElement.css('padding-right'));
const marginWidth = this.MARGIN.left + this.MARGIN.right;
return baseWidth - parentPaddingWidth - marginWidth;
};
ContributorsGraph.set_x_domain = function(data) { ContributorsGraph.set_x_domain = function(data) {
return ContributorsGraph.prototype.x_domain = data; return ContributorsGraph.prototype.x_domain = data;
}; };
...@@ -105,11 +111,10 @@ export const ContributorsMasterGraph = (function(superClass) { ...@@ -105,11 +111,10 @@ export const ContributorsMasterGraph = (function(superClass) {
function ContributorsMasterGraph(data1) { function ContributorsMasterGraph(data1) {
const $parentElement = $('#contributors-master'); const $parentElement = $('#contributors-master');
const parentPadding = parseFloat($parentElement.css('padding-left')) + parseFloat($parentElement.css('padding-right'));
this.data = data1; this.data = data1;
this.update_content = this.update_content.bind(this); this.update_content = this.update_content.bind(this);
this.width = $('.content').width() - parentPadding - (this.MARGIN.left + this.MARGIN.right); this.width = this.determine_width($('.js-graphs-show').width(), $parentElement);
this.height = 200; this.height = 200;
this.x = null; this.x = null;
this.y = null; this.y = null;
...@@ -122,8 +127,7 @@ export const ContributorsMasterGraph = (function(superClass) { ...@@ -122,8 +127,7 @@ export const ContributorsMasterGraph = (function(superClass) {
} }
ContributorsMasterGraph.prototype.process_dates = function(data) { ContributorsMasterGraph.prototype.process_dates = function(data) {
var dates; const dates = this.get_dates(data);
dates = this.get_dates(data);
this.parse_dates(data); this.parse_dates(data);
return ContributorsGraph.set_dates(dates); return ContributorsGraph.set_dates(dates);
}; };
...@@ -133,8 +137,7 @@ export const ContributorsMasterGraph = (function(superClass) { ...@@ -133,8 +137,7 @@ export const ContributorsMasterGraph = (function(superClass) {
}; };
ContributorsMasterGraph.prototype.parse_dates = function(data) { ContributorsMasterGraph.prototype.parse_dates = function(data) {
var parseDate; const parseDate = d3.timeParse("%Y-%m-%d");
parseDate = d3.timeParse("%Y-%m-%d");
return data.forEach(function(d) { return data.forEach(function(d) {
return d.date = parseDate(d.date); return d.date = parseDate(d.date);
}); });
...@@ -152,7 +155,14 @@ export const ContributorsMasterGraph = (function(superClass) { ...@@ -152,7 +155,14 @@ export const ContributorsMasterGraph = (function(superClass) {
}; };
ContributorsMasterGraph.prototype.create_svg = function() { ContributorsMasterGraph.prototype.create_svg = function() {
return this.svg = d3.select("#contributors-master").append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "tint-box").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")"); this.svg = d3.select("#contributors-master")
.append("svg")
.attr("width", this.width + this.MARGIN.left + this.MARGIN.right)
.attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom)
.attr("class", "tint-box")
.append("g")
.attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
return this.svg;
}; };
ContributorsMasterGraph.prototype.create_area = function(x, y) { ContributorsMasterGraph.prototype.create_area = function(x, y) {
...@@ -218,12 +228,14 @@ export const ContributorsAuthorGraph = (function(superClass) { ...@@ -218,12 +228,14 @@ export const ContributorsAuthorGraph = (function(superClass) {
extend(ContributorsAuthorGraph, superClass); extend(ContributorsAuthorGraph, superClass);
function ContributorsAuthorGraph(data1) { function ContributorsAuthorGraph(data1) {
const $parentElements = $('.person');
this.data = data1; this.data = data1;
// Don't split graph size in half for mobile devices. // Don't split graph size in half for mobile devices.
if ($(window).width() < 768) { if ($(window).width() < 790) {
this.width = $('.content').width() - 80; this.width = this.determine_width($('.js-graphs-show').width(), $parentElements);
} else { } else {
this.width = ($('.content').width() / 2) - 100; this.width = this.determine_width($('.js-graphs-show').width() / 2, $parentElements);
} }
this.height = 200; this.height = 200;
this.x = null; this.x = null;
...@@ -249,8 +261,7 @@ export const ContributorsAuthorGraph = (function(superClass) { ...@@ -249,8 +261,7 @@ export const ContributorsAuthorGraph = (function(superClass) {
ContributorsAuthorGraph.prototype.create_area = function(x, y) { ContributorsAuthorGraph.prototype.create_area = function(x, y) {
return this.area = d3.area().x(function(d) { return this.area = d3.area().x(function(d) {
var parseDate; const parseDate = d3.timeParse("%Y-%m-%d");
parseDate = d3.timeParse("%Y-%m-%d");
return x(parseDate(d)); return x(parseDate(d));
}).y0(this.height).y1((function(_this) { }).y0(this.height).y1((function(_this) {
return function(d) { return function(d) {
...@@ -264,9 +275,16 @@ export const ContributorsAuthorGraph = (function(superClass) { ...@@ -264,9 +275,16 @@ export const ContributorsAuthorGraph = (function(superClass) {
}; };
ContributorsAuthorGraph.prototype.create_svg = function() { ContributorsAuthorGraph.prototype.create_svg = function() {
var persons = document.querySelectorAll('.person'); const persons = document.querySelectorAll('.person');
this.list_item = persons[persons.length - 1]; this.list_item = persons[persons.length - 1];
return this.svg = d3.select(this.list_item).append("svg").attr("width", this.width + this.MARGIN.left + this.MARGIN.right).attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom).attr("class", "spark").append("g").attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")"); this.svg = d3.select(this.list_item)
.append("svg")
.attr("width", this.width + this.MARGIN.left + this.MARGIN.right)
.attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom)
.attr("class", "spark")
.append("g")
.attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")");
return this.svg;
}; };
ContributorsAuthorGraph.prototype.draw_path = function(data) { ContributorsAuthorGraph.prototype.draw_path = function(data) {
......
<script>
import _ from 'underscore';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
export default {
components: {
GlModal,
},
props: {
deleteWikiUrl: {
type: String,
required: true,
default: '',
},
pageTitle: {
type: String,
required: true,
default: '',
},
csrfToken: {
type: String,
required: true,
default: '',
},
},
computed: {
message() {
return s__('WikiPageConfirmDelete|Are you sure you want to delete this page?');
},
title() {
return sprintf(
s__('WikiPageConfirmDelete|Delete page %{pageTitle}?'),
{
pageTitle: _.escape(this.pageTitle),
},
false,
);
},
},
methods: {
onSubmit() {
this.$refs.form.submit();
},
},
};
</script>
<template>
<gl-modal
id="delete-wiki-modal"
:header-title-text="title"
footer-primary-button-variant="danger"
:footer-primary-button-text="s__('WikiPageConfirmDelete|Delete page')"
@submit="onSubmit"
>
{{ message }}
<form
ref="form"
:action="deleteWikiUrl"
method="post"
class="form-horizontal js-requires-input"
>
<input
ref="method"
type="hidden"
name="_method"
value="delete"
/>
<input
type="hidden"
name="authenticity_token"
:value="csrfToken"
/>
</form>
</gl-modal>
</template>
import $ from 'jquery'; import $ from 'jquery';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import csrf from '~/lib/utils/csrf';
import Wikis from './wikis'; import Wikis from './wikis';
import ShortcutsWiki from '../../../shortcuts_wiki'; import ShortcutsWiki from '../../../shortcuts_wiki';
import ZenMode from '../../../zen_mode'; import ZenMode from '../../../zen_mode';
import GLForm from '../../../gl_form'; import GLForm from '../../../gl_form';
import deleteWikiModal from './components/delete_wiki_modal.vue';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new Wikis(); // eslint-disable-line no-new new Wikis(); // eslint-disable-line no-new
new ShortcutsWiki(); // eslint-disable-line no-new new ShortcutsWiki(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
new GLForm($('.wiki-form'), true); // eslint-disable-line no-new new GLForm($('.wiki-form'), true); // eslint-disable-line no-new
const deleteWikiButton = document.getElementById('delete-wiki-button');
if (deleteWikiButton) {
Vue.use(Translate);
const { deleteWikiUrl, pageTitle } = deleteWikiButton.dataset;
const deleteWikiModalEl = document.getElementById('delete-wiki-modal');
const deleteModal = new Vue({ // eslint-disable-line
el: deleteWikiModalEl,
data: {
deleteWikiUrl: '',
},
render(createElement) {
return createElement(deleteWikiModal, {
props: {
pageTitle,
deleteWikiUrl,
csrfToken: csrf.token,
},
});
},
});
}
}); });
...@@ -28,11 +28,6 @@ ...@@ -28,11 +28,6 @@
isOpen: false, isOpen: false,
}; };
}, },
computed: {
clipboardText() {
return `docker pull ${this.repo.location}`;
},
},
methods: { methods: {
...mapActions([ ...mapActions([
'fetchRepos', 'fetchRepos',
...@@ -84,7 +79,7 @@ ...@@ -84,7 +79,7 @@
<clipboard-button <clipboard-button
v-if="repo.location" v-if="repo.location"
:text="clipboardText" :text="repo.location"
:title="repo.location" :title="repo.location"
css-class="btn-default btn-transparent btn-clipboard" css-class="btn-default btn-transparent btn-clipboard"
/> />
......
...@@ -56,10 +56,6 @@ ...@@ -56,10 +56,6 @@
.catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY)); .catch(() => this.showError(errorMessagesTypes.FETCH_REGISTRY));
}, },
clipboardText(text) {
return `docker pull ${text}`;
},
showError(message) { showError(message) {
Flash(errorMessages[message]); Flash(errorMessages[message]);
}, },
...@@ -89,7 +85,7 @@ ...@@ -89,7 +85,7 @@
<clipboard-button <clipboard-button
v-if="item.location" v-if="item.location"
:title="item.location" :title="item.location"
:text="clipboardText(item.location)" :text="item.location"
css-class="btn-default btn-transparent btn-clipboard" css-class="btn-default btn-transparent btn-clipboard"
/> />
</td> </td>
......
<script>
export default {
name: 'MRWidgetMaintainerEdit',
props: {
maintainerEditAllowed: {
type: Boolean,
default: false,
required: false,
},
},
};
</script>
<template>
<section class="mr-info-list mr-links">
<p v-if="maintainerEditAllowed">
{{ s__("mrWidget|Allows edits from maintainers") }}
</p>
</section>
</template>
...@@ -10,6 +10,6 @@ In EE, the configuration extends this object to add a functioning squash-before- ...@@ -10,6 +10,6 @@ In EE, the configuration extends this object to add a functioning squash-before-
button. button.
*/ */
export default { <script>
template: '', export default {};
}; </script>
...@@ -15,7 +15,6 @@ export { default as WidgetHeader } from './components/mr_widget_header.vue'; ...@@ -15,7 +15,6 @@ export { default as WidgetHeader } from './components/mr_widget_header.vue';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue'; export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue'; export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
export { default as Deployment } from './components/deployment.vue'; export { default as Deployment } from './components/deployment.vue';
export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue'; export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
export { default as MergedState } from './components/states/mr_widget_merged.vue'; export { default as MergedState } from './components/states/mr_widget_merged.vue';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue';
...@@ -41,8 +40,8 @@ export { default as MRWidgetService } from './services/mr_widget_service'; ...@@ -41,8 +40,8 @@ export { default as MRWidgetService } from './services/mr_widget_service';
export { default as eventHub } from './event_hub'; export { default as eventHub } from './event_hub';
export { default as getStateKey } from './stores/get_state_key'; export { default as getStateKey } from './stores/get_state_key';
export { default as stateMaps } from './stores/state_maps'; export { default as stateMaps } from './stores/state_maps';
export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge'; export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge.vue';
export { default as notify } from '../lib/utils/notify'; export { default as notify } from '../lib/utils/notify';
export { default as SourceBranchRemovalStatus } from './components/source_branch_removal_status.vue'; export { default as SourceBranchRemovalStatus } from './components/source_branch_removal_status.vue';
export { default as mrWidgetOptions } from './mr_widget_options'; export { default as mrWidgetOptions } from './mr_widget_options.vue';
<script>
import Project from '~/pages/projects/project'; import Project from '~/pages/projects/project';
import SmartInterval from '~/smart_interval'; import SmartInterval from '~/smart_interval';
import Flash from '../flash'; import createFlash from '../flash';
import { import {
WidgetHeader, WidgetHeader,
WidgetMergeHelp, WidgetMergeHelp,
WidgetPipeline, WidgetPipeline,
Deployment, Deployment,
WidgetMaintainerEdit,
WidgetRelatedLinks, WidgetRelatedLinks,
MergedState, MergedState,
ClosedState, ClosedState,
...@@ -40,10 +41,39 @@ import { setFavicon } from '../lib/utils/common_utils'; ...@@ -40,10 +41,39 @@ import { setFavicon } from '../lib/utils/common_utils';
export default { export default {
el: '#js-vue-mr-widget', el: '#js-vue-mr-widget',
name: 'MRWidget', name: 'MRWidget',
components: {
'mr-widget-header': WidgetHeader,
'mr-widget-merge-help': WidgetMergeHelp,
'mr-widget-pipeline': WidgetPipeline,
Deployment,
'mr-widget-related-links': WidgetRelatedLinks,
'mr-widget-merged': MergedState,
'mr-widget-closed': ClosedState,
'mr-widget-merging': MergingState,
'mr-widget-failed-to-merge': FailedToMerge,
'mr-widget-wip': WorkInProgressState,
'mr-widget-archived': ArchivedState,
'mr-widget-conflicts': ConflictsState,
'mr-widget-nothing-to-merge': NothingToMergeState,
'mr-widget-not-allowed': NotAllowedState,
'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': ReadyToMergeState,
'sha-mismatch': ShaMismatchState,
'mr-widget-squash-before-merge': SquashBeforeMerge,
'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
'mr-widget-pipeline-blocked': PipelineBlockedState,
'mr-widget-pipeline-failed': PipelineFailedState,
'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
'mr-widget-auto-merge-failed': AutoMergeFailed,
'mr-widget-rebase': RebaseState,
SourceBranchRemovalStatus,
},
props: { props: {
mrData: { mrData: {
type: Object, type: Object,
required: false, required: false,
default: null,
}, },
}, },
data() { data() {
...@@ -72,6 +102,13 @@ export default { ...@@ -72,6 +102,13 @@ export default {
(!this.mr.isNothingToMergeState && !this.mr.isMergedState); (!this.mr.isNothingToMergeState && !this.mr.isMergedState);
}, },
}, },
created() {
this.initPolling();
this.bindEventHubListeners();
},
mounted() {
this.handleMounted();
},
methods: { methods: {
createService(store) { createService(store) {
const endpoints = { const endpoints = {
...@@ -99,7 +136,7 @@ export default { ...@@ -99,7 +136,7 @@ export default {
cb.call(null, data); cb.call(null, data);
} }
}) })
.catch(() => new Flash('Something went wrong. Please try again.')); .catch(() => createFlash('Something went wrong. Please try again.'));
}, },
initPolling() { initPolling() {
this.pollingInterval = new SmartInterval({ this.pollingInterval = new SmartInterval({
...@@ -134,7 +171,7 @@ export default { ...@@ -134,7 +171,7 @@ export default {
} }
}) })
.catch(() => { .catch(() => {
new Flash('Something went wrong while fetching the environments for this merge request. Please try again.'); // eslint-disable-line createFlash('Something went wrong while fetching the environments for this merge request. Please try again.'); // eslint-disable-line
}); });
}, },
fetchActionsContent() { fetchActionsContent() {
...@@ -147,7 +184,7 @@ export default { ...@@ -147,7 +184,7 @@ export default {
Project.initRefSwitcher(); Project.initRefSwitcher();
} }
}) })
.catch(() => new Flash('Something went wrong. Please try again.')); .catch(() => createFlash('Something went wrong. Please try again.'));
}, },
handleNotification(data) { handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return; if (data.ci_status === this.mr.ciStatus) return;
...@@ -202,76 +239,53 @@ export default { ...@@ -202,76 +239,53 @@ export default {
this.initDeploymentsPolling(); this.initDeploymentsPolling();
}, },
}, },
created() { };
this.initPolling(); </script>
this.bindEventHubListeners(); <template>
}, <div class="mr-state-widget prepend-top-default">
mounted() { <mr-widget-header
this.handleMounted(); :mr="mr"
}, />
components: { <mr-widget-pipeline
'mr-widget-header': WidgetHeader, v-if="shouldRenderPipelines"
'mr-widget-merge-help': WidgetMergeHelp, :pipeline="mr.pipeline"
'mr-widget-pipeline': WidgetPipeline, :ci-status="mr.ciStatus"
Deployment, :has-ci="mr.hasCI"
'mr-widget-maintainer-edit': WidgetMaintainerEdit, />
'mr-widget-related-links': WidgetRelatedLinks, <deployment
'mr-widget-merged': MergedState, v-for="deployment in mr.deployments"
'mr-widget-closed': ClosedState, :key="deployment.id"
'mr-widget-merging': MergingState, :deployment="deployment"
'mr-widget-failed-to-merge': FailedToMerge, />
'mr-widget-wip': WorkInProgressState, <div class="mr-widget-section">
'mr-widget-archived': ArchivedState, <component
'mr-widget-conflicts': ConflictsState, :is="componentName"
'mr-widget-nothing-to-merge': NothingToMergeState, :mr="mr"
'mr-widget-not-allowed': NotAllowedState, :service="service"
'mr-widget-missing-branch': MissingBranchState, />
'mr-widget-ready-to-merge': ReadyToMergeState,
'mr-widget-sha-mismatch': ShaMismatchState, <section
'mr-widget-squash-before-merge': SquashBeforeMerge, v-if="mr.maintainerEditAllowed"
'mr-widget-checking': CheckingState, class="mr-info-list mr-links"
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState, >
'mr-widget-pipeline-blocked': PipelineBlockedState, {{ s__("mrWidget|Allows edits from maintainers") }}
'mr-widget-pipeline-failed': PipelineFailedState, </section>
'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
'mr-widget-auto-merge-failed': AutoMergeFailed, <mr-widget-related-links
'mr-widget-rebase': RebaseState, v-if="shouldRenderRelatedLinks"
SourceBranchRemovalStatus, :state="mr.state"
}, :related-links="mr.relatedLinks"
template: ` />
<div class="mr-state-widget prepend-top-default">
<mr-widget-header :mr="mr" /> <source-branch-removal-status
<mr-widget-pipeline v-if="shouldRenderSourceBranchRemovalStatus"
v-if="shouldRenderPipelines"
:pipeline="mr.pipeline"
:ci-status="mr.ciStatus"
:has-ci="mr.hasCI"
/>
<deployment
v-for="deployment in mr.deployments"
:key="deployment.id"
:deployment="deployment"
/> />
<div class="mr-widget-section">
<component
:is="componentName"
:mr="mr"
:service="service" />
<mr-widget-maintainer-edit
:maintainerEditAllowed="mr.maintainerEditAllowed" />
<mr-widget-related-links
v-if="shouldRenderRelatedLinks"
:state="mr.state"
:related-links="mr.relatedLinks" />
<source-branch-removal-status
v-if="shouldRenderSourceBranchRemovalStatus"
/>
</div>
<div
class="mr-widget-footer"
v-if="shouldRenderMergeHelp">
<mr-widget-merge-help />
</div>
</div> </div>
`, <div
}; class="mr-widget-footer"
v-if="shouldRenderMergeHelp"
>
<mr-widget-merge-help />
</div>
</div>
</template>
...@@ -210,3 +210,15 @@ ...@@ -210,3 +210,15 @@
margin-left: -$size; margin-left: -$size;
} }
} }
/*
* Mixin that fixes wrapping issues with long strings (e.g. URLs)
*
* Note: the width needs to be set for it to work in Firefox
*/
@mixin overflow-break-word {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
max-width: 100%;
}
...@@ -67,7 +67,8 @@ ...@@ -67,7 +67,8 @@
padding: 8px 40px; padding: 8px 40px;
} }
.embed-toggle { .embed-toggle,
.snippet-clipboard-btn {
height: 35px; height: 35px;
} }
} }
...@@ -284,6 +284,9 @@ ...@@ -284,6 +284,9 @@
box-shadow: 0 1px 2px $issue-boards-card-shadow; box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none; list-style: none;
// as a fallback, hide overflow content so that dragging and dropping still works
overflow: hidden;
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 5px; margin-bottom: 5px;
} }
...@@ -310,14 +313,13 @@ ...@@ -310,14 +313,13 @@
} }
.card-title { .card-title {
@include overflow-break-word();
margin: 0 30px 0 0; margin: 0 30px 0 0;
font-size: 1em; font-size: 1em;
line-height: inherit; line-height: inherit;
a { a {
color: $gl-text-color; color: $gl-text-color;
word-wrap: break-word;
word-break: break-word;
margin-right: 2px; margin-right: 2px;
} }
} }
...@@ -462,8 +464,8 @@ ...@@ -462,8 +464,8 @@
} }
.issuable-header-text { .issuable-header-text {
@include overflow-break-word();
padding-right: 35px; padding-right: 35px;
word-break: break-word;
> strong { > strong {
font-weight: $gl-font-weight-bold; font-weight: $gl-font-weight-bold;
......
...@@ -407,10 +407,6 @@ ul.notes { ...@@ -407,10 +407,6 @@ ul.notes {
.note-header { .note-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@include notes-media('max', $screen-xs-max) {
flex-flow: row wrap;
}
} }
.note-header-info { .note-header-info {
...@@ -473,11 +469,6 @@ ul.notes { ...@@ -473,11 +469,6 @@ ul.notes {
margin-left: 10px; margin-left: 10px;
color: $gray-darkest; color: $gray-darkest;
@include notes-media('max', $screen-md-max) {
float: none;
margin-left: 0;
}
.btn-group > .discussion-next-btn { .btn-group > .discussion-next-btn {
margin-left: -1px; margin-left: -1px;
} }
......
class Admin::DashboardController < Admin::ApplicationController class Admin::DashboardController < Admin::ApplicationController
include CountHelper
def index def index
@projects = Project.order_id_desc.without_deleted.with_route.limit(10) @projects = Project.order_id_desc.without_deleted.with_route.limit(10)
@users = User.order_id_desc.limit(10) @users = User.order_id_desc.limit(10)
......
...@@ -2,6 +2,10 @@ module SendFileUpload ...@@ -2,6 +2,10 @@ module SendFileUpload
def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, disposition: 'attachment') def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, disposition: 'attachment')
if attachment if attachment
redirect_params[:query] = { "response-content-disposition" => "#{disposition};filename=#{attachment.inspect}" } redirect_params[:query] = { "response-content-disposition" => "#{disposition};filename=#{attachment.inspect}" }
# By default, Rails will send uploads with an extension of .js with a
# content-type of text/javascript, which will trigger Rails'
# cross-origin JavaScript protection.
send_params[:content_type] = 'text/plain' if File.extname(attachment) == '.js'
send_params.merge!(filename: attachment, disposition: disposition) send_params.merge!(filename: attachment, disposition: disposition)
end end
......
...@@ -8,19 +8,6 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -8,19 +8,6 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve] before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
#
# This is a fix to make spinach feature tests passing:
# Controller actions are returned from AbstractController::Base and methods of parent classes are
# excluded in order to return only specific controller related methods.
# That is ok for the app (no :create method in ancestors)
# but fails for tests because there is a :create method on FactoryBot (one of the ancestors)
#
# see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78
#
def create
super
end
def delete_attachment def delete_attachment
note.remove_attachment! note.remove_attachment!
note.update_attribute(:attachment, nil) note.update_attribute(:attachment, nil)
......
...@@ -181,4 +181,8 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -181,4 +181,8 @@ class Projects::PipelinesController < Projects::ApplicationController
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343 # Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339')
end end
def authorize_update_pipeline!
return access_denied! unless can?(current_user, :update_pipeline, @pipeline)
end
end end
...@@ -69,7 +69,7 @@ module Projects ...@@ -69,7 +69,7 @@ module Projects
@project_runners = @project.runners.ordered @project_runners = @project.runners.ordered
@assignable_runners = current_user @assignable_runners = current_user
.ci_authorized_runners .ci_owned_runners
.assignable_for(project) .assignable_for(project)
.ordered .ordered
.page(params[:page]).per(20) .page(params[:page]).per(20)
......
...@@ -24,6 +24,15 @@ module AutoDevopsHelper ...@@ -24,6 +24,15 @@ module AutoDevopsHelper
end end
end end
def cluster_ingress_ip(project)
project
.cluster_ingresses
.where("external_ip is not null")
.limit(1)
.pluck(:external_ip)
.first
end
private private
def missing_auto_devops_domain?(project) def missing_auto_devops_domain?(project)
......
module CountHelper
def approximate_count_with_delimiters(model)
number_with_delimiter(Gitlab::Database::Count.approximate_count(model))
end
end
...@@ -41,7 +41,7 @@ module EventsHelper ...@@ -41,7 +41,7 @@ module EventsHelper
key = key.to_s key = key.to_s
active = 'active' if @event_filter.active?(key) active = 'active' if @event_filter.active?(key)
link_opts = { link_opts = {
class: "event-filter-link has-tooltip", class: "event-filter-link",
id: "#{key}_event_filter", id: "#{key}_event_filter",
title: tooltip title: tooltip
} }
......
...@@ -257,6 +257,7 @@ module ProjectsHelper ...@@ -257,6 +257,7 @@ module ProjectsHelper
if project.builds_enabled? && can?(current_user, :read_pipeline, project) if project.builds_enabled? && can?(current_user, :read_pipeline, project)
nav_tabs << :pipelines nav_tabs << :pipelines
nav_tabs << :operations
end end
if project.external_issue_tracker if project.external_issue_tracker
......
...@@ -2,6 +2,7 @@ class Appearance < ActiveRecord::Base ...@@ -2,6 +2,7 @@ class Appearance < ActiveRecord::Base
include CacheMarkdownField include CacheMarkdownField
include AfterCommitQueue include AfterCommitQueue
include ObjectStorage::BackgroundMove include ObjectStorage::BackgroundMove
include WithUploads
cache_markdown_field :description cache_markdown_field :description
cache_markdown_field :new_project_guidelines cache_markdown_field :new_project_guidelines
...@@ -14,8 +15,6 @@ class Appearance < ActiveRecord::Base ...@@ -14,8 +15,6 @@ class Appearance < ActiveRecord::Base
mount_uploader :logo, AttachmentUploader mount_uploader :logo, AttachmentUploader
mount_uploader :header_logo, AttachmentUploader mount_uploader :header_logo, AttachmentUploader
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
CACHE_KEY = "current_appearance:#{Gitlab::VERSION}".freeze CACHE_KEY = "current_appearance:#{Gitlab::VERSION}".freeze
after_commit :flush_redis_cache after_commit :flush_redis_cache
......
...@@ -42,12 +42,16 @@ module Ci ...@@ -42,12 +42,16 @@ module Ci
delegate :id, to: :project, prefix: true delegate :id, to: :project, prefix: true
delegate :full_path, to: :project, prefix: true delegate :full_path, to: :project, prefix: true
validates :source, exclusion: { in: %w(unknown), unless: :importing? }, on: :create
validates :sha, presence: { unless: :importing? } validates :sha, presence: { unless: :importing? }
validates :ref, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? }
validates :status, presence: { unless: :importing? } validates :status, presence: { unless: :importing? }
validate :valid_commit_sha, unless: :importing? validate :valid_commit_sha, unless: :importing?
# Replace validator below with
# `validates :source, presence: { unless: :importing? }, on: :create`
# when removing Gitlab.rails5? code.
validate :valid_source, unless: :importing?, on: :create
after_create :keep_around_commits, unless: :importing? after_create :keep_around_commits, unless: :importing?
enum source: { enum source: {
...@@ -607,5 +611,11 @@ module Ci ...@@ -607,5 +611,11 @@ module Ci
project.repository.keep_around(self.sha) project.repository.keep_around(self.sha)
project.repository.keep_around(self.before_sha) project.repository.keep_around(self.before_sha)
end end
def valid_source
if source.nil? || source == "unknown"
errors.add(:source, "invalid source")
end
end
end end
end end
...@@ -52,7 +52,7 @@ module Ci ...@@ -52,7 +52,7 @@ module Ci
# Without that, placeholders would miss one and couldn't match. # Without that, placeholders would miss one and couldn't match.
where(locked: false) where(locked: false)
.where.not("ci_runners.id IN (#{project.runners.select(:id).to_sql})") .where.not("ci_runners.id IN (#{project.runners.select(:id).to_sql})")
.specific .project_type
end end
validate :tag_constraints validate :tag_constraints
......
# Mounted uploaders are destroyed by carrierwave's after_commit
# hook. This hook fetches upload location (local vs remote) from
# Upload model. So it's neccessary to make sure that during that
# after_commit hook model's associated uploads are not deleted yet.
# IOW we can not use dependent: :destroy :
# has_many :uploads, as: :model, dependent: :destroy
#
# And because not-mounted uploads require presence of upload's
# object model when destroying them (FileUploader's `build_upload` method
# references `model` on delete), we can not use after_commit hook for these
# uploads.
#
# Instead FileUploads are destroyed in before_destroy hook and remaining uploads
# are destroyed by the carrierwave's after_commit hook.
module WithUploads
extend ActiveSupport::Concern
# Currently there is no simple way how to select only not-mounted
# uploads, it should be all FileUploaders so we select them by
# `uploader` class
FILE_UPLOADERS = %w(PersonalFileUploader NamespaceFileUploader FileUploader).freeze
included do
has_many :uploads, as: :model
before_destroy :destroy_file_uploads
end
# mounted uploads are deleted in carrierwave's after_commit hook,
# but FileUploaders which are not mounted must be deleted explicitly and
# it can not be done in after_commit because FileUploader requires loads
# associated model on destroy (which is already deleted in after_commit)
def destroy_file_uploads
self.uploads.where(uploader: FILE_UPLOADERS).find_each do |upload|
upload.destroy
end
end
end
...@@ -10,6 +10,7 @@ class Group < Namespace ...@@ -10,6 +10,7 @@ class Group < Namespace
include LoadedInGroupList include LoadedInGroupList
include GroupDescendant include GroupDescendant
include TokenAuthenticatable include TokenAuthenticatable
include WithUploads
has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent has_many :group_members, -> { where(requested_at: nil) }, dependent: :destroy, as: :source # rubocop:disable Cop/ActiveRecordDependent
alias_method :members, :group_members alias_method :members, :group_members
...@@ -30,8 +31,6 @@ class Group < Namespace ...@@ -30,8 +31,6 @@ class Group < Namespace
has_many :variables, class_name: 'Ci::GroupVariable' has_many :variables, class_name: 'Ci::GroupVariable'
has_many :custom_attributes, class_name: 'GroupCustomAttribute' has_many :custom_attributes, class_name: 'GroupCustomAttribute'
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :boards has_many :boards
has_many :badges, class_name: 'GroupBadge' has_many :badges, class_name: 'GroupBadge'
......
...@@ -31,7 +31,8 @@ class List < ActiveRecord::Base ...@@ -31,7 +31,8 @@ class List < ActiveRecord::Base
if options.key?(:label) if options.key?(:label)
json[:label] = label.as_json( json[:label] = label.as_json(
project: board.project, project: board.project,
only: [:id, :title, :description, :color] only: [:id, :title, :description, :color],
methods: [:text_color]
) )
end end
end end
......
...@@ -23,6 +23,7 @@ class Project < ActiveRecord::Base ...@@ -23,6 +23,7 @@ class Project < ActiveRecord::Base
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
include ChronicDurationAttribute include ChronicDurationAttribute
include FastDestroyAll::Helpers include FastDestroyAll::Helpers
include WithUploads
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
...@@ -217,6 +218,7 @@ class Project < ActiveRecord::Base ...@@ -217,6 +218,7 @@ class Project < ActiveRecord::Base
has_one :cluster_project, class_name: 'Clusters::Project' has_one :cluster_project, class_name: 'Clusters::Project'
has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster' has_many :clusters, through: :cluster_project, class_name: 'Clusters::Cluster'
has_many :cluster_ingresses, through: :clusters, source: :application_ingress, class_name: 'Clusters::Applications::Ingress'
# Container repositories need to remove data from the container registry, # Container repositories need to remove data from the container registry,
# which is not managed by the DB. Hence we're still using dependent: :destroy # which is not managed by the DB. Hence we're still using dependent: :destroy
...@@ -300,8 +302,6 @@ class Project < ActiveRecord::Base ...@@ -300,8 +302,6 @@ class Project < ActiveRecord::Base
inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } } inclusion: { in: ->(_object) { Gitlab.config.repositories.storages.keys } }
validates :variables, variable_duplicates: { scope: :environment_scope } validates :variables, variable_duplicates: { scope: :environment_scope }
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# Scopes # Scopes
scope :pending_delete, -> { where(pending_delete: true) } scope :pending_delete, -> { where(pending_delete: true) }
scope :without_deleted, -> { where(pending_delete: false) } scope :without_deleted, -> { where(pending_delete: false) }
......
...@@ -17,6 +17,7 @@ class User < ActiveRecord::Base ...@@ -17,6 +17,7 @@ class User < ActiveRecord::Base
include IgnorableColumn include IgnorableColumn
include BulkMemberAccessLoad include BulkMemberAccessLoad
include BlocksJsonSerialization include BlocksJsonSerialization
include WithUploads
DEFAULT_NOTIFICATION_LEVEL = :participating DEFAULT_NOTIFICATION_LEVEL = :participating
...@@ -137,7 +138,6 @@ class User < ActiveRecord::Base ...@@ -137,7 +138,6 @@ class User < ActiveRecord::Base
has_many :custom_attributes, class_name: 'UserCustomAttribute' has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout' has_many :callouts, class_name: 'UserCallout'
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :term_agreements has_many :term_agreements
belongs_to :accepted_term, class_name: 'ApplicationSetting::Term' belongs_to :accepted_term, class_name: 'ApplicationSetting::Term'
...@@ -999,12 +999,19 @@ class User < ActiveRecord::Base ...@@ -999,12 +999,19 @@ class User < ActiveRecord::Base
!solo_owned_groups.present? !solo_owned_groups.present?
end end
def ci_authorized_runners def ci_owned_runners
@ci_authorized_runners ||= begin @ci_owned_runners ||= begin
runner_ids = Ci::RunnerProject project_runner_ids = Ci::RunnerProject
.where(project: authorized_projects(Gitlab::Access::MASTER)) .where(project: authorized_projects(Gitlab::Access::MASTER))
.select(:runner_id) .select(:runner_id)
Ci::Runner.specific.where(id: runner_ids)
group_runner_ids = Ci::RunnerNamespace
.where(namespace_id: owned_or_masters_groups.select(:id))
.select(:runner_id)
union = Gitlab::SQL::Union.new([project_runner_ids, group_runner_ids])
Ci::Runner.specific.where("ci_runners.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection
end end
end end
...@@ -1097,8 +1104,11 @@ class User < ActiveRecord::Base ...@@ -1097,8 +1104,11 @@ class User < ActiveRecord::Base
# <https://github.com/plataformatec/devise/blob/v4.0.0/lib/devise/models/lockable.rb#L92> # <https://github.com/plataformatec/devise/blob/v4.0.0/lib/devise/models/lockable.rb#L92>
# #
def increment_failed_attempts! def increment_failed_attempts!
return if ::Gitlab::Database.read_only?
self.failed_attempts ||= 0 self.failed_attempts ||= 0
self.failed_attempts += 1 self.failed_attempts += 1
if attempts_exceeded? if attempts_exceeded?
lock_access! unless access_locked? lock_access! unless access_locked?
else else
...@@ -1202,6 +1212,11 @@ class User < ActiveRecord::Base ...@@ -1202,6 +1212,11 @@ class User < ActiveRecord::Base
!terms_accepted? !terms_accepted?
end end
def owned_or_masters_groups
union = Gitlab::SQL::Union.new([owned_groups, masters_groups])
Group.from("(#{union.to_sql}) namespaces")
end
protected protected
# override, from Devise::Validatable # override, from Devise::Validatable
......
...@@ -14,11 +14,20 @@ module Ci ...@@ -14,11 +14,20 @@ module Ci
@subject.triggered_by?(@user) @subject.triggered_by?(@user)
end end
condition(:branch_allows_maintainer_push) do
@subject.project.branch_allows_maintainer_push?(@user, @subject.ref)
end
rule { protected_ref }.policy do rule { protected_ref }.policy do
prevent :update_build prevent :update_build
prevent :erase_build prevent :erase_build
end end
rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build
rule { can?(:public_access) & branch_allows_maintainer_push }.policy do
enable :update_build
enable :update_commit_status
end
end end
end end
...@@ -4,8 +4,16 @@ module Ci ...@@ -4,8 +4,16 @@ module Ci
condition(:protected_ref) { ref_protected?(@user, @subject.project, @subject.tag?, @subject.ref) } condition(:protected_ref) { ref_protected?(@user, @subject.project, @subject.tag?, @subject.ref) }
condition(:branch_allows_maintainer_push) do
@subject.project.branch_allows_maintainer_push?(@user, @subject.ref)
end
rule { protected_ref }.prevent :update_pipeline rule { protected_ref }.prevent :update_pipeline
rule { can?(:public_access) & branch_allows_maintainer_push }.policy do
enable :update_pipeline
end
def ref_protected?(user, project, tag, ref) def ref_protected?(user, project, tag, ref)
access = ::Gitlab::UserAccess.new(user, project: project) access = ::Gitlab::UserAccess.new(user, project: project)
......
module Ci module Ci
class RunnerPolicy < BasePolicy class RunnerPolicy < BasePolicy
with_options scope: :subject, score: 0
condition(:shared) { @subject.is_shared? }
with_options scope: :subject, score: 0 with_options scope: :subject, score: 0
condition(:locked, scope: :subject) { @subject.locked? } condition(:locked, scope: :subject) { @subject.locked? }
condition(:authorized_runner) { @user.ci_authorized_runners.include?(@subject) } condition(:owned_runner) { @user.ci_owned_runners.exists?(@subject.id) }
rule { anonymous }.prevent_all rule { anonymous }.prevent_all
rule { admin | authorized_runner }.enable :assign_runner
rule { ~admin & shared }.prevent :assign_runner rule { admin | owned_runner }.policy do
enable :assign_runner
enable :read_runner
enable :update_runner
enable :delete_runner
end
rule { ~admin & locked }.prevent :assign_runner rule { ~admin & locked }.prevent :assign_runner
end end
end end
...@@ -76,7 +76,7 @@ class ProjectPolicy < BasePolicy ...@@ -76,7 +76,7 @@ class ProjectPolicy < BasePolicy
condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled } condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled }
desc "Has merge requests allowing pushes to user" desc "Has merge requests allowing pushes to user"
condition(:has_merge_requests_allowing_pushes, scope: :subject) do condition(:has_merge_requests_allowing_pushes) do
project.merge_requests_allowing_push_to_user(user).any? project.merge_requests_allowing_push_to_user(user).any?
end end
...@@ -354,9 +354,7 @@ class ProjectPolicy < BasePolicy ...@@ -354,9 +354,7 @@ class ProjectPolicy < BasePolicy
# to run pipelines for the branches they have access to. # to run pipelines for the branches they have access to.
rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do
enable :create_build enable :create_build
enable :update_build
enable :create_pipeline enable :create_pipeline
enable :update_pipeline
end end
rule do rule do
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
= link_to admin_projects_path do = link_to admin_projects_path do
%h3.text-center %h3.text-center
Projects: Projects:
= number_with_delimiter(Project.cached_count) = approximate_count_with_delimiters(Project)
%hr %hr
= link_to('New project', new_project_path, class: "btn btn-new") = link_to('New project', new_project_path, class: "btn btn-new")
.col-sm-4 .col-sm-4
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= link_to admin_users_path do = link_to admin_users_path do
%h3.text-center %h3.text-center
Users: Users:
= number_with_delimiter(User.count) = approximate_count_with_delimiters(User)
%hr %hr
= link_to 'New user', new_admin_user_path, class: "btn btn-new" = link_to 'New user', new_admin_user_path, class: "btn btn-new"
.col-sm-4 .col-sm-4
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
= link_to admin_groups_path do = link_to admin_groups_path do
%h3.text-center %h3.text-center
Groups: Groups:
= number_with_delimiter(Group.count) = approximate_count_with_delimiters(Group)
%hr %hr
= link_to 'New group', new_admin_group_path, class: "btn btn-new" = link_to 'New group', new_admin_group_path, class: "btn btn-new"
.row .row
...@@ -39,31 +39,31 @@ ...@@ -39,31 +39,31 @@
%p %p
Forks Forks
%span.light.pull-right %span.light.pull-right
= number_with_delimiter(ForkedProjectLink.count) = approximate_count_with_delimiters(ForkedProjectLink)
%p %p
Issues Issues
%span.light.pull-right %span.light.pull-right
= number_with_delimiter(Issue.count) = approximate_count_with_delimiters(Issue)
%p %p
Merge Requests Merge Requests
%span.light.pull-right %span.light.pull-right
= number_with_delimiter(MergeRequest.count) = approximate_count_with_delimiters(MergeRequest)
%p %p
Notes Notes
%span.light.pull-right %span.light.pull-right
= number_with_delimiter(Note.count) = approximate_count_with_delimiters(Note)
%p %p
Snippets Snippets
%span.light.pull-right %span.light.pull-right
= number_with_delimiter(Snippet.count) = approximate_count_with_delimiters(Snippet)
%p %p
SSH Keys SSH Keys
%span.light.pull-right %span.light.pull-right
= number_with_delimiter(Key.count) = approximate_count_with_delimiters(Key)
%p %p
Milestones Milestones
%span.light.pull-right %span.light.pull-right
= number_with_delimiter(Milestone.count) = approximate_count_with_delimiters(Milestone)
%p %p
Active Users Active Users
%span.light.pull-right %span.light.pull-right
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
= icon("chevron-up") = icon("chevron-up")
- else - else
= icon("chevron-down") = icon("chevron-down")
Toggle discussion = _('Toggle discussion')
= link_to_member(@project, discussion.author, avatar: false) = link_to_member(@project, discussion.author, avatar: false)
.inline.discussion-headline-light .inline.discussion-headline-light
......
...@@ -35,7 +35,9 @@ ...@@ -35,7 +35,9 @@
= _('Domain') = _('Domain')
= form.text_field :domain, class: 'form-control', placeholder: 'domain.com' = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
.help-block .help-block
= s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.') = s_('CICD|A domain is required to use Auto Review Apps and Auto Deploy Stages.')
- if cluster_ingress_ip = cluster_ingress_ip(@project)
= s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe }
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank' = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank'
= f.submit 'Save changes', class: "btn btn-success prepend-top-15" = f.submit 'Save changes', class: "btn btn-success prepend-top-15"
...@@ -28,9 +28,16 @@ ...@@ -28,9 +28,16 @@
= link_to project_wiki_history_path(@project, @page), class: "btn" do = link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history") = s_("Wiki|Page history")
- if can?(current_user, :admin_wiki, @project) - if can?(current_user, :admin_wiki, @project)
= link_to project_wiki_path(@project, @page), data: { confirm: s_("WikiPageConfirmDelete|Are you sure you want to delete this page?")}, method: :delete, class: "btn btn-danger" do %button.btn.btn-danger{ data: { toggle: 'modal',
= _("Delete") target: '#delete-wiki-modal',
delete_wiki_url: project_wiki_path(@project, @page),
page_title: @page.title.capitalize },
id: 'delete-wiki-button',
type: 'button' }
= _('Delete')
= render 'form' = render 'form'
= render 'sidebar' = render 'sidebar'
#delete-wiki-modal.modal.fade
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
":title" => '(list.label ? list.label.description : "")', ":title" => '(list.label ? list.label.description : "")',
data: { container: "body", placement: "bottom" }, data: { container: "body", placement: "bottom" },
class: "label color-label title board-title-text", class: "label color-label title board-title-text",
":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.text_color ? list.label.text_color : \"#2e2e2e\") }" } ":style" => "{ backgroundColor: (list.label && list.label.color ? list.label.color : null), color: (list.label && list.label.textColor ? list.label.textColor : \"#2e2e2e\") }" }
{{ list.title }} {{ list.title }}
- if can?(current_user, :admin_list, current_board_parent) - if can?(current_user, :admin_list, current_board_parent)
......
...@@ -45,6 +45,6 @@ ...@@ -45,6 +45,6 @@
%strong.embed-toggle-list-item= _("Share") %strong.embed-toggle-list-item= _("Share")
%input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed } %input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
.input-group-btn .input-group-btn
%button.js-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '#snippet-url-area' } %button.js-clipboard-btn.snippet-clipboard-btn.btn.btn-default.has-tooltip{ title: "Copy to clipboard", 'data-clipboard-target': '.js-snippet-url-area' }
= sprite_icon('duplicate', size: 16) = sprite_icon('duplicate', size: 16)
.clearfix .clearfix
...@@ -5,7 +5,6 @@ require 'rainbow/refinement' ...@@ -5,7 +5,6 @@ require 'rainbow/refinement'
using Rainbow using Rainbow
BRANCH_PREFIX = 'security'.freeze BRANCH_PREFIX = 'security'.freeze
STABLE_BRANCH_SUFFIX = 'stable'.freeze
REMOTE = 'dev'.freeze REMOTE = 'dev'.freeze
options = { version: nil, branch: nil, sha: nil } options = { version: nil, branch: nil, sha: nil }
...@@ -37,9 +36,9 @@ abort("Missing options. Use #{$0} --help to see the list of options available".r ...@@ -37,9 +36,9 @@ abort("Missing options. Use #{$0} --help to see the list of options available".r
abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/ abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
branch = [BRANCH_PREFIX, options[:branch], options[:version]].join('-').freeze branch = [BRANCH_PREFIX, options[:branch], options[:version]].join('-').freeze
stable_branch = "#{options[:version]}-#{STABLE_BRANCH_SUFFIX}".freeze stable_branch = "#{BRANCH_PREFIX}-#{options[:version]}".freeze
command = "git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch}" command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch}"
_stdin, stdout, stderr = Open3.popen3(command) _stdin, stdout, stderr = Open3.popen3(command)
......
#!/usr/bin/env ruby
# Remove this block when removing rails5? code.
gemfile = %w[1 true].include?(ENV["RAILS5"]) ? "Gemfile.rails5" : "Gemfile"
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../#{gemfile}", __dir__)
begin
load File.expand_path('../spring', __FILE__)
rescue LoadError => e
raise unless e.message.include?('spring')
end
require 'bundler/setup'
load Gem.bin_path('spinach', 'spinach')
---
title: Fix width of contributors graphs
merge_request: 18639
author: Paul Vorbach
type: fixed
---
title: made listing and showing public issue apis available without authentication
merge_request: 18638
author: haseebeqx
type: changed
---
title: Fix issue board bug with long strings in titles
merge_request: 18924
author:
type: fixed
---
title: Move project sidebar sub-entries 'Environments' and 'Kubernetes' from 'CI/CD' to a new entry 'Operations'
merge_request: 18941
author:
type: changed
---
title: Display help text below auto devops domain with nip.io domain name (#45561)
merge_request: 18496
author:
type: added
---
title: Add index on runner_type for ci_runners
merge_request: 18897
author:
type: performance
---
title: Disables RBAC on nginx-ingress
merge_request: 18947
author:
type: fixed
---
title: fixed copy to blipboard button in embed bar of snippets
merge_request: 18923
author: haseebeqx
type: fixed
---
title: Correct skewed Kubernetes popover illustration
merge_request: 18949
author:
type: fixed
---
title: Does not log failed sign-in attempts when the database is in read-only mode
merge_request: 18957
author:
type: fixed
---
title: Remove Spinach
merge_request: 18869
author: '@blackst0ne'
type: other
---
title: 'Replace the `project/forked_merge_requests.feature` spinach test with an rspec analog'
merge_request: 18867
author: '@blackst0ne'
type: other
---
title: Fix setting Gitlab metrics content types
merge_request:
author:
type: fixed
---
title: Fix filename matching when processing file or blob search results
merge_request:
author:
type: fixed
---
title: Allow maintainers to retry pipelines on forked projects (if allowed in merge
request)
merge_request:
author:
type: fixed
---
title: Fix deletion of Object Store uploads
merge_request:
author:
type: fixed
---
title: Move discussion actions to the right for small viewports
merge_request: 18476
author: George Tsiolis
type: changed
---
title: Move SquashBeforeMerge vue component
merge_request: 18813
author: George Tsiolis
type: performance
---
title: Remove docker pull prefix from registry clipboard feature
merge_request: 18933
author: Lars Greiss
type: changed
---
title: Add a unique and not null constraint on the project_features.project_id column
merge_request:
author:
type: fixed
---
title: Fix system hook not firing for blocked users when LDAP sign-in is used
merge_request:
author:
type: fixed
---
title: Fix cross-origin errors when attempting to download JavaScript attachments
merge_request:
author:
type: fixed
---
title: New design for wiki page deletion confirmation
merge_request: 18712
author: Constance Okoghenun
type: added
---
title: Adding branches through the WebUI is handled by Gitaly
merge_request:
author:
type: other
---
title: Refs containting sha checks are done by Gitaly
merge_request:
author:
type: other
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
if Gitlab.dev_env_or_com? if Gitlab.dev_env_or_com?
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
deprecator.behavior = -> (message, callstack) {
Rails.logger.warn("#{message}: #{callstack[1..20].join}")
}
ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator) ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator)
end end
class AddIndexOnCiRunnersRunnerType < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_index :ci_runners, :runner_type
end
def down
remove_index :ci_runners, :runner_type
end
end
class AddUniqueConstraintToProjectFeaturesProjectId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
class ProjectFeature < ActiveRecord::Base
self.table_name = 'project_features'
include EachBatch
end
def up
remove_duplicates
add_concurrent_index :project_features, :project_id, unique: true, name: 'index_project_features_on_project_id_unique'
remove_concurrent_index_by_name :project_features, 'index_project_features_on_project_id'
rename_index :project_features, 'index_project_features_on_project_id_unique', 'index_project_features_on_project_id'
end
def down
rename_index :project_features, 'index_project_features_on_project_id', 'index_project_features_on_project_id_old'
add_concurrent_index :project_features, :project_id
remove_concurrent_index_by_name :project_features, 'index_project_features_on_project_id_old'
end
private
def remove_duplicates
features = ProjectFeature
.select('MAX(id) AS max, COUNT(id), project_id')
.group(:project_id)
.having('COUNT(id) > 1')
features.each do |feature|
ProjectFeature
.where(project_id: feature['project_id'])
.where('id <> ?', feature['max'])
.each_batch { |batch| batch.delete_all }
end
end
end
class AddNotNullConstraintToProjectFeaturesProjectId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
class ProjectFeature < ActiveRecord::Base
include EachBatch
self.table_name = 'project_features'
end
def up
ProjectFeature.where(project_id: nil).delete_all
change_column_null :project_features, :project_id, false
end
def down
change_column_null :project_features, :project_id, true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180508135515) do ActiveRecord::Schema.define(version: 20180512061621) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -505,6 +505,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do ...@@ -505,6 +505,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do
add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
add_index "ci_runners", ["is_shared"], name: "index_ci_runners_on_is_shared", using: :btree add_index "ci_runners", ["is_shared"], name: "index_ci_runners_on_is_shared", using: :btree
add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree
add_index "ci_runners", ["runner_type"], name: "index_ci_runners_on_runner_type", using: :btree
add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree
create_table "ci_stages", force: :cascade do |t| create_table "ci_stages", force: :cascade do |t|
...@@ -1495,7 +1496,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do ...@@ -1495,7 +1496,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do
add_index "project_deploy_tokens", ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree add_index "project_deploy_tokens", ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree
create_table "project_features", force: :cascade do |t| create_table "project_features", force: :cascade do |t|
t.integer "project_id" t.integer "project_id", null: false
t.integer "merge_requests_access_level" t.integer "merge_requests_access_level"
t.integer "issues_access_level" t.integer "issues_access_level"
t.integer "wiki_access_level" t.integer "wiki_access_level"
...@@ -1506,7 +1507,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do ...@@ -1506,7 +1507,7 @@ ActiveRecord::Schema.define(version: 20180508135515) do
t.integer "repository_access_level", default: 20, null: false t.integer "repository_access_level", default: 20, null: false
end end
add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
create_table "project_group_links", force: :cascade do |t| create_table "project_group_links", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
......
...@@ -241,7 +241,7 @@ GitLab.com is hosted, managed, and administered by GitLab, Inc., with ...@@ -241,7 +241,7 @@ GitLab.com is hosted, managed, and administered by GitLab, Inc., with
and teams: Free, Bronze, Silver, and Gold. and teams: Free, Bronze, Silver, and Gold.
GitLab.com subscriptions grants access GitLab.com subscriptions grants access
to the same features available in GitLab self-hosted, **expect to the same features available in GitLab self-hosted, **except
[administration](administration/index.md) tools and settings**: [administration](administration/index.md) tools and settings**:
- GitLab.com Free includes the same features available in Core - GitLab.com Free includes the same features available in Core
......
# NFS # NFS
You can view information and options set for each of the mounted NFS file You can view information and options set for each of the mounted NFS file
systems by running `sudo nfsstat -m`. systems by running `nfsstat -m` and `cat /etc/fstab`.
## NFS Server features ## NFS Server features
......
...@@ -22,7 +22,7 @@ Parameters: ...@@ -22,7 +22,7 @@ Parameters:
| --------- | ---- | -------- | ----------- | | --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` | | `iids[]` | Array[integer] | optional | Return only the milestones having the given `iid` |
| `state` | string | optional | Return only `active` or `closed` milestones` | | `state` | string | optional | Return only `active` or `closed` milestones |
| `search` | string | optional | Return only milestones with a title or description matching the provided string | | `search` | string | optional | Return only milestones with a title or description matching the provided string |
```bash ```bash
......
...@@ -82,7 +82,7 @@ Example of response ...@@ -82,7 +82,7 @@ Example of response
"artifacts_file": null, "artifacts_file": null,
"finished_at": "2015-12-24T17:54:24.921Z", "finished_at": "2015-12-24T17:54:24.921Z",
"id": 6, "id": 6,
"name": "spinach:other", "name": "rspec:other",
"pipeline": { "pipeline": {
"id": 6, "id": 6,
"ref": "master", "ref": "master",
...@@ -196,7 +196,7 @@ Example of response ...@@ -196,7 +196,7 @@ Example of response
"artifacts_file": null, "artifacts_file": null,
"finished_at": "2015-12-24T17:54:24.921Z", "finished_at": "2015-12-24T17:54:24.921Z",
"id": 6, "id": 6,
"name": "spinach:other", "name": "rspec:other",
"pipeline": { "pipeline": {
"id": 6, "id": 6,
"ref": "master", "ref": "master",
......
...@@ -151,7 +151,8 @@ The next step is to configure a Runner so that it picks the pending jobs. ...@@ -151,7 +151,8 @@ The next step is to configure a Runner so that it picks the pending jobs.
In GitLab, Runners run the jobs that you define in `.gitlab-ci.yml`. A Runner In GitLab, Runners run the jobs that you define in `.gitlab-ci.yml`. A Runner
can be a virtual machine, a VPS, a bare-metal machine, a docker container or can be a virtual machine, a VPS, a bare-metal machine, a docker container or
even a cluster of containers. GitLab and the Runners communicate through an API, even a cluster of containers. GitLab and the Runners communicate through an API,
so the only requirement is that the Runner's machine has [Internet] access. so the only requirement is that the Runner's machine has network access to the
GitLab server.
A Runner can be specific to a certain project or serve multiple projects in A Runner can be specific to a certain project or serve multiple projects in
GitLab. If it serves all projects it's called a _Shared Runner_. GitLab. If it serves all projects it's called a _Shared Runner_.
...@@ -226,4 +227,3 @@ CI with various languages. ...@@ -226,4 +227,3 @@ CI with various languages.
[enabled]: ../enable_or_disable_ci.md [enabled]: ../enable_or_disable_ci.md
[stages]: ../yaml/README.md#stages [stages]: ../yaml/README.md#stages
[pipeline]: ../pipelines.md [pipeline]: ../pipelines.md
[internet]: https://about.gitlab.com/images/theinternet.png
...@@ -29,6 +29,10 @@ There are a few rules to get your merge request accepted: ...@@ -29,6 +29,10 @@ There are a few rules to get your merge request accepted:
to ask one of the [Merge request coaches][team]. to ask one of the [Merge request coaches][team].
1. The reviewer will assign the merge request to a maintainer once the 1. The reviewer will assign the merge request to a maintainer once the
reviewer is satisfied with the state of the merge request. reviewer is satisfied with the state of the merge request.
1. Keep in mind that maintainers are also going to perform a final code review.
The ideal scenario is that the reviewer has already addressed any concerns
the maintainer would have found, and the maintainer only has to perform the
merge, but be prepared for further review comments.
For more guidance, see [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md). For more guidance, see [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md).
...@@ -207,3 +211,4 @@ Largely based on the [thoughtbot code review guide]. ...@@ -207,3 +211,4 @@ Largely based on the [thoughtbot code review guide].
[projects]: https://about.gitlab.com/handbook/engineering/projects/ [projects]: https://about.gitlab.com/handbook/engineering/projects/
[team]: https://about.gitlab.com/team/ [team]: https://about.gitlab.com/team/
[build handbook]: https://about.gitlab.com/handbook/build/handbook/build#how-to-work-with-build [build handbook]: https://about.gitlab.com/handbook/build/handbook/build#how-to-work-with-build
[^1]: Please note that specs other than JavaScript specs are considered backend code.
...@@ -11,7 +11,7 @@ Available `RAILS_ENV` ...@@ -11,7 +11,7 @@ Available `RAILS_ENV`
- `production` (generally not for your main GDK db, but you may need this for e.g. omnibus) - `production` (generally not for your main GDK db, but you may need this for e.g. omnibus)
- `development` (this is your main GDK db) - `development` (this is your main GDK db)
- `test` (used for tests like rspec and spinach) - `test` (used for tests like rspec)
## Nuke everything and start over ## Nuke everything and start over
......
...@@ -22,9 +22,9 @@ Please use your best judgement when to use it and please contribute new points t ...@@ -22,9 +22,9 @@ Please use your best judgement when to use it and please contribute new points t
- [ ] Are all [departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) that are needed from your perspective already involved in the issue? (For example is UX missing?) - [ ] Are all [departments](https://about.gitlab.com/handbook/engineering/#engineering-teams) that are needed from your perspective already involved in the issue? (For example is UX missing?)
- [ ] Is the specification complete? Are you missing decisions? How about error handling/defaults/edge cases? Take your time to understand the needed implementation and go through its flow. - [ ] Is the specification complete? Are you missing decisions? How about error handling/defaults/edge cases? Take your time to understand the needed implementation and go through its flow.
- [ ] Are all necessary UX specifications available that you will need in order to implement? Are there new UX components/patterns in the designs? Then contact the UI component team early on. How should error messages or validation be handled? - [ ] Are all necessary UX specifications available that you will need in order to implement? Are there new UX components/patterns in the designs? Then contact the UI component team early on. How should error messages or validation be handled?
- [ ] **Library usage** Use Vuex as soon as you have even a medium state to manage, use Vue router if you need to have different views internally and want to link from the outside. Check what libraries we already have for which occassions. - [ ] **Library usage** Use Vuex as soon as you have even a medium state to manage, use Vue router if you need to have different views internally and want to link from the outside. Check what libraries we already have for which occasions.
- [ ] **Plan your implementation:** - [ ] **Plan your implementation:**
- [ ] **Architecture plan:** Create a plan aligned with GitLab's architecture, how you are going to do the implementation, for example Vue application setup and its components (through [onion skinning](https://gitlab.com/gitlab-org/gitlab-ce/issues/35873#note_39994091)), Store structure and data flow, which existing Vue components can you reuse. Its a good idea to go through your plan with another engineer to refine it. - [ ] **Architecture plan:** Create a plan aligned with GitLab's architecture, how you are going to do the implementation, for example Vue application setup and its components (through [onion skinning](https://gitlab.com/gitlab-org/gitlab-ce/issues/35873#note_39994091)), Store structure and data flow, which existing Vue components can you reuse. It's a good idea to go through your plan with another engineer to refine it.
- [ ] **Backend:** The best way is to kickoff the implementation in a call and discuss with the assigned Backend engineer what you will need from the backend and also when. Can you reuse existing API's? How is the performance with the planned architecture? Maybe create together a JSON mock object to already start with development. - [ ] **Backend:** The best way is to kickoff the implementation in a call and discuss with the assigned Backend engineer what you will need from the backend and also when. Can you reuse existing API's? How is the performance with the planned architecture? Maybe create together a JSON mock object to already start with development.
- [ ] **Communication:** It also makes sense to have for bigger features an own slack channel (normally called #f_{feature_name}) and even weekly demo calls with all people involved. - [ ] **Communication:** It also makes sense to have for bigger features an own slack channel (normally called #f_{feature_name}) and even weekly demo calls with all people involved.
- [ ] **Dependency Plan:** Are there big dependencies in the plan between you and others, then maybe create an execution diagram to show what is blocking which part and the order of the different parts. - [ ] **Dependency Plan:** Are there big dependencies in the plan between you and others, then maybe create an execution diagram to show what is blocking which part and the order of the different parts.
...@@ -56,7 +56,7 @@ Please use your best judgement when to use it and please contribute new points t ...@@ -56,7 +56,7 @@ Please use your best judgement when to use it and please contribute new points t
- [ ] If you have multiple MR's then also smoke test against the final merge. - [ ] If you have multiple MR's then also smoke test against the final merge.
- [ ] Are there any big changes on how and especially how frequently we use the API then let production know about it - [ ] Are there any big changes on how and especially how frequently we use the API then let production know about it
- [ ] Smoke test of the RC on dev., staging., canary deployments and .com - [ ] Smoke test of the RC on dev., staging., canary deployments and .com
- [ ] Follow up on issues that came out of the review. Create isssues for discovered edge cases that should be covered in future iterations. - [ ] Follow up on issues that came out of the review. Create issues for discovered edge cases that should be covered in future iterations.
--- ---
......
...@@ -68,10 +68,10 @@ Often we need to provide data from haml to our Vue application. Let's store it i ...@@ -68,10 +68,10 @@ Often we need to provide data from haml to our Vue application. Let's store it i
You can use `mapState` to access state properties in the components. You can use `mapState` to access state properties in the components.
### `actions.js` ### `actions.js`
An action is a playload of information to send data from our application to our store. An action is a payload of information to send data from our application to our store.
An action is usually composed by a `type` and a `payload` and they describe what happened. An action is usually composed by a `type` and a `payload` and they describe what happened.
Enforcing that every change is described as an action lets us have a clear understanting of what is going on in the app. Enforcing that every change is described as an action lets us have a clear understanding of what is going on in the app.
In this file, we will write the actions that will call the respective mutations: In this file, we will write the actions that will call the respective mutations:
...@@ -87,7 +87,7 @@ In this file, we will write the actions that will call the respective mutations: ...@@ -87,7 +87,7 @@ In this file, we will write the actions that will call the respective mutations:
export const fetchUsers = ({ state, dispatch }) => { export const fetchUsers = ({ state, dispatch }) => {
dispatch('requestUsers'); dispatch('requestUsers');
axios.get(state.endoint) axios.get(state.endpoint)
.then(({ data }) => dispatch('receiveUsersSuccess', data)) .then(({ data }) => dispatch('receiveUsersSuccess', data))
.catch((error) => { .catch((error) => {
dispatch('receiveUsersError', error) dispatch('receiveUsersError', error)
...@@ -102,7 +102,7 @@ In this file, we will write the actions that will call the respective mutations: ...@@ -102,7 +102,7 @@ In this file, we will write the actions that will call the respective mutations:
export const addUser = ({ state, dispatch }, user) => { export const addUser = ({ state, dispatch }, user) => {
dispatch('requestAddUser'); dispatch('requestAddUser');
axios.post(state.endoint, user) axios.post(state.endpoint, user)
.then(({ data }) => dispatch('receiveAddUserSuccess', data)) .then(({ data }) => dispatch('receiveAddUserSuccess', data))
.catch((error) => dispatch('receiveAddUserError', error)); .catch((error) => dispatch('receiveAddUserError', error));
} }
...@@ -126,7 +126,7 @@ The component MUST only dispatch the `fetchNamespace` action. Actions namespaced ...@@ -126,7 +126,7 @@ The component MUST only dispatch the `fetchNamespace` action. Actions namespaced
The `fetch` action will be responsible to dispatch `requestNamespace`, `receiveNamespaceSuccess` and `receiveNamespaceError` The `fetch` action will be responsible to dispatch `requestNamespace`, `receiveNamespaceSuccess` and `receiveNamespaceError`
By following this pattern we guarantee: By following this pattern we guarantee:
1. All aplications follow the same pattern, making it easier for anyone to maintain the code 1. All applications follow the same pattern, making it easier for anyone to maintain the code
1. All data in the application follows the same lifecycle pattern 1. All data in the application follows the same lifecycle pattern
1. Actions are contained and human friendly 1. Actions are contained and human friendly
1. Unit tests are easier 1. Unit tests are easier
...@@ -149,7 +149,7 @@ import { mapActions } from 'vuex'; ...@@ -149,7 +149,7 @@ import { mapActions } from 'vuex';
}; };
``` ```
#### `mutations.js` ### `mutations.js`
The mutations specify how the application state changes in response to actions sent to the store. The mutations specify how the application state changes in response to actions sent to the store.
The only way to change state in a Vuex store should be by committing a mutation. The only way to change state in a Vuex store should be by committing a mutation.
...@@ -175,7 +175,7 @@ Remember that actions only describe that something happened, they don't describe ...@@ -175,7 +175,7 @@ Remember that actions only describe that something happened, they don't describe
state.isLoading = false; state.isLoading = false;
}, },
[types.REQUEST_ADD_USER](state, user) { [types.REQUEST_ADD_USER](state, user) {
state.isAddingUser = true; state.isAddingUser = true;
}, },
[types.RECEIVE_ADD_USER_SUCCESS](state, user) { [types.RECEIVE_ADD_USER_SUCCESS](state, user) {
state.isAddingUser = false; state.isAddingUser = false;
...@@ -183,12 +183,12 @@ Remember that actions only describe that something happened, they don't describe ...@@ -183,12 +183,12 @@ Remember that actions only describe that something happened, they don't describe
}, },
[types.REQUEST_ADD_USER_ERROR](state, error) { [types.REQUEST_ADD_USER_ERROR](state, error) {
state.isAddingUser = true; state.isAddingUser = true;
state.errorAddingUser = error; state.errorAddingUser = error;
}, },
}; };
``` ```
#### `getters.js` ### `getters.js`
Sometimes we may need to get derived state based on store state, like filtering for a specific prop. Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods) Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods)
This can be done through the `getters`: This can be done through the `getters`:
...@@ -213,7 +213,7 @@ import { mapGetters } from 'vuex'; ...@@ -213,7 +213,7 @@ import { mapGetters } from 'vuex';
}; };
``` ```
#### `mutations_types.js` ### `mutations_types.js`
From [vuex mutations docs][vuex-mutations]: From [vuex mutations docs][vuex-mutations]:
> It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application. > It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
...@@ -289,7 +289,7 @@ export default { ...@@ -289,7 +289,7 @@ export default {
``` ```
### Vuex Gotchas ### Vuex Gotchas
1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency through out the application. From Vuex docs: 1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency throughout the application. From Vuex docs:
> why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action. > why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
...@@ -342,7 +342,7 @@ describe('component', () => { ...@@ -342,7 +342,7 @@ describe('component', () => {
}; };
// populate the store // populate the store
store.dipatch('addUser', user); store.dispatch('addUser', user);
vm = new Component({ vm = new Component({
store, store,
...@@ -352,6 +352,18 @@ describe('component', () => { ...@@ -352,6 +352,18 @@ describe('component', () => {
}); });
``` ```
#### Testing Vuex actions and getters
Because we're currently using [`babel-plugin-rewire`](https://github.com/speedskater/babel-plugin-rewire), you may encounter the following error when testing your Vuex actions and getters:
`[vuex] actions should be function or object with "handler" function`
To prevent this error from happening, you need to export an empty function as `default`:
```
// getters.js or actions.js
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
```
[vuex-docs]: https://vuex.vuejs.org [vuex-docs]: https://vuex.vuejs.org
[vuex-structure]: https://vuex.vuejs.org/en/structure.html [vuex-structure]: https://vuex.vuejs.org/en/structure.html
[vuex-mutations]: https://vuex.vuejs.org/en/mutations.html [vuex-mutations]: https://vuex.vuejs.org/en/mutations.html
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
QueryRecorder is a tool for detecting the [N+1 queries problem](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations) from tests. QueryRecorder is a tool for detecting the [N+1 queries problem](http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations) from tests.
> Implemented in [spec/support/query_recorder.rb](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/support/query_recorder.rb) via [9c623e3e](https://gitlab.com/gitlab-org/gitlab-ce/commit/9c623e3e5d7434f2e30f7c389d13e5af4ede770a) > Implemented in [spec/support/query_recorder.rb](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/support/helpers/query_recorder.rb) via [9c623e3e](https://gitlab.com/gitlab-org/gitlab-ce/commit/9c623e3e5d7434f2e30f7c389d13e5af4ede770a)
As a rule, merge requests [should not increase query counts](merge_request_performance_guidelines.md#query-counts). If you find yourself adding something like `.includes(:author, :assignee)` to avoid having `N+1` queries, consider using QueryRecorder to enforce this with a test. Without this, a new feature which causes an additional model to be accessed will silently reintroduce the problem. As a rule, merge requests [should not increase query counts](merge_request_performance_guidelines.md#query-counts). If you find yourself adding something like `.includes(:author, :assignee)` to avoid having `N+1` queries, consider using QueryRecorder to enforce this with a test. Without this, a new feature which causes an additional model to be accessed will silently reintroduce the problem.
......
...@@ -65,12 +65,11 @@ To make sure that indices still fit. You could find great details in: ...@@ -65,12 +65,11 @@ To make sure that indices still fit. You could find great details in:
## Run tests ## Run tests
In order to run the test you can use the following commands: In order to run the test you can use the following commands:
- `rake spinach` to run the spinach suite
- `rake spec` to run the rspec suite - `rake spec` to run the rspec suite
- `rake karma` to run the karma test suite - `rake karma` to run the karma test suite
- `rake gitlab:test` to run all the tests - `rake gitlab:test` to run all the tests
Note: Both `rake spinach` and `rake spec` takes significant time to pass. Note: `rake spec` takes significant time to pass.
Instead of running full test suite locally you can save a lot of time by running Instead of running full test suite locally you can save a lot of time by running
a single test or directory related to your changes. After you submit merge request a single test or directory related to your changes. After you submit merge request
CI will run full test suite for you. Green CI status in the merge request means CI will run full test suite for you. Green CI status in the merge request means
...@@ -82,12 +81,10 @@ files it can find, also the ones in `/tmp` ...@@ -82,12 +81,10 @@ files it can find, also the ones in `/tmp`
To run a single test file you can use: To run a single test file you can use:
- `bin/rspec spec/controllers/commit_controller_spec.rb` for a rspec test - `bin/rspec spec/controllers/commit_controller_spec.rb` for a rspec test
- `bin/spinach features/project/issues/milestones.feature` for a spinach test
To run several tests inside one directory: To run several tests inside one directory:
- `bin/rspec spec/requests/api/` for the rspec tests if you want to test API only - `bin/rspec spec/requests/api/` for the rspec tests if you want to test API only
- `bin/spinach features/profile/` for the spinach tests if you want to test only profile pages
### Speed-up tests, rake tasks, and migrations ### Speed-up tests, rake tasks, and migrations
......
...@@ -12,8 +12,7 @@ Here are some things to keep in mind regarding test performance: ...@@ -12,8 +12,7 @@ Here are some things to keep in mind regarding test performance:
- `FactoryBot.build(...)` and `.build_stubbed` are faster than `.create`. - `FactoryBot.build(...)` and `.build_stubbed` are faster than `.create`.
- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`, - Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
`spy`, or `double` will do. Database persistence is slow! `spy`, or `double` will do. Database persistence is slow!
- Don't mark a feature as requiring JavaScript (through `@javascript` in - Don't mark a feature as requiring JavaScript (through `:js` in RSpec) unless it's _actually_ required for the test
Spinach or `:js` in RSpec) unless it's _actually_ required for the test
to be valid. Headless browser testing is slow! to be valid. Headless browser testing is slow!
[parallelization]: ci.md#test-suite-parallelization-on-the-ci [parallelization]: ci.md#test-suite-parallelization-on-the-ci
...@@ -134,11 +133,24 @@ really fast since: ...@@ -134,11 +133,24 @@ really fast since:
- gitlab-shell and Gitaly setup are skipped - gitlab-shell and Gitaly setup are skipped
- Test repositories setup are skipped - Test repositories setup are skipped
Note that in some cases, you might have to add some `require_dependency 'foo'` `fast_spec_helper` also support autoloading classes that are located inside the
in your file under test since Rails autoloading is not available in these cases. `lib/` directory. It means that as long as your class / module is using only
code from the `lib/` directory you will not need to explicitly load any
dependencies. `fast_spec_helper` also loads all ActiveSupport extensions,
including core extensions that are commonly used in the Rails environment.
This shouldn't be a problem since explicitely listing dependencies should be Note that in some cases, you might still have to load some dependencies using
considered a good practice anyway. `require_dependency` when a code is using gems or a dependency is not located
in `lib/`.
For example, if you want to test your code that is calling the
`Gitlab::UntrustedRegexp` class, which under the hood uses `re2` library, you
should either add `require_dependency 're2'` to files in your library that
need `re2` gem, to make this requirement explicit, or you can add it to the
spec itself, but the former is preferred.
It takes around one second to load tests that are using `fast_spec_helper`
instead of 30+ seconds in case of a regular `spec_helper`.
### `let` variables ### `let` variables
......
...@@ -24,8 +24,7 @@ Our current CI parallelization setup is as follows: ...@@ -24,8 +24,7 @@ Our current CI parallelization setup is as follows:
uploaded to S3. uploaded to S3.
After that, the next pipeline will use the up-to-date After that, the next pipeline will use the up-to-date
`knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file. The same strategy `knapsack/${CI_PROJECT_NAME}/rspec_report-master.json` file.
is used for Spinach tests as well.
### Monitoring ### Monitoring
......
...@@ -280,26 +280,6 @@ describe "Admin::AbuseReports", :js do ...@@ -280,26 +280,6 @@ describe "Admin::AbuseReports", :js do
end end
``` ```
### Spinach errors due to missing JavaScript
NOTE: **Note:** Since we are discouraging the use of Spinach when writing new
feature tests, you shouldn't ever need to use this. This information is kept
available for legacy purposes only.
In Spinach, the JavaScript driver is enabled differently. In the `*.feature`
file for the failing spec, add the `@javascript` flag above the Scenario:
```
@javascript
Scenario: Developer can approve merge request
Given I am a "Shop" developer
And I visit project "Shop" merge requests page
And merge request 'Bug NS-04' must be approved
And I click link "Bug NS-04"
When I click link "Approve"
Then I should see approved merge request "Bug NS-04"
```
[jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html [jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html
[jasmine-jquery]: https://github.com/velesin/jasmine-jquery [jasmine-jquery]: https://github.com/velesin/jasmine-jquery
[karma]: http://karma-runner.github.io/ [karma]: http://karma-runner.github.io/
......
...@@ -72,21 +72,6 @@ Everything you should know about how to run end-to-end tests using ...@@ -72,21 +72,6 @@ Everything you should know about how to run end-to-end tests using
--- ---
## Spinach (feature) tests
GitLab [moved from Cucumber to Spinach](https://github.com/gitlabhq/gitlabhq/pull/1426)
for its feature/integration tests in September 2012.
As of March 2016, we are [trying to avoid adding new Spinach
tests](https://gitlab.com/gitlab-org/gitlab-ce/issues/14121) going forward,
opting for [RSpec feature](#features-integration) specs.
Adding new Spinach scenarios is acceptable _only if_ the new scenario requires
no more than one new `step` definition. If more than that is required, the
test should be re-implemented using RSpec instead.
---
[Return to Development documentation](../README.md) [Return to Development documentation](../README.md)
[^1]: /ci/yaml/README.html#dependencies [^1]: /ci/yaml/README.html#dependencies
......
...@@ -81,7 +81,6 @@ possible). ...@@ -81,7 +81,6 @@ possible).
| Tests path | Testing engine | Notes | | Tests path | Testing engine | Notes |
| ---------- | -------------- | ----- | | ---------- | -------------- | ----- |
| `spec/features/` | [Capybara] + [RSpec] | If your spec has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. | | `spec/features/` | [Capybara] + [RSpec] | If your spec has the `:js` metadata, the browser driver will be [Poltergeist], otherwise it's using [RackTest]. |
| `features/` | Spinach | Spinach tests are deprecated, [you shouldn't add new Spinach tests](#spinach-feature-tests). |
### Consider **not** writing a system test! ### Consider **not** writing a system test!
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment