Commit 5996e8f9 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into issue-boards-no-avatar

parents 79d50538 985737fd
...@@ -45,6 +45,7 @@ eslint-report.html ...@@ -45,6 +45,7 @@ eslint-report.html
/public/uploads.* /public/uploads.*
/public/uploads/ /public/uploads/
/shared/artifacts/ /shared/artifacts/
/spec/javascripts/fixtures/blob/pdf/
/rails_best_practices_output.html /rails_best_practices_output.html
/tags /tags
/tmp/* /tmp/*
......
...@@ -19,8 +19,8 @@ variables: ...@@ -19,8 +19,8 @@ variables:
before_script: before_script:
- bundle --version - bundle --version
- . scripts/utils.sh - source scripts/utils.sh
- ./scripts/prepare_build.sh - source scripts/prepare_build.sh
stages: stages:
- prepare - prepare
...@@ -68,6 +68,13 @@ stages: ...@@ -68,6 +68,13 @@ stages:
- //@gitlab-org/gitlab-ee - //@gitlab-org/gitlab-ee
- //@gitlab/gitlab-ee - //@gitlab/gitlab-ee
# Skip all jobs except the ones that begin with 'docs/'.
# Used for commits including ONLY documentation changes.
# https://docs.gitlab.com/ce/development/writing_documentation.html#testing
.except-docs: &except-docs
except:
- /^docs\/.*/
.rspec-knapsack: &rspec-knapsack .rspec-knapsack: &rspec-knapsack
stage: test stage: test
<<: *dedicated-runner <<: *dedicated-runner
...@@ -91,11 +98,13 @@ stages: ...@@ -91,11 +98,13 @@ stages:
.rspec-knapsack-pg: &rspec-knapsack-pg .rspec-knapsack-pg: &rspec-knapsack-pg
<<: *rspec-knapsack <<: *rspec-knapsack
<<: *use-pg <<: *use-pg
<<: *except-docs
.rspec-knapsack-mysql: &rspec-knapsack-mysql .rspec-knapsack-mysql: &rspec-knapsack-mysql
<<: *rspec-knapsack <<: *rspec-knapsack
<<: *use-mysql <<: *use-mysql
<<: *only-master-and-ee-or-mysql <<: *only-master-and-ee-or-mysql
<<: *except-docs
.spinach-knapsack: &spinach-knapsack .spinach-knapsack: &spinach-knapsack
stage: test stage: test
...@@ -120,16 +129,19 @@ stages: ...@@ -120,16 +129,19 @@ stages:
.spinach-knapsack-pg: &spinach-knapsack-pg .spinach-knapsack-pg: &spinach-knapsack-pg
<<: *spinach-knapsack <<: *spinach-knapsack
<<: *use-pg <<: *use-pg
<<: *except-docs
.spinach-knapsack-mysql: &spinach-knapsack-mysql .spinach-knapsack-mysql: &spinach-knapsack-mysql
<<: *spinach-knapsack <<: *spinach-knapsack
<<: *use-mysql <<: *use-mysql
<<: *only-master-and-ee-or-mysql <<: *only-master-and-ee-or-mysql
<<: *except-docs
# Prepare and merge knapsack tests # Prepare and merge knapsack tests
knapsack: knapsack:
<<: *knapsack-state <<: *knapsack-state
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
stage: prepare stage: prepare
script: script:
- mkdir -p knapsack/${CI_PROJECT_NAME}/ - mkdir -p knapsack/${CI_PROJECT_NAME}/
...@@ -156,6 +168,7 @@ update-knapsack: ...@@ -156,6 +168,7 @@ update-knapsack:
setup-test-env: setup-test-env:
<<: *use-pg <<: *use-pg
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
stage: prepare stage: prepare
script: script:
- node --version - node --version
...@@ -240,35 +253,51 @@ spinach mysql 9 10: *spinach-knapsack-mysql ...@@ -240,35 +253,51 @@ spinach mysql 9 10: *spinach-knapsack-mysql
SETUP_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
.exec: &exec .rake-exec: &rake-exec
<<: *ruby-static-analysis <<: *ruby-static-analysis
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
stage: test stage: test
script: script:
- bundle exec $CI_JOB_NAME - bundle exec rake $CI_JOB_NAME
rubocop: static-analysis:
<<: *ruby-static-analysis <<: *ruby-static-analysis
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
stage: test stage: test
script: script:
- bundle exec "rubocop --require rubocop-rspec" - scripts/static-analysis
rake haml_lint: *exec # Documentation checks:
rake scss_lint: *exec # - Check validity of relative links
rake config_lint: *exec # - Make sure cURL examples in API docs use the full switches
rake brakeman: *exec docs lint:
rake flay: *exec image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
license_finder: *exec stage: test
rake downtime_check: <<: *dedicated-runner
<<: *exec cache: {}
dependencies: []
before_script: []
script:
- scripts/lint-doc.sh
- mv doc/ /nanoc/content/
- cd /nanoc
# Build HTML from Markdown
- bundle exec nanoc
# Check the internal links
- bundle exec nanoc check internal_links
downtime_check:
<<: *rake-exec
except: except:
- master - master
- tags - tags
- /^[\d-]+-stable(-ee)?$/ - /^[\d-]+-stable(-ee)?$/
- /^docs\/*/
rake ee_compat_check: ee_compat_check:
<<: *exec <<: *rake-exec
only: only:
- branches@gitlab-org/gitlab-ce - branches@gitlab-org/gitlab-ce
except: except:
...@@ -290,6 +319,7 @@ rake ee_compat_check: ...@@ -290,6 +319,7 @@ rake ee_compat_check:
.db-migrate-reset: &db-migrate-reset .db-migrate-reset: &db-migrate-reset
stage: test stage: test
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
script: script:
- bundle exec rake db:migrate:reset - bundle exec rake db:migrate:reset
...@@ -304,6 +334,7 @@ rake mysql db:migrate:reset: ...@@ -304,6 +334,7 @@ rake mysql db:migrate:reset:
.db-rollback: &db-rollback .db-rollback: &db-rollback
stage: test stage: test
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
script: script:
- bundle exec rake db:rollback STEP=120 - bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate - bundle exec rake db:migrate
...@@ -319,6 +350,7 @@ rake mysql db:rollback: ...@@ -319,6 +350,7 @@ rake mysql db:rollback:
.db-seed_fu: &db-seed_fu .db-seed_fu: &db-seed_fu
stage: test stage: test
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
variables: variables:
SIZE: "1" SIZE: "1"
SETUP_DB: "false" SETUP_DB: "false"
...@@ -344,6 +376,7 @@ rake mysql db:seed_fu: ...@@ -344,6 +376,7 @@ rake mysql db:seed_fu:
rake gitlab:assets:compile: rake gitlab:assets:compile:
stage: test stage: test
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
dependencies: [] dependencies: []
variables: variables:
NODE_ENV: "production" NODE_ENV: "production"
...@@ -367,6 +400,7 @@ rake karma: ...@@ -367,6 +400,7 @@ rake karma:
stage: test stage: test
<<: *use-pg <<: *use-pg
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
variables: variables:
BABEL_ENV: "coverage" BABEL_ENV: "coverage"
script: script:
...@@ -378,31 +412,6 @@ rake karma: ...@@ -378,31 +412,6 @@ rake karma:
paths: paths:
- coverage-javascript/ - coverage-javascript/
docs:check:apilint:
image: "phusion/baseimage"
stage: test
<<: *dedicated-runner
cache: {}
dependencies: []
before_script: []
script:
- scripts/lint-doc.sh
docs:check:links:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine"
stage: test
<<: *dedicated-runner
cache: {}
dependencies: []
before_script: []
script:
- mv doc/ /nanoc/content/
- cd /nanoc
# Build HTML from Markdown
- bundle exec nanoc
# Check the internal links
- bundle exec nanoc check internal_links
bundler:audit: bundler:audit:
stage: test stage: test
<<: *ruby-static-analysis <<: *ruby-static-analysis
...@@ -447,6 +456,7 @@ coverage: ...@@ -447,6 +456,7 @@ coverage:
stage: post-test stage: post-test
services: [] services: []
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
variables: variables:
SETUP_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "true" USE_BUNDLE_INSTALL: "true"
...@@ -460,15 +470,9 @@ coverage: ...@@ -460,15 +470,9 @@ coverage:
- coverage/index.html - coverage/index.html
- coverage/assets/ - coverage/assets/
lint:javascript:
<<: *dedicated-runner
stage: test
before_script: []
script:
- yarn run eslint
lint:javascript:report: lint:javascript:report:
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs
stage: post-test stage: post-test
before_script: [] before_script: []
script: script:
...@@ -499,22 +503,6 @@ trigger_docs: ...@@ -499,22 +503,6 @@ trigger_docs:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee - master@gitlab-org/gitlab-ee
# Notify slack in the end
notify:slack:
stage: post-test
<<: *dedicated-runner
variables:
SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false"
script:
- ./scripts/notify_slack.sh "#development" "Build on \`$CI_COMMIT_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_COMMIT_SHA"/pipelines>"
when: on_failure
only:
- master@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- tags@gitlab-org/gitlab-ee
pages: pages:
before_script: [] before_script: []
stage: pages stage: pages
......
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "regression" or "bug" label:
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=regression
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=bug
and verify the issue you're about to submit isn't a duplicate.
Please remove this notice if you're confident your issue isn't a duplicate.
------
### Summary ### Summary
(Summarize the bug encountered concisely) (Summarize the bug encountered concisely)
...@@ -56,3 +70,5 @@ logs, and code as it's very hard to read otherwise.) ...@@ -56,3 +70,5 @@ logs, and code as it's very hard to read otherwise.)
### Possible fixes ### Possible fixes
(If you can, link to the line of code that might be responsible for the problem) (If you can, link to the line of code that might be responsible for the problem)
/label ~bug
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
filtered by the "feature proposal" label:
- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=feature+proposal
and verify the issue you're about to submit isn't a duplicate.
Please remove this notice if you're confident your issue isn't a duplicate.
------
### Description ### Description
(Include problem, use cases, benefits, and/or goals) (Include problem, use cases, benefits, and/or goals)
...@@ -15,3 +28,5 @@ ...@@ -15,3 +28,5 @@
3. How does someone use this 3. How does someone use this
During implementation, this can then be copied and used as a starter for the documentation.) During implementation, this can then be copied and used as a starter for the documentation.)
/label ~"feature proposal"
...@@ -961,7 +961,7 @@ RSpec/DescribeSymbol: ...@@ -961,7 +961,7 @@ RSpec/DescribeSymbol:
# Checks that the second argument to top level describe is the tested method # Checks that the second argument to top level describe is the tested method
# name. # name.
RSpec/DescribedClass: RSpec/DescribedClass:
Enabled: false Enabled: true
# Checks for long example. # Checks for long example.
RSpec/ExampleLength: RSpec/ExampleLength:
...@@ -983,10 +983,12 @@ RSpec/ExpectActual: ...@@ -983,10 +983,12 @@ RSpec/ExpectActual:
# Checks the file and folder naming of the spec file. # Checks the file and folder naming of the spec file.
RSpec/FilePath: RSpec/FilePath:
Enabled: false Enabled: true
CustomTransform: IgnoreMethods: true
RuboCop: rubocop Exclude:
RSpec: rspec - 'qa/**/*'
- 'spec/javascripts/fixtures/*'
- 'spec/requests/api/v3/*'
# Checks if there are focused specs. # Checks if there are focused specs.
RSpec/Focus: RSpec/Focus:
......
...@@ -2,6 +2,17 @@ ...@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 9.1.2 (2017-05-01)
- Add index on ci_runners.contacted_at. !10876 (blackst0ne)
- Fix pipeline events description for Slack and Mattermost integration. !10908
- Fixed milestone sidebar showing incorrect number of MRs when collapsed. !10933
- Fix ordering of commits in the network graph. !10936
- Ensure the chat notifications service properly saves the "Notify only default branch" setting. !10959
- Lazily sets UUID in ApplicationSetting for new installations.
- Skip validation when creating internal (ghost, service desk) users.
- Use GitLab Pages v0.4.1.
## 9.1.1 (2017-04-26) ## 9.1.1 (2017-04-26)
- Add a transaction around move_issues_to_ghost_user. !10465 - Add a transaction around move_issues_to_ghost_user. !10465
......
This diff is collapsed.
...@@ -85,14 +85,14 @@ gem 'kaminari', '~> 0.17.0' ...@@ -85,14 +85,14 @@ gem 'kaminari', '~> 0.17.0'
gem 'hamlit', '~> 2.6.1' gem 'hamlit', '~> 2.6.1'
# Files attachments # Files attachments
gem 'carrierwave', '~> 0.11.0' gem 'carrierwave', '~> 1.0'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
# for backups # for backups
gem 'fog-aws', '~> 0.9' gem 'fog-aws', '~> 0.9'
gem 'fog-core', '~> 1.40' gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5' gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3' gem 'fog-local', '~> 0.3'
gem 'fog-openstack', '~> 0.1' gem 'fog-openstack', '~> 0.1'
......
...@@ -105,12 +105,10 @@ GEM ...@@ -105,12 +105,10 @@ GEM
capybara-screenshot (1.0.14) capybara-screenshot (1.0.14)
capybara (>= 1.0, < 3) capybara (>= 1.0, < 3)
launchy launchy
carrierwave (0.11.2) carrierwave (1.0.0)
activemodel (>= 3.2.0) activemodel (>= 4.0.0)
activesupport (>= 3.2.0) activesupport (>= 4.0.0)
json (>= 1.7)
mime-types (>= 1.16) mime-types (>= 1.16)
mimemagic (>= 0.3.0)
cause (0.1) cause (0.1)
charlock_holmes (0.7.3) charlock_holmes (0.7.3)
chronic (0.10.2) chronic (0.10.2)
...@@ -184,7 +182,7 @@ GEM ...@@ -184,7 +182,7 @@ GEM
erubis (2.7.0) erubis (2.7.0)
escape_utils (1.1.1) escape_utils (1.1.1)
eventmachine (1.0.8) eventmachine (1.0.8)
excon (0.52.0) excon (0.55.0)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
extlib (0.9.16) extlib (0.9.16)
...@@ -210,12 +208,12 @@ GEM ...@@ -210,12 +208,12 @@ GEM
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
fog-aws (0.11.0) fog-aws (0.13.0)
fog-core (~> 1.38) fog-core (~> 1.38)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
ipaddress (~> 0.8) ipaddress (~> 0.8)
fog-core (1.42.0) fog-core (1.44.1)
builder builder
excon (~> 0.49) excon (~> 0.49)
formatador (~> 0.2) formatador (~> 0.2)
...@@ -237,9 +235,9 @@ GEM ...@@ -237,9 +235,9 @@ GEM
fog-json (>= 1.0) fog-json (>= 1.0)
fog-xml (>= 0.1) fog-xml (>= 0.1)
ipaddress (>= 0.8) ipaddress (>= 0.8)
fog-xml (0.1.2) fog-xml (0.1.3)
fog-core fog-core
nokogiri (~> 1.5, >= 1.5.11) nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.1) font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1) railties (>= 3.2, < 5.1)
foreman (0.78.0) foreman (0.78.0)
...@@ -330,7 +328,7 @@ GEM ...@@ -330,7 +328,7 @@ GEM
grape-entity (0.6.0) grape-entity (0.6.0)
activesupport activesupport
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grpc (1.1.2) grpc (1.2.5)
google-protobuf (~> 3.1) google-protobuf (~> 3.1)
googleauth (~> 0.5.1) googleauth (~> 0.5.1)
haml (4.0.7) haml (4.0.7)
...@@ -871,7 +869,7 @@ DEPENDENCIES ...@@ -871,7 +869,7 @@ DEPENDENCIES
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
capybara (~> 2.6.2) capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.11.0) carrierwave (~> 1.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
chronic (~> 0.10.2) chronic (~> 0.10.2)
chronic_duration (~> 0.10.6) chronic_duration (~> 0.10.6)
...@@ -896,7 +894,7 @@ DEPENDENCIES ...@@ -896,7 +894,7 @@ DEPENDENCIES
ffaker (~> 2.4) ffaker (~> 2.4)
flay (~> 2.8.0) flay (~> 2.8.0)
fog-aws (~> 0.9) fog-aws (~> 0.9)
fog-core (~> 1.40) fog-core (~> 1.44)
fog-google (~> 0.5) fog-google (~> 0.5)
fog-local (~> 0.3) fog-local (~> 0.3)
fog-openstack (~> 0.1) fog-openstack (~> 0.1)
......
# GitLab Contributing Process ## GitLab Core Team & GitLab Inc. Contribution Process
---
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Purpose of describing the contributing process](#purpose-of-describing-the-contributing-process)
- [Common actions](#common-actions)
- [Merge request coaching](#merge-request-coaching)
- [Assigning issues](#assigning-issues)
- [Be kind](#be-kind)
- [Feature freeze on the 7th for the release on the 22nd](#feature-freeze-on-the-7th-for-the-release-on-the-22nd)
- [Between the 1st and the 7th](#between-the-1st-and-the-7th)
- [On the 7th](#on-the-7th)
- [After the 7th](#after-the-7th)
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
- [Retrospective](#retrospective)
- [Kickoff](#kickoff)
- [Copy & paste responses](#copy--paste-responses)
- [Improperly formatted issue](#improperly-formatted-issue)
- [Issue report for old version](#issue-report-for-old-version)
- [Support requests and configuration questions](#support-requests-and-configuration-questions)
- [Code format](#code-format)
- [Issue fixed in newer version](#issue-fixed-in-newer-version)
- [Improperly formatted merge request](#improperly-formatted-merge-request)
- [Inactivity close of an issue](#inactivity-close-of-an-issue)
- [Inactivity close of a merge request](#inactivity-close-of-a-merge-request)
- [Accepting merge requests](#accepting-merge-requests)
- [Only accepting merge requests with green tests](#only-accepting-merge-requests-with-green-tests)
- [Closing down the issue tracker on GitHub](#closing-down-the-issue-tracker-on-github)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
---
## Purpose of describing the contributing process ## Purpose of describing the contributing process
Below we describe the contributing process to GitLab for two reasons. So that Below we describe the contributing process to GitLab for two reasons:
contributors know what to expect from maintainers (possible responses, friendly
treatment, etc.). And so that maintainers know what to expect from contributors 1. Contributors know what to expect from maintainers (possible responses, friendly
(use the latest version, ensure that the issue is addressed, friendly treatment, treatment, etc.)
etc.). 1. Maintainers know what to expect from contributors (use the latest version,
ensure that the issue is addressed, friendly treatment, etc.).
- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/) - [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/)
## Common actions ## Common actions
### Issue triaging
Our issue triage policies are [described in our handbook]. You are very welcome
to help the GitLab team triage issues. We also organize [issue bash events] once
every quarter.
The most important thing is making sure valid issues receive feedback from the
development team. Therefore the priority is mentioning developers that can help
on those issues. Please select someone with relevant experience from
[GitLab team][team]. If there is nobody mentioned with that expertise
look in the commit history for the affected files to find someone. Avoid
mentioning the lead developer, this is the person that is least likely to give a
timely response. If the involvement of the lead developer is needed the other
core team members will mention this person.
[described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/
[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
### Merge request coaching ### Merge request coaching
Several people from the [GitLab team][team] are helping community members to get Several people from the [GitLab team][team] are helping community members to get
...@@ -37,12 +55,6 @@ their contributions accepted by meeting our [Definition of done][done]. ...@@ -37,12 +55,6 @@ their contributions accepted by meeting our [Definition of done][done].
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/. What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/.
## Workflow labels
Labelling issues is described in the [GitLab Inc engineering workflow].
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
## Assigning issues ## Assigning issues
If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover. If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover.
...@@ -146,6 +158,29 @@ release should have the correct milestone assigned _and_ have the label ...@@ -146,6 +158,29 @@ release should have the correct milestone assigned _and_ have the label
Merge requests without a milestone and this label will Merge requests without a milestone and this label will
not be merged into any stable branches. not be merged into any stable branches.
## Release retrospective and kickoff
### Retrospective
After each release, we have a retrospective call where we discuss what went well,
what went wrong, and what we can improve for the next release. The
[retrospective notes] are public and you are invited to comment on them.
If you're interested, you can even join the
[retrospective call][retro-kickoff-call], on the first working day after the
22nd at 6pm CET / 9am PST.
### Kickoff
Before working on the next release, we have a
kickoff call to explain what we expect to ship in the next release. The
[kickoff notes] are public and you are invited to comment on them.
If you're interested, you can even join the [kickoff call][retro-kickoff-call],
on the first working day after the 7th at 6pm CET / 9am PST..
[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
[retro-kickoff-call]: https://gitlab.zoom.us/j/918821206
## Copy & paste responses ## Copy & paste responses
### Improperly formatted issue ### Improperly formatted issue
......
app/assets/images/ci_favicons/favicon_status_canceled.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_canceled.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_canceled.ico
app/assets/images/ci_favicons/favicon_status_canceled.ico
app/assets/images/ci_favicons/favicon_status_canceled.ico
app/assets/images/ci_favicons/favicon_status_canceled.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_created.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_created.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_created.ico
app/assets/images/ci_favicons/favicon_status_created.ico
app/assets/images/ci_favicons/favicon_status_created.ico
app/assets/images/ci_favicons/favicon_status_created.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_failed.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_failed.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_failed.ico
app/assets/images/ci_favicons/favicon_status_failed.ico
app/assets/images/ci_favicons/favicon_status_failed.ico
app/assets/images/ci_favicons/favicon_status_failed.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_manual.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_manual.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_manual.ico
app/assets/images/ci_favicons/favicon_status_manual.ico
app/assets/images/ci_favicons/favicon_status_manual.ico
app/assets/images/ci_favicons/favicon_status_manual.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_not_found.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_not_found.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_not_found.ico
app/assets/images/ci_favicons/favicon_status_not_found.ico
app/assets/images/ci_favicons/favicon_status_not_found.ico
app/assets/images/ci_favicons/favicon_status_not_found.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_pending.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_pending.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_pending.ico
app/assets/images/ci_favicons/favicon_status_pending.ico
app/assets/images/ci_favicons/favicon_status_pending.ico
app/assets/images/ci_favicons/favicon_status_pending.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_running.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_running.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_running.ico
app/assets/images/ci_favicons/favicon_status_running.ico
app/assets/images/ci_favicons/favicon_status_running.ico
app/assets/images/ci_favicons/favicon_status_running.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_skipped.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_skipped.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_skipped.ico
app/assets/images/ci_favicons/favicon_status_skipped.ico
app/assets/images/ci_favicons/favicon_status_skipped.ico
app/assets/images/ci_favicons/favicon_status_skipped.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_success.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_success.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_success.ico
app/assets/images/ci_favicons/favicon_status_success.ico
app/assets/images/ci_favicons/favicon_status_success.ico
app/assets/images/ci_favicons/favicon_status_success.ico
  • 2-up
  • Swipe
  • Onion skin
app/assets/images/ci_favicons/favicon_status_warning.ico

5.3 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_warning.ico

4.19 KB | W: | H:

app/assets/images/ci_favicons/favicon_status_warning.ico
app/assets/images/ci_favicons/favicon_status_warning.ico
app/assets/images/ci_favicons/favicon_status_warning.ico
app/assets/images/ci_favicons/favicon_status_warning.ico
  • 2-up
  • Swipe
  • Onion skin
...@@ -62,6 +62,7 @@ function glEmojiTag(inputName, options) { ...@@ -62,6 +62,7 @@ function glEmojiTag(inputName, options) {
data-fallback-src="${fallbackImageSrc}" data-fallback-src="${fallbackImageSrc}"
${fallbackSpriteAttribute} ${fallbackSpriteAttribute}
data-unicode-version="${emojiInfo.unicodeVersion}" data-unicode-version="${emojiInfo.unicodeVersion}"
title="${emojiInfo.description}"
> >
${contents} ${contents}
</gl-emoji> </gl-emoji>
......
/* eslint-disable no-new */ /* eslint-disable no-new */
import Vue from 'vue'; import Vue from 'vue';
import PDFLab from 'vendor/pdflab'; import pdfLab from '../../pdf/index.vue';
import workerSrc from 'vendor/pdf.worker';
Vue.use(PDFLab, {
workerSrc,
});
export default () => { export default () => {
const el = document.getElementById('js-pdf-viewer'); const el = document.getElementById('js-pdf-viewer');
...@@ -20,6 +15,9 @@ export default () => { ...@@ -20,6 +15,9 @@ export default () => {
pdf: el.dataset.endpoint, pdf: el.dataset.endpoint,
}; };
}, },
components: {
pdfLab,
},
methods: { methods: {
onLoad() { onLoad() {
this.loading = false; this.loading = false;
......
...@@ -6,7 +6,7 @@ export default class BlobViewer { ...@@ -6,7 +6,7 @@ export default class BlobViewer {
this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn'); this.copySourceBtn = document.querySelector('.js-copy-blob-source-btn');
this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]'); this.simpleViewer = document.querySelector('.blob-viewer[data-type="simple"]');
this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]'); this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]');
this.$blobContentHolder = $('#blob-content-holder'); this.$fileHolder = $('.file-holder');
let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type'); let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type');
...@@ -82,7 +82,7 @@ export default class BlobViewer { ...@@ -82,7 +82,7 @@ export default class BlobViewer {
viewer.setAttribute('data-loaded', 'true'); viewer.setAttribute('data-loaded', 'true');
this.$blobContentHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}); });
......
...@@ -27,6 +27,9 @@ gl.issueBoards.BoardSidebar = Vue.extend({ ...@@ -27,6 +27,9 @@ gl.issueBoards.BoardSidebar = Vue.extend({
computed: { computed: {
showSidebar () { showSidebar () {
return Object.keys(this.issue).length; return Object.keys(this.issue).length;
},
assigneeId() {
return this.issue.assignee ? this.issue.assignee.id : 0;
} }
}, },
watch: { watch: {
......
...@@ -46,6 +46,7 @@ export default Vue.component('pipelines-table', { ...@@ -46,6 +46,7 @@ export default Vue.component('pipelines-table', {
isLoading: false, isLoading: false,
hasError: false, hasError: false,
isMakingRequest: false, isMakingRequest: false,
updateGraphDropdown: false,
}; };
}, },
...@@ -130,15 +131,21 @@ export default Vue.component('pipelines-table', { ...@@ -130,15 +131,21 @@ export default Vue.component('pipelines-table', {
const pipelines = response.pipelines || response; const pipelines = response.pipelines || response;
this.store.storePipelines(pipelines); this.store.storePipelines(pipelines);
this.isLoading = false; this.isLoading = false;
this.updateGraphDropdown = true;
}, },
errorCallback() { errorCallback() {
this.hasError = true; this.hasError = true;
this.isLoading = false; this.isLoading = false;
this.updateGraphDropdown = false;
}, },
setIsMakingRequest(isMakingRequest) { setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest; this.isMakingRequest = isMakingRequest;
if (isMakingRequest) {
this.updateGraphDropdown = false;
}
}, },
}, },
...@@ -163,7 +170,9 @@ export default Vue.component('pipelines-table', { ...@@ -163,7 +170,9 @@ export default Vue.component('pipelines-table', {
v-if="shouldRenderTable"> v-if="shouldRenderTable">
<pipelines-table-component <pipelines-table-component
:pipelines="state.pipelines" :pipelines="state.pipelines"
:service="service" /> :service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div> </div>
</div> </div>
`, `,
......
/* eslint-disable no-new */
/* global Flash */
import DropLab from './droplab/drop_lab';
import ISetter from './droplab/plugins/input_setter';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = Object.assign({}, ISetter);
const CREATE_MERGE_REQUEST = 'create-mr';
const CREATE_BRANCH = 'create-branch';
export default class CreateMergeRequestDropdown {
constructor(wrapperEl) {
this.wrapperEl = wrapperEl;
this.createMergeRequestButton = this.wrapperEl.querySelector('.js-create-merge-request');
this.dropdownToggle = this.wrapperEl.querySelector('.js-dropdown-toggle');
this.dropdownList = this.wrapperEl.querySelector('.dropdown-menu');
this.availableButton = this.wrapperEl.querySelector('.available');
this.unavailableButton = this.wrapperEl.querySelector('.unavailable');
this.unavailableButtonArrow = this.unavailableButton.querySelector('.fa');
this.unavailableButtonText = this.unavailableButton.querySelector('.text');
this.createBranchPath = this.wrapperEl.dataset.createBranchPath;
this.canCreatePath = this.wrapperEl.dataset.canCreatePath;
this.createMrPath = this.wrapperEl.dataset.createMrPath;
this.droplabInitialized = false;
this.isCreatingMergeRequest = false;
this.mergeRequestCreated = false;
this.isCreatingBranch = false;
this.branchCreated = false;
this.init();
}
init() {
this.checkAbilityToCreateBranch();
}
available() {
this.availableButton.classList.remove('hide');
this.unavailableButton.classList.add('hide');
}
unavailable() {
this.availableButton.classList.add('hide');
this.unavailableButton.classList.remove('hide');
}
enable() {
this.createMergeRequestButton.classList.remove('disabled');
this.createMergeRequestButton.removeAttribute('disabled');
this.dropdownToggle.classList.remove('disabled');
this.dropdownToggle.removeAttribute('disabled');
}
disable() {
this.createMergeRequestButton.classList.add('disabled');
this.createMergeRequestButton.setAttribute('disabled', 'disabled');
this.dropdownToggle.classList.add('disabled');
this.dropdownToggle.setAttribute('disabled', 'disabled');
}
hide() {
this.wrapperEl.classList.add('hide');
}
setUnavailableButtonState(isLoading = true) {
if (isLoading) {
this.unavailableButtonArrow.classList.add('fa-spinner', 'fa-spin');
this.unavailableButtonArrow.classList.remove('fa-exclamation-triangle');
this.unavailableButtonText.textContent = 'Checking branch availability…';
} else {
this.unavailableButtonArrow.classList.remove('fa-spinner', 'fa-spin');
this.unavailableButtonArrow.classList.add('fa-exclamation-triangle');
this.unavailableButtonText.textContent = 'New branch unavailable';
}
}
checkAbilityToCreateBranch() {
return $.ajax({
type: 'GET',
dataType: 'json',
url: this.canCreatePath,
beforeSend: () => this.setUnavailableButtonState(),
})
.done((data) => {
this.setUnavailableButtonState(false);
if (data.can_create_branch) {
this.available();
this.enable();
if (!this.droplabInitialized) {
this.droplabInitialized = true;
this.initDroplab();
this.bindEvents();
}
} else if (data.has_related_branch) {
this.hide();
}
}).fail(() => {
this.unavailable();
this.disable();
new Flash('Failed to check if a new branch can be created.');
});
}
initDroplab() {
this.droplab = new DropLab();
this.droplab.init(this.dropdownToggle, this.dropdownList, [InputSetter],
this.getDroplabConfig());
}
getDroplabConfig() {
return {
InputSetter: [{
input: this.createMergeRequestButton,
valueAttribute: 'data-value',
inputAttribute: 'data-action',
}, {
input: this.createMergeRequestButton,
valueAttribute: 'data-text',
}],
};
}
bindEvents() {
this.createMergeRequestButton
.addEventListener('click', this.onClickCreateMergeRequestButton.bind(this));
}
isBusy() {
return this.isCreatingMergeRequest ||
this.mergeRequestCreated ||
this.isCreatingBranch ||
this.branchCreated;
}
onClickCreateMergeRequestButton(e) {
let xhr = null;
e.preventDefault();
if (this.isBusy()) {
return;
}
if (e.target.dataset.action === CREATE_MERGE_REQUEST) {
xhr = this.createMergeRequest();
} else if (e.target.dataset.action === CREATE_BRANCH) {
xhr = this.createBranch();
}
xhr.fail(() => {
this.isCreatingMergeRequest = false;
this.isCreatingBranch = false;
});
xhr.always(() => this.enable());
this.disable();
}
createMergeRequest() {
return $.ajax({
method: 'POST',
dataType: 'json',
url: this.createMrPath,
beforeSend: () => (this.isCreatingMergeRequest = true),
})
.done((data) => {
this.mergeRequestCreated = true;
window.location.href = data.url;
})
.fail(() => new Flash('Failed to create Merge Request. Please try again.'));
}
createBranch() {
return $.ajax({
method: 'POST',
dataType: 'json',
url: this.createBranchPath,
beforeSend: () => (this.isCreatingBranch = true),
})
.done((data) => {
this.branchCreated = true;
window.location.href = data.url;
})
.fail(() => new Flash('Failed to create a branch for this issue. Please try again.'));
}
}
...@@ -44,6 +44,7 @@ import GroupsList from './groups_list'; ...@@ -44,6 +44,7 @@ import GroupsList from './groups_list';
import ProjectsList from './projects_list'; import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing';
import BlobForkSuggestion from './blob/blob_fork_suggestion'; import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout'; import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags'; import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
...@@ -148,8 +149,19 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -148,8 +149,19 @@ const ShortcutsBlob = require('./shortcuts_blob');
new ProjectsList(); new ProjectsList();
break; break;
case 'dashboard:groups:index': case 'dashboard:groups:index':
new GroupsList();
break;
case 'explore:groups:index': case 'explore:groups:index':
new GroupsList(); new GroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) break;
const exploreGroupsLanding = new Landing(
landingElement,
landingElement.querySelector('.dismiss-button'),
'explore_groups_landing_dismissed',
);
exploreGroupsLanding.toggle();
break; break;
case 'projects:milestones:new': case 'projects:milestones:new':
case 'projects:milestones:edit': case 'projects:milestones:edit':
...@@ -356,6 +368,10 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -356,6 +368,10 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'users:show': case 'users:show':
new UserCallout(); new UserCallout();
break; break;
case 'snippets:show':
new LineHighlighter();
new BlobViewer();
break;
} }
switch (path.first()) { switch (path.first()) {
case 'sessions': case 'sessions':
...@@ -434,6 +450,8 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -434,6 +450,8 @@ const ShortcutsBlob = require('./shortcuts_blob');
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
if (path[2] === 'show') { if (path[2] === 'show') {
new ZenMode(); new ZenMode();
new LineHighlighter();
new BlobViewer();
} }
break; break;
case 'labels': case 'labels':
......
...@@ -5,7 +5,7 @@ require('./preview_markdown'); ...@@ -5,7 +5,7 @@ require('./preview_markdown');
window.DropzoneInput = (function() { window.DropzoneInput = (function() {
function DropzoneInput(form) { function DropzoneInput(form) {
var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, project_uploads_path, showError, showSpinner, uploadFile, uploadProgress; var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, uploads_path, showError, showSpinner, uploadFile, uploadProgress;
Dropzone.autoDiscover = false; Dropzone.autoDiscover = false;
alertClass = "alert alert-danger alert-dismissable div-dropzone-alert"; alertClass = "alert alert-danger alert-dismissable div-dropzone-alert";
alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\""; alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"";
...@@ -16,7 +16,7 @@ window.DropzoneInput = (function() { ...@@ -16,7 +16,7 @@ window.DropzoneInput = (function() {
iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"; iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>";
uploadProgress = $("<div class=\"div-dropzone-progress\"></div>"); uploadProgress = $("<div class=\"div-dropzone-progress\"></div>");
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"; btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>";
project_uploads_path = window.project_uploads_path || null; uploads_path = window.uploads_path || null;
max_file_size = gon.max_file_size || 10; max_file_size = gon.max_file_size || 10;
form_textarea = $(form).find(".js-gfm-input"); form_textarea = $(form).find(".js-gfm-input");
form_textarea.wrap("<div class=\"div-dropzone\"></div>"); form_textarea.wrap("<div class=\"div-dropzone\"></div>");
...@@ -39,10 +39,10 @@ window.DropzoneInput = (function() { ...@@ -39,10 +39,10 @@ window.DropzoneInput = (function() {
"display": "none" "display": "none"
}); });
if (!project_uploads_path) return; if (!uploads_path) return;
dropzone = form_dropzone.dropzone({ dropzone = form_dropzone.dropzone({
url: project_uploads_path, url: uploads_path,
dictDefaultMessage: "", dictDefaultMessage: "",
clickable: true, clickable: true,
paramName: "file", paramName: "file",
...@@ -159,7 +159,7 @@ window.DropzoneInput = (function() { ...@@ -159,7 +159,7 @@ window.DropzoneInput = (function() {
formData = new FormData(); formData = new FormData();
formData.append("file", item, filename); formData.append("file", item, filename);
return $.ajax({ return $.ajax({
url: project_uploads_path, url: uploads_path,
type: "POST", type: "POST",
data: formData, data: formData,
dataType: "json", dataType: "json",
......
<script> <script>
/* eslint-disable no-new */
/* global Flash */ /* global Flash */
import EnvironmentsService from '../services/environments_service'; import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table.vue'; import EnvironmentTable from './environments_table.vue';
...@@ -71,11 +69,13 @@ export default { ...@@ -71,11 +69,13 @@ export default {
eventHub.$on('refreshEnvironments', this.fetchEnvironments); eventHub.$on('refreshEnvironments', this.fetchEnvironments);
eventHub.$on('toggleFolder', this.toggleFolder); eventHub.$on('toggleFolder', this.toggleFolder);
eventHub.$on('postAction', this.postAction);
}, },
beforeDestroyed() { beforeDestroyed() {
eventHub.$off('refreshEnvironments'); eventHub.$off('refreshEnvironments');
eventHub.$off('toggleFolder'); eventHub.$off('toggleFolder');
eventHub.$off('postAction');
}, },
methods: { methods: {
...@@ -122,6 +122,7 @@ export default { ...@@ -122,6 +122,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.'); new Flash('An error occurred while fetching the environments.');
}); });
}, },
...@@ -137,9 +138,16 @@ export default { ...@@ -137,9 +138,16 @@ export default {
}) })
.catch(() => { .catch(() => {
this.isLoadingFolderContent = false; this.isLoadingFolderContent = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.'); new Flash('An error occurred while fetching the environments.');
}); });
}, },
postAction(endpoint) {
this.service.postAction(endpoint)
.then(() => this.fetchEnvironments())
.catch(() => new Flash('An error occured while making the request.'));
},
}, },
}; };
</script> </script>
...@@ -217,7 +225,6 @@ export default { ...@@ -217,7 +225,6 @@ export default {
:environments="state.environments" :environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"
:service="service"
:is-loading-folder-content="isLoadingFolderContent" /> :is-loading-folder-content="isLoadingFolderContent" />
</div> </div>
......
<script> <script>
/* global Flash */
/* eslint-disable no-new */
import playIconSvg from 'icons/_icon_play.svg'; import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -12,11 +9,6 @@ export default { ...@@ -12,11 +9,6 @@ export default {
required: false, required: false,
default: () => [], default: () => [],
}, },
service: {
type: Object,
required: true,
},
}, },
data() { data() {
...@@ -38,15 +30,7 @@ export default { ...@@ -38,15 +30,7 @@ export default {
$(this.$refs.tooltip).tooltip('destroy'); $(this.$refs.tooltip).tooltip('destroy');
this.service.postAction(endpoint) eventHub.$emit('postAction', endpoint);
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.');
});
}, },
isActionDisabled(action) { isActionDisabled(action) {
......
...@@ -46,11 +46,6 @@ export default { ...@@ -46,11 +46,6 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
service: {
type: Object,
required: true,
},
}, },
computed: { computed: {
...@@ -543,31 +538,34 @@ export default { ...@@ -543,31 +538,34 @@ export default {
<actions-component <actions-component
v-if="hasManualActions && canCreateDeployment" v-if="hasManualActions && canCreateDeployment"
:service="service" :actions="manualActions"
:actions="manualActions"/> />
<external-url-component <external-url-component
v-if="externalURL && canReadEnvironment" v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/> :external-url="externalURL"
/>
<monitoring-button-component <monitoring-button-component
v-if="monitoringUrl && canReadEnvironment" v-if="monitoringUrl && canReadEnvironment"
:monitoring-url="monitoringUrl"/> :monitoring-url="monitoringUrl"
/>
<terminal-button-component <terminal-button-component
v-if="model && model.terminal_path" v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/> :terminal-path="model.terminal_path"
/>
<stop-component <stop-component
v-if="hasStopAction && canCreateDeployment" v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path" :stop-url="model.stop_path"
:service="service"/> />
<rollback-component <rollback-component
v-if="canRetry && canCreateDeployment" v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment" :is-last-deployment="isLastDeployment"
:retry-url="retryUrl" :retry-url="retryUrl"
:service="service"/> />
</div> </div>
</td> </td>
</tr> </tr>
......
<script> <script>
/* global Flash */
/* eslint-disable no-new */
/** /**
* Renders Rollback or Re deploy button in environments table depending * Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`. * of the provided property `isLastDeployment`.
...@@ -20,11 +18,6 @@ export default { ...@@ -20,11 +18,6 @@ export default {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
service: {
type: Object,
required: true,
},
}, },
data() { data() {
...@@ -37,17 +30,7 @@ export default { ...@@ -37,17 +30,7 @@ export default {
onClick() { onClick() {
this.isLoading = true; this.isLoading = true;
$(this.$el).tooltip('destroy'); eventHub.$emit('postAction', this.retryUrl);
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.');
});
}, },
}, },
}; };
......
<script> <script>
/* global Flash */
/* eslint-disable no-new, no-alert */
/** /**
* Renders the stop "button" that allows stop an environment. * Renders the stop "button" that allows stop an environment.
* Used in environments table. * Used in environments table.
...@@ -13,11 +11,6 @@ export default { ...@@ -13,11 +11,6 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
service: {
type: Object,
required: true,
},
}, },
data() { data() {
...@@ -34,20 +27,13 @@ export default { ...@@ -34,20 +27,13 @@ export default {
methods: { methods: {
onClick() { onClick() {
// eslint-disable-next-line no-alert
if (confirm('Are you sure you want to stop this environment?')) { if (confirm('Are you sure you want to stop this environment?')) {
this.isLoading = true; this.isLoading = true;
$(this.$el).tooltip('destroy'); $(this.$el).tooltip('destroy');
this.service.postAction(this.retryUrl) eventHub.$emit('postAction', this.stopUrl);
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.', 'alert');
});
} }
}, },
}, },
......
...@@ -28,11 +28,6 @@ export default { ...@@ -28,11 +28,6 @@ export default {
default: false, default: false,
}, },
service: {
type: Object,
required: true,
},
isLoadingFolderContent: { isLoadingFolderContent: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -78,7 +73,7 @@ export default { ...@@ -78,7 +73,7 @@ export default {
:model="model" :model="model"
:can-create-deployment="canCreateDeployment" :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment" :can-read-environment="canReadEnvironment"
:service="service" /> />
<template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0"> <template v-if="model.isFolder && model.isOpen && model.children && model.children.length > 0">
<tr v-if="isLoadingFolderContent"> <tr v-if="isLoadingFolderContent">
...@@ -96,7 +91,7 @@ export default { ...@@ -96,7 +91,7 @@ export default {
:model="children" :model="children"
:can-create-deployment="canCreateDeployment" :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment" :can-read-environment="canReadEnvironment"
:service="service" /> />
<tr> <tr>
<td <td
......
<script> <script>
/* eslint-disable no-new */
/* global Flash */ /* global Flash */
import EnvironmentsService from '../services/environments_service'; import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table.vue'; import EnvironmentTable from '../components/environments_table.vue';
...@@ -99,6 +98,7 @@ export default { ...@@ -99,6 +98,7 @@ export default {
}) })
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
// eslint-disable-next-line no-new
new Flash('An error occurred while fetching the environments.', 'alert'); new Flash('An error occurred while fetching the environments.', 'alert');
}); });
}, },
...@@ -169,7 +169,7 @@ export default { ...@@ -169,7 +169,7 @@ export default {
:environments="state.environments" :environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"
:service="service"/> />
<table-pagination <table-pagination
v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
......
...@@ -34,9 +34,9 @@ GLForm.prototype.setupForm = function() { ...@@ -34,9 +34,9 @@ GLForm.prototype.setupForm = function() {
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form); new DropzoneInput(this.form);
autosize(this.textarea); autosize(this.textarea);
// form and textarea event listeners
this.addEventListeners();
} }
// form and textarea event listeners
this.addEventListeners();
gl.text.init(this.form); gl.text.init(this.form);
// hide discard button // hide discard button
this.form.find('.js-note-discard').hide(); this.form.find('.js-note-discard').hide();
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
/* global Flash */ /* global Flash */
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
require('./flash'); require('./flash');
require('~/lib/utils/text_utility'); require('~/lib/utils/text_utility');
...@@ -18,48 +19,49 @@ class Issue { ...@@ -18,48 +19,49 @@ class Issue {
document.querySelector('#task_status_short').innerText = result.task_status_short; document.querySelector('#task_status_short').innerText = result.task_status_short;
} }
}); });
Issue.initIssueBtnEventListeners(); this.initIssueBtnEventListeners();
} }
Issue.$btnNewBranch = $('#new-branch'); Issue.$btnNewBranch = $('#new-branch');
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
Issue.initMergeRequests(); Issue.initMergeRequests();
Issue.initRelatedBranches(); Issue.initRelatedBranches();
Issue.initCanCreateBranch();
if (Issue.createMrDropdownWrap) {
this.createMergeRequestDropdown = new CreateMergeRequestDropdown(Issue.createMrDropdownWrap);
}
} }
static initIssueBtnEventListeners() { initIssueBtnEventListeners() {
const issueFailMessage = 'Unable to update this issue at this time.'; const issueFailMessage = 'Unable to update this issue at this time.';
const closeButtons = $('a.btn-close'); const closeButtons = $('a.btn-close');
const isClosedBadge = $('div.status-box-closed'); const isClosedBadge = $('div.status-box-closed');
const isOpenBadge = $('div.status-box-open'); const isOpenBadge = $('div.status-box-open');
const projectIssuesCounter = $('.issue_counter'); const projectIssuesCounter = $('.issue_counter');
const reopenButtons = $('a.btn-reopen'); const reopenButtons = $('a.btn-reopen');
return closeButtons.add(reopenButtons).on('click', function(e) { return closeButtons.add(reopenButtons).on('click', (e) => {
var $this, shouldSubmit, url; var $button, shouldSubmit, url;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
$this = $(this); $button = $(e.currentTarget);
shouldSubmit = $this.hasClass('btn-comment'); shouldSubmit = $button.hasClass('btn-comment');
if (shouldSubmit) { if (shouldSubmit) {
Issue.submitNoteForm($this.closest('form')); Issue.submitNoteForm($button.closest('form'));
} }
$this.prop('disabled', true); $button.prop('disabled', true);
Issue.setNewBranchButtonState(true, null); url = $button.attr('href');
url = $this.attr('href');
return $.ajax({ return $.ajax({
type: 'PUT', type: 'PUT',
url: url url: url
}).fail(function(jqXHR, textStatus, errorThrown) { })
new Flash(issueFailMessage); .fail(() => new Flash(issueFailMessage))
Issue.initCanCreateBranch(); .done((data) => {
}).done(function(data, textStatus, jqXHR) {
if ('id' in data) { if ('id' in data) {
$(document).trigger('issuable:change'); $(document).trigger('issuable:change');
const isClosed = $this.hasClass('btn-close'); const isClosed = $button.hasClass('btn-close');
closeButtons.toggleClass('hidden', isClosed); closeButtons.toggleClass('hidden', isClosed);
reopenButtons.toggleClass('hidden', !isClosed); reopenButtons.toggleClass('hidden', !isClosed);
isClosedBadge.toggleClass('hidden', !isClosed); isClosedBadge.toggleClass('hidden', !isClosed);
...@@ -68,12 +70,21 @@ class Issue { ...@@ -68,12 +70,21 @@ class Issue {
let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, '')); let numProjectIssues = Number(projectIssuesCounter.text().replace(/[^\d]/, ''));
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1; numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues)); projectIssuesCounter.text(gl.text.addDelimiter(numProjectIssues));
if (this.createMergeRequestDropdown) {
if (isClosed) {
this.createMergeRequestDropdown.unavailable();
this.createMergeRequestDropdown.disable();
} else {
// We should check in case a branch was created in another tab
this.createMergeRequestDropdown.checkAbilityToCreateBranch();
}
}
} else { } else {
new Flash(issueFailMessage); new Flash(issueFailMessage);
} }
$this.prop('disabled', false); $button.prop('disabled', false);
Issue.initCanCreateBranch();
}); });
}); });
} }
...@@ -109,29 +120,6 @@ class Issue { ...@@ -109,29 +120,6 @@ class Issue {
} }
}); });
} }
static initCanCreateBranch() {
// If the user doesn't have the required permissions the container isn't
// rendered at all.
if (Issue.$btnNewBranch.length === 0) {
return;
}
return $.getJSON(Issue.$btnNewBranch.data('path')).fail(function() {
Issue.setNewBranchButtonState(false, false);
new Flash('Failed to check if a new branch can be created.');
}).done(function(data) {
Issue.setNewBranchButtonState(false, data.can_create_branch);
});
}
static setNewBranchButtonState(isPending, canCreate) {
if (Issue.$btnNewBranch.length === 0) {
return;
}
Issue.$btnNewBranch.find('.available').toggle(!isPending && canCreate);
Issue.$btnNewBranch.find('.unavailable').toggle(!isPending && !canCreate);
}
} }
export default Issue; export default Issue;
import Cookies from 'js-cookie';
class Landing {
constructor(landingElement, dismissButton, cookieName) {
this.landingElement = landingElement;
this.cookieName = cookieName;
this.dismissButton = dismissButton;
this.eventWrapper = {};
}
toggle() {
const isDismissed = this.isDismissed();
this.landingElement.classList.toggle('hidden', isDismissed);
if (!isDismissed) this.addEvents();
}
addEvents() {
this.eventWrapper.dismissLanding = this.dismissLanding.bind(this);
this.dismissButton.addEventListener('click', this.eventWrapper.dismissLanding);
}
removeEvents() {
this.dismissButton.removeEventListener('click', this.eventWrapper.dismissLanding);
}
dismissLanding() {
this.landingElement.classList.add('hidden');
Cookies.set(this.cookieName, 'true', { expires: 365 });
}
isDismissed() {
return Cookies.get(this.cookieName) === 'true';
}
}
export default Landing;
...@@ -57,9 +57,9 @@ require('vendor/jquery.scrollTo'); ...@@ -57,9 +57,9 @@ require('vendor/jquery.scrollTo');
} }
LineHighlighter.prototype.bindEvents = function() { LineHighlighter.prototype.bindEvents = function() {
const $blobContentHolder = $('#blob-content-holder'); const $fileHolder = $('.file-holder');
$blobContentHolder.on('click', 'a[data-line-number]', this.clickHandler); $fileHolder.on('click', 'a[data-line-number]', this.clickHandler);
$blobContentHolder.on('highlight:line', this.highlightHash); $fileHolder.on('highlight:line', this.highlightHash);
}; };
LineHighlighter.prototype.highlightHash = function() { LineHighlighter.prototype.highlightHash = function() {
......
...@@ -19,12 +19,10 @@ ...@@ -19,12 +19,10 @@
}); });
}; };
Milestone.sortIssues = function(data) { Milestone.sortIssues = function(url, data) {
var sort_issues_url;
sort_issues_url = location.href + "/sort_issues";
return $.ajax({ return $.ajax({
type: "PUT", type: "PUT",
url: sort_issues_url, url,
data: data, data: data,
success: function(_data) { success: function(_data) {
return Milestone.successCallback(_data); return Milestone.successCallback(_data);
...@@ -36,12 +34,10 @@ ...@@ -36,12 +34,10 @@
}); });
}; };
Milestone.sortMergeRequests = function(data) { Milestone.sortMergeRequests = function(url, data) {
var sort_mr_url;
sort_mr_url = location.href + "/sort_merge_requests";
return $.ajax({ return $.ajax({
type: "PUT", type: "PUT",
url: sort_mr_url, url,
data: data, data: data,
success: function(_data) { success: function(_data) {
return Milestone.successCallback(_data); return Milestone.successCallback(_data);
...@@ -81,42 +77,55 @@ ...@@ -81,42 +77,55 @@
}; };
function Milestone() { function Milestone() {
var oldMouseStart; this.issuesSortEndpoint = $('#tab-issues').data('sort-endpoint');
this.mergeRequestsSortEndpoint = $('#tab-merge-requests').data('sort-endpoint');
this.bindIssuesSorting(); this.bindIssuesSorting();
this.bindMergeRequestSorting();
this.bindTabsSwitching(); this.bindTabsSwitching();
// Load merge request tab if it is active
// merge request tab is active based on different conditions in the backend
this.loadTab($('.js-milestone-tabs .active a'));
this.loadInitialTab();
} }
Milestone.prototype.bindIssuesSorting = function() { Milestone.prototype.bindIssuesSorting = function() {
if (!this.issuesSortEndpoint) return;
$('#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed').each(function (i, el) { $('#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed').each(function (i, el) {
this.createSortable(el, { this.createSortable(el, {
group: 'issue-list', group: 'issue-list',
listEls: $('.issues-sortable-list'), listEls: $('.issues-sortable-list'),
fieldName: 'issue', fieldName: 'issue',
sortCallback: Milestone.sortIssues, sortCallback: (data) => {
Milestone.sortIssues(this.issuesSortEndpoint, data);
},
updateCallback: Milestone.updateIssue, updateCallback: Milestone.updateIssue,
}); });
}.bind(this)); }.bind(this));
}; };
Milestone.prototype.bindTabsSwitching = function() { Milestone.prototype.bindTabsSwitching = function() {
return $('a[data-toggle="tab"]').on('show.bs.tab', function(e) { return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
var currentTabClass, previousTabClass; const $target = $(e.target);
currentTabClass = $(e.target).data('show');
previousTabClass = $(e.relatedTarget).data('show'); location.hash = $target.attr('href');
$(previousTabClass).hide(); this.loadTab($target);
$(currentTabClass).removeClass('hidden');
return $(currentTabClass).show();
}); });
}; };
Milestone.prototype.bindMergeRequestSorting = function() { Milestone.prototype.bindMergeRequestSorting = function() {
if (!this.mergeRequestsSortEndpoint) return;
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").each(function (i, el) { $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").each(function (i, el) {
this.createSortable(el, { this.createSortable(el, {
group: 'merge-request-list', group: 'merge-request-list',
listEls: $(".merge_requests-sortable-list:not(#merge_requests-list-merged)"), listEls: $(".merge_requests-sortable-list:not(#merge_requests-list-merged)"),
fieldName: 'merge_request', fieldName: 'merge_request',
sortCallback: Milestone.sortMergeRequests, sortCallback: (data) => {
Milestone.sortMergeRequests(this.mergeRequestsSortEndpoint, data);
},
updateCallback: Milestone.updateMergeRequest, updateCallback: Milestone.updateMergeRequest,
}); });
}.bind(this)); }.bind(this));
...@@ -169,6 +178,35 @@ ...@@ -169,6 +178,35 @@
}); });
}; };
Milestone.prototype.loadInitialTab = function() {
const $target = $(`.js-milestone-tabs a[href="${location.hash}"]`);
if ($target.length) {
$target.tab('show');
}
};
Milestone.prototype.loadTab = function($target) {
const endpoint = $target.data('endpoint');
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
$.ajax({
url: endpoint,
dataType: 'JSON',
})
.fail(() => new Flash('Error loading milestone tab'))
.done((data) => {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
if (tabElId === '#tab-merge-requests') {
this.bindMergeRequestSorting();
}
});
}
};
return Milestone; return Milestone;
})(); })();
}).call(window); }).call(window);
import d3 from 'd3';
export const dateFormat = d3.time.format('%b %d, %Y');
export const timeFormat = d3.time.format('%H:%M%p');
/* global Flash */
import d3 from 'd3';
import {
dateFormat,
timeFormat,
} from './constants';
export default class Deployments {
constructor(width, height) {
this.width = width;
this.height = height;
this.endpoint = document.getElementById('js-metrics').dataset.deploymentEndpoint;
this.createGradientDef();
}
init(chartData) {
this.chartData = chartData;
this.x = d3.time.scale().range([0, this.width]);
this.x.domain(d3.extent(this.chartData, d => d.time));
this.charts = d3.selectAll('.prometheus-graph');
this.getData();
}
getData() {
$.ajax({
url: this.endpoint,
dataType: 'JSON',
})
.fail(() => new Flash('Error getting deployment information.'))
.done((data) => {
this.data = data.deployments.reduce((deploymentDataArray, deployment) => {
const time = new Date(deployment.created_at);
const xPos = Math.floor(this.x(time));
time.setSeconds(this.chartData[0].time.getSeconds());
if (xPos >= 0) {
deploymentDataArray.push({
id: deployment.id,
time,
sha: deployment.sha,
tag: deployment.tag,
ref: deployment.ref.name,
xPos,
});
}
return deploymentDataArray;
}, []);
this.plotData();
});
}
plotData() {
this.charts.each((d, i) => {
const svg = d3.select(this.charts[0][i]);
const chart = svg.select('.graph-container');
const key = svg.node().getAttribute('graph-type');
this.createLine(chart, key);
this.createDeployInfoBox(chart, key);
});
}
createGradientDef() {
const defs = d3.select('body')
.append('svg')
.attr({
height: 0,
width: 0,
})
.append('defs');
defs.append('linearGradient')
.attr({
id: 'shadow-gradient',
})
.append('stop')
.attr({
offset: '0%',
'stop-color': '#000',
'stop-opacity': 0.4,
})
.select(this.selectParentNode)
.append('stop')
.attr({
offset: '100%',
'stop-color': '#000',
'stop-opacity': 0,
});
}
createLine(chart, key) {
chart.append('g')
.attr({
class: 'deploy-info',
})
.selectAll('.deploy-info')
.data(this.data)
.enter()
.append('g')
.attr({
class: d => `deploy-info-${d.id}-${key}`,
transform: d => `translate(${Math.floor(d.xPos) + 1}, 0)`,
})
.append('rect')
.attr({
x: 1,
y: 0,
height: this.height + 1,
width: 3,
fill: 'url(#shadow-gradient)',
})
.select(this.selectParentNode)
.append('line')
.attr({
class: 'deployment-line',
x1: 0,
x2: 0,
y1: 0,
y2: this.height + 1,
});
}
createDeployInfoBox(chart, key) {
chart.selectAll('.deploy-info')
.selectAll('.js-deploy-info-box')
.data(this.data)
.enter()
.select(d => document.querySelector(`.deploy-info-${d.id}-${key}`))
.append('svg')
.attr({
class: 'js-deploy-info-box hidden',
x: 3,
y: 0,
width: 92,
height: 60,
})
.append('rect')
.attr({
class: 'rect-text-metric deploy-info-rect rect-metric',
x: 1,
y: 1,
rx: 2,
width: 90,
height: 58,
})
.select(this.selectParentNode)
.append('g')
.attr({
transform: 'translate(5, 2)',
})
.append('text')
.attr({
class: 'deploy-info-text text-metric-bold',
})
.text(Deployments.refText)
.select(this.selectParentNode)
.append('text')
.attr({
class: 'deploy-info-text',
y: 18,
})
.text(d => dateFormat(d.time))
.select(this.selectParentNode)
.append('text')
.attr({
class: 'deploy-info-text text-metric-bold',
y: 38,
})
.text(d => timeFormat(d.time));
}
static toggleDeployTextbox(deploy, key, showInfoBox) {
d3.selectAll(`.deploy-info-${deploy.id}-${key} .js-deploy-info-box`)
.classed('hidden', !showInfoBox);
}
mouseOverDeployInfo(mouseXPos, key) {
if (!this.data) return false;
let dataFound = false;
this.data.forEach((d) => {
if (d.xPos >= mouseXPos - 10 && d.xPos <= mouseXPos + 10 && !dataFound) {
dataFound = d.xPos + 1;
Deployments.toggleDeployTextbox(d, key, true);
} else {
Deployments.toggleDeployTextbox(d, key, false);
}
});
return dataFound;
}
/* `this` is bound to the D3 node */
selectParentNode() {
return this.parentNode;
}
static refText(d) {
return d.tag ? d.ref : d.sha.slice(0, 6);
}
}
...@@ -3,16 +3,20 @@ ...@@ -3,16 +3,20 @@
import d3 from 'd3'; import d3 from 'd3';
import statusCodes from '~/lib/utils/http_status'; import statusCodes from '~/lib/utils/http_status';
import { formatRelevantDigits } from '~/lib/utils/number_utils'; import Deployments from './deployments';
import '../lib/utils/common_utils';
import { formatRelevantDigits } from '../lib/utils/number_utils';
import '../flash'; import '../flash';
import {
dateFormat,
timeFormat,
} from './constants';
const prometheusContainer = '.prometheus-container'; const prometheusContainer = '.prometheus-container';
const prometheusParentGraphContainer = '.prometheus-graphs'; const prometheusParentGraphContainer = '.prometheus-graphs';
const prometheusGraphsContainer = '.prometheus-graph'; const prometheusGraphsContainer = '.prometheus-graph';
const prometheusStatesContainer = '.prometheus-state'; const prometheusStatesContainer = '.prometheus-state';
const metricsEndpoint = 'metrics.json'; const metricsEndpoint = 'metrics.json';
const timeFormat = d3.time.format('%H:%M');
const dayFormat = d3.time.format('%b %e, %a');
const bisectDate = d3.bisector(d => d.time).left; const bisectDate = d3.bisector(d => d.time).left;
const extraAddedWidthParent = 100; const extraAddedWidthParent = 100;
...@@ -36,6 +40,7 @@ class PrometheusGraph { ...@@ -36,6 +40,7 @@ class PrometheusGraph {
this.width = parentContainerWidth - this.margin.left - this.margin.right; this.width = parentContainerWidth - this.margin.left - this.margin.right;
this.height = this.originalHeight - this.margin.top - this.margin.bottom; this.height = this.originalHeight - this.margin.top - this.margin.bottom;
this.backOffRequestCounter = 0; this.backOffRequestCounter = 0;
this.deployments = new Deployments(this.width, this.height);
this.configureGraph(); this.configureGraph();
this.init(); this.init();
} else { } else {
...@@ -74,6 +79,12 @@ class PrometheusGraph { ...@@ -74,6 +79,12 @@ class PrometheusGraph {
$(prometheusParentGraphContainer).show(); $(prometheusParentGraphContainer).show();
this.transformData(metricsResponse); this.transformData(metricsResponse);
this.createGraph(); this.createGraph();
const firstMetricData = this.graphSpecificProperties[
Object.keys(this.graphSpecificProperties)[0]
].data;
this.deployments.init(firstMetricData);
} }
}); });
} }
...@@ -96,6 +107,7 @@ class PrometheusGraph { ...@@ -96,6 +107,7 @@ class PrometheusGraph {
.attr('width', this.width + this.margin.left + this.margin.right) .attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.bottom + this.margin.top) .attr('height', this.height + this.margin.bottom + this.margin.top)
.append('g') .append('g')
.attr('class', 'graph-container')
.attr('transform', `translate(${this.margin.left},${this.margin.top})`); .attr('transform', `translate(${this.margin.left},${this.margin.top})`);
const axisLabelContainer = d3.select(prometheusGraphContainer) const axisLabelContainer = d3.select(prometheusGraphContainer)
...@@ -116,6 +128,7 @@ class PrometheusGraph { ...@@ -116,6 +128,7 @@ class PrometheusGraph {
.scale(y) .scale(y)
.ticks(this.commonGraphProperties.axis_no_ticks) .ticks(this.commonGraphProperties.axis_no_ticks)
.tickSize(-this.width) .tickSize(-this.width)
.outerTickSize(0)
.orient('left'); .orient('left');
this.createAxisLabelContainers(axisLabelContainer, key); this.createAxisLabelContainers(axisLabelContainer, key);
...@@ -248,7 +261,8 @@ class PrometheusGraph { ...@@ -248,7 +261,8 @@ class PrometheusGraph {
const d1 = currentGraphProps.data[overlayIndex]; const d1 = currentGraphProps.data[overlayIndex];
const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay; const evalTime = timeValueOverlay - d0.time > d1.time - timeValueOverlay;
const currentData = evalTime ? d1 : d0; const currentData = evalTime ? d1 : d0;
const currentTimeCoordinate = currentGraphProps.xScale(currentData.time); const currentTimeCoordinate = Math.floor(currentGraphProps.xScale(currentData.time));
const currentDeployXPos = this.deployments.mouseOverDeployInfo(currentXCoordinate, key);
const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`; const currentPrometheusGraphContainer = `${prometheusGraphsContainer}[graph-type=${key}]`;
const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value)); const maxValueFromData = d3.max(currentGraphProps.data.map(metricValue => metricValue.value));
const maxMetricValue = currentGraphProps.yScale(maxValueFromData); const maxMetricValue = currentGraphProps.yScale(maxValueFromData);
...@@ -256,13 +270,12 @@ class PrometheusGraph { ...@@ -256,13 +270,12 @@ class PrometheusGraph {
// Clear up all the pieces of the flag // Clear up all the pieces of the flag
d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove(); d3.selectAll(`${currentPrometheusGraphContainer} .selected-metric-line`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove(); d3.selectAll(`${currentPrometheusGraphContainer} .circle-metric`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric`).remove(); d3.selectAll(`${currentPrometheusGraphContainer} .rect-text-metric:not(.deploy-info-rect)`).remove();
d3.selectAll(`${currentPrometheusGraphContainer} .text-metric`).remove();
const currentChart = d3.select(currentPrometheusGraphContainer).select('g'); const currentChart = d3.select(currentPrometheusGraphContainer).select('g');
currentChart.append('line') currentChart.append('line')
.attr('class', 'selected-metric-line')
.attr({ .attr({
class: `${currentDeployXPos ? 'hidden' : ''} selected-metric-line`,
x1: currentTimeCoordinate, x1: currentTimeCoordinate,
y1: currentGraphProps.yScale(0), y1: currentGraphProps.yScale(0),
x2: currentTimeCoordinate, x2: currentTimeCoordinate,
...@@ -272,33 +285,45 @@ class PrometheusGraph { ...@@ -272,33 +285,45 @@ class PrometheusGraph {
currentChart.append('circle') currentChart.append('circle')
.attr('class', 'circle-metric') .attr('class', 'circle-metric')
.attr('fill', currentGraphProps.line_color) .attr('fill', currentGraphProps.line_color)
.attr('cx', currentTimeCoordinate) .attr('cx', currentDeployXPos || currentTimeCoordinate)
.attr('cy', currentGraphProps.yScale(currentData.value)) .attr('cy', currentGraphProps.yScale(currentData.value))
.attr('r', this.commonGraphProperties.circle_radius_metric); .attr('r', this.commonGraphProperties.circle_radius_metric);
if (currentDeployXPos) return;
// The little box with text // The little box with text
const rectTextMetric = currentChart.append('g') const rectTextMetric = currentChart.append('svg')
.attr('class', 'rect-text-metric') .attr({
.attr('translate', `(${currentTimeCoordinate}, ${currentGraphProps.yScale(currentData.value)})`); class: 'rect-text-metric',
x: currentTimeCoordinate,
y: 0,
});
rectTextMetric.append('rect') rectTextMetric.append('rect')
.attr('class', 'rect-metric') .attr({
.attr('x', currentTimeCoordinate + 10) class: 'rect-metric',
.attr('y', maxMetricValue) x: 4,
.attr('width', this.commonGraphProperties.rect_text_width) y: 1,
.attr('height', this.commonGraphProperties.rect_text_height); rx: 2,
width: this.commonGraphProperties.rect_text_width,
height: this.commonGraphProperties.rect_text_height,
});
rectTextMetric.append('text') rectTextMetric.append('text')
.attr('class', 'text-metric') .attr({
.attr('x', currentTimeCoordinate + 35) class: 'text-metric text-metric-bold',
.attr('y', maxMetricValue + 35) x: 8,
y: 35,
})
.text(timeFormat(currentData.time)); .text(timeFormat(currentData.time));
rectTextMetric.append('text') rectTextMetric.append('text')
.attr('class', 'text-metric-date') .attr({
.attr('x', currentTimeCoordinate + 15) class: 'text-metric-date',
.attr('y', maxMetricValue + 15) x: 8,
.text(dayFormat(currentData.time)); y: 15,
})
.text(dateFormat(currentData.time));
let currentMetricValue = formatRelevantDigits(currentData.value); let currentMetricValue = formatRelevantDigits(currentData.value);
if (key === 'cpu_values') { if (key === 'cpu_values') {
......
This diff is collapsed.
<template>
<div class="pdf-viewer" v-if="hasPDF">
<page v-for="(page, index) in pages"
:key="index"
:v-if="!loading"
:page="page"
:number="index + 1" />
</div>
</template>
<script>
import pdfjsLib from 'pdfjs-dist';
import workerSrc from 'vendor/pdf.worker';
import page from './page/index.vue';
export default {
props: {
pdf: {
type: [String, Uint8Array],
required: true,
},
},
data() {
return {
loading: false,
pages: [],
};
},
components: { page },
watch: { pdf: 'load' },
computed: {
document() {
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
},
hasPDF() {
return this.pdf && this.pdf.length > 0;
},
},
methods: {
load() {
this.pages = [];
return pdfjsLib.getDocument(this.document)
.then(this.renderPages)
.then(() => this.$emit('pdflabload'))
.catch(error => this.$emit('pdflaberror', error))
.then(() => { this.loading = false; });
},
renderPages(pdf) {
const pagePromises = [];
this.loading = true;
for (let num = 1; num <= pdf.numPages; num += 1) {
pagePromises.push(
pdf.getPage(num).then(p => this.pages.push(p)),
);
}
return Promise.all(pagePromises);
},
},
mounted() {
pdfjsLib.PDFJS.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
};
</script>
<style>
.pdf-viewer {
background: url('./assets/img/bg.gif');
display: flex;
flex-flow: column nowrap;
}
</style>
<template>
<canvas
class="pdf-page"
ref="canvas"
:data-page="number" />
</template>
<script>
export default {
props: {
page: {
type: Object,
required: true,
},
number: {
type: Number,
required: true,
},
},
data() {
return {
scale: 4,
rendering: false,
};
},
computed: {
viewport() {
return this.page.getViewport(this.scale);
},
context() {
return this.$refs.canvas.getContext('2d');
},
renderContext() {
return {
canvasContext: this.context,
viewport: this.viewport,
};
},
},
mounted() {
this.$refs.canvas.height = this.viewport.height;
this.$refs.canvas.width = this.viewport.width;
this.rendering = true;
this.page.render(this.renderContext)
.then(() => { this.rendering = false; })
.catch(error => this.$emit('pdflaberror', error));
},
};
</script>
<style>
.pdf-page {
margin: 8px auto 0 auto;
border-top: 1px #ddd solid;
border-bottom: 1px #ddd solid;
width: 100%;
}
.pdf-page:first-child {
margin-top: 0px;
border-top: 0px;
}
.pdf-page:last-child {
margin-bottom: 0px;
border-bottom: 0px;
}
</style>
<script>
/**
* Renders each stage of the pipeline mini graph.
*
* Given the provided endpoint will make a request to
* fetch the dropdown data when the stage is clicked.
*
* Request is made inside this component to make it reusable between:
* 1. Pipelines main table
* 2. Pipelines table in commit and Merge request views
* 3. Merge request widget
* 4. Commit widget
*/
/* global Flash */ /* global Flash */
import StatusIconEntityMap from '../../ci_status_icons'; import StatusIconEntityMap from '../../ci_status_icons';
...@@ -7,36 +22,55 @@ export default { ...@@ -7,36 +22,55 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
updateDropdown: {
type: Boolean,
required: false,
default: false,
},
}, },
data() { data() {
return { return {
builds: '', isLoading: false,
spinner: '<span class="fa fa-spinner fa-spin"></span>', dropdownContent: '',
endpoint: this.stage.dropdown_path,
}; };
}, },
updated() { updated() {
if (this.builds) { if (this.dropdownContent.length > 0) {
this.stopDropdownClickPropagation(); this.stopDropdownClickPropagation();
} }
}, },
methods: { watch: {
fetchBuilds(e) { updateDropdown() {
const ariaExpanded = e.currentTarget.attributes['aria-expanded']; if (this.updateDropdown &&
this.isDropdownOpen() &&
!this.isLoading) {
this.fetchJobs();
}
},
},
if (ariaExpanded && (ariaExpanded.textContent === 'true')) return null; methods: {
onClickStage() {
if (!this.isDropdownOpen()) {
this.isLoading = true;
this.fetchJobs();
}
},
return this.$http.get(this.stage.dropdown_path) fetchJobs() {
this.$http.get(this.endpoint)
.then((response) => { .then((response) => {
this.builds = JSON.parse(response.body).html; this.dropdownContent = response.json().html;
this.isLoading = false;
}) })
.catch(() => { .catch(() => {
// If dropdown is opened we'll close it. this.closeDropdown();
if (this.$el.classList.contains('open')) { this.isLoading = false;
$(this.$refs.dropdown).dropdown('toggle');
}
const flash = new Flash('Something went wrong on our end.'); const flash = new Flash('Something went wrong on our end.');
return flash; return flash;
...@@ -57,59 +91,83 @@ export default { ...@@ -57,59 +91,83 @@ export default {
e.stopPropagation(); e.stopPropagation();
}); });
}, },
closeDropdown() {
if (this.isDropdownOpen()) {
$(this.$refs.dropdown).dropdown('toggle');
}
},
isDropdownOpen() {
return this.$el.classList.contains('open');
},
}, },
computed: { computed: {
buildsOrSpinner() {
return this.builds ? this.builds : this.spinner;
},
dropdownClass() { dropdownClass() {
if (this.builds) return 'js-builds-dropdown-container'; return this.dropdownContent.length > 0 ? 'js-builds-dropdown-container' : 'js-builds-dropdown-loading';
return 'js-builds-dropdown-loading builds-dropdown-loading';
},
buildStatus() {
return `Build: ${this.stage.status.label}`;
},
tooltip() {
return `has-tooltip ci-status-icon ci-status-icon-${this.stage.status.group}`;
}, },
triggerButtonClass() { triggerButtonClass() {
return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; return `ci-status-icon-${this.stage.status.group}`;
}, },
svgHTML() {
svgIcon() {
return StatusIconEntityMap[this.stage.status.icon]; return StatusIconEntityMap[this.stage.status.icon];
}, },
}, },
template: ` };
<div> </script>
<button
@click="fetchBuilds($event)" <template>
:class="triggerButtonClass" <div class="dropdown">
:title="stage.title" <button
data-placement="top" :class="triggerButtonClass"
data-toggle="dropdown" @click="onClickStage"
type="button" class="mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button"
:aria-label="stage.title" :title="stage.title"
ref="dropdown"> data-placement="top"
<span data-toggle="dropdown"
v-html="svgHTML" type="button"
aria-hidden="true"> id="stageDropdown"
</span> aria-haspopup="true"
<i aria-expanded="false">
class="fa fa-caret-down"
aria-hidden="true" /> <span
</button> v-html="svgIcon"
<ul aria-hidden="true"
ref="dropdown-content" :aria-label="stage.title">
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> </span>
<div
class="arrow-up" <i
aria-hidden="true"></div> class="fa fa-caret-down"
aria-hidden="true">
</i>
</button>
<ul
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"
aria-labelledby="stageDropdown">
<li
:class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu">
<div <div
:class="dropdownClass" class="text-center"
class="js-builds-dropdown-list scrollable-menu" v-if="isLoading">
v-html="buildsOrSpinner"> <i
class="fa fa-spin fa-spinner"
aria-hidden="true"
aria-label="Loading">
</i>
</div> </div>
</ul>
</div> <ul
`, v-else
}; v-html="dropdownContent">
</ul>
</li>
</ul>
</div>
</script>
...@@ -49,6 +49,7 @@ export default { ...@@ -49,6 +49,7 @@ export default {
isLoading: false, isLoading: false,
hasError: false, hasError: false,
isMakingRequest: false, isMakingRequest: false,
updateGraphDropdown: false,
}; };
}, },
...@@ -198,15 +199,21 @@ export default { ...@@ -198,15 +199,21 @@ export default {
this.store.storePagination(response.headers); this.store.storePagination(response.headers);
this.isLoading = false; this.isLoading = false;
this.updateGraphDropdown = true;
}, },
errorCallback() { errorCallback() {
this.hasError = true; this.hasError = true;
this.isLoading = false; this.isLoading = false;
this.updateGraphDropdown = false;
}, },
setIsMakingRequest(isMakingRequest) { setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest; this.isMakingRequest = isMakingRequest;
if (isMakingRequest) {
this.updateGraphDropdown = false;
}
}, },
}, },
...@@ -263,7 +270,9 @@ export default { ...@@ -263,7 +270,9 @@ export default {
<pipelines-table-component <pipelines-table-component
:pipelines="state.pipelines" :pipelines="state.pipelines"
:service="service"/> :service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</div> </div>
<gl-pagination <gl-pagination
......
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
$els.each((function(_this) { $els.each((function(_this) {
return function(i, dropdown) { return function(i, dropdown) {
var options = {}; var options = {};
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, firstUser, issueURL, selectedId, showAnyUser, showNullUser, showMenuAbove; var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, defaultNullUser, firstUser, issueURL, selectedId, selectedIdDefault, showAnyUser, showNullUser, showMenuAbove;
$dropdown = $(dropdown); $dropdown = $(dropdown);
options.projectId = $dropdown.data('project-id'); options.projectId = $dropdown.data('project-id');
options.groupId = $dropdown.data('group-id'); options.groupId = $dropdown.data('group-id');
...@@ -38,11 +38,11 @@ ...@@ -38,11 +38,11 @@
options.todoFilter = $dropdown.data('todo-filter'); options.todoFilter = $dropdown.data('todo-filter');
options.todoStateFilter = $dropdown.data('todo-state-filter'); options.todoStateFilter = $dropdown.data('todo-state-filter');
showNullUser = $dropdown.data('null-user'); showNullUser = $dropdown.data('null-user');
defaultNullUser = $dropdown.data('null-user-default');
showMenuAbove = $dropdown.data('showMenuAbove'); showMenuAbove = $dropdown.data('showMenuAbove');
showAnyUser = $dropdown.data('any-user'); showAnyUser = $dropdown.data('any-user');
firstUser = $dropdown.data('first-user'); firstUser = $dropdown.data('first-user');
options.authorId = $dropdown.data('author-id'); options.authorId = $dropdown.data('author-id');
selectedId = $dropdown.data('selected');
defaultLabel = $dropdown.data('default-label'); defaultLabel = $dropdown.data('default-label');
issueURL = $dropdown.data('issueUpdate'); issueURL = $dropdown.data('issueUpdate');
$selectbox = $dropdown.closest('.selectbox'); $selectbox = $dropdown.closest('.selectbox');
...@@ -51,6 +51,8 @@ ...@@ -51,6 +51,8 @@
$value = $block.find('.value'); $value = $block.find('.value');
$collapsedSidebar = $block.find('.sidebar-collapsed-user'); $collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut(); $loading = $block.find('.block-loading').fadeOut();
selectedIdDefault = (defaultNullUser && showNullUser) ? 0 : null;
selectedId = $dropdown.data('selected') || selectedIdDefault;
var updateIssueBoardsIssue = function () { var updateIssueBoardsIssue = function () {
$loading.removeClass('hidden').fadeIn(); $loading.removeClass('hidden').fadeIn();
...@@ -186,12 +188,14 @@ ...@@ -186,12 +188,14 @@
fieldName: $dropdown.data('field-name'), fieldName: $dropdown.data('field-name'),
toggleLabel: function(selected, el) { toggleLabel: function(selected, el) {
if (selected && 'id' in selected && $(el).hasClass('is-active')) { if (selected && 'id' in selected && $(el).hasClass('is-active')) {
$dropdown.find('.dropdown-toggle-text').removeClass('is-default');
if (selected.text) { if (selected.text) {
return selected.text; return selected.text;
} else { } else {
return selected.name; return selected.name;
} }
} else { } else {
$dropdown.find('.dropdown-toggle-text').addClass('is-default');
return defaultLabel; return defaultLabel;
} }
}, },
...@@ -204,13 +208,14 @@ ...@@ -204,13 +208,14 @@
}, },
vue: $dropdown.hasClass('js-issue-board-sidebar'), vue: $dropdown.hasClass('js-issue-board-sidebar'),
clicked: function(user, $el, e) { clicked: function(user, $el, e) {
var isIssueIndex, isMRIndex, page, selected; var isIssueIndex, isMRIndex, page, selected, isSelecting;
page = $('body').data('page'); page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index'; isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index'); isMRIndex = (page === page && page === 'projects:merge_requests:index');
isSelecting = (user.id !== selectedId);
selectedId = isSelecting ? user.id : selectedIdDefault;
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
e.preventDefault(); e.preventDefault();
selectedId = user.id;
if (selectedId === gon.current_user_id) { if (selectedId === gon.current_user_id) {
$('.assign-to-me-link').hide(); $('.assign-to-me-link').hide();
} else { } else {
...@@ -221,12 +226,11 @@ ...@@ -221,12 +226,11 @@
if ($el.closest('.add-issues-modal').length) { if ($el.closest('.add-issues-modal').length) {
gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id; gl.issueBoards.ModalStore.store.filter[$dropdown.data('field-name')] = user.id;
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
selectedId = user.id;
return Issuable.filterResults($dropdown.closest('form')); return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) { } else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit(); return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) { } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (user.id) { if (user.id && isSelecting) {
gl.issueBoards.boardStoreIssueSet('assignee', new ListUser({ gl.issueBoards.boardStoreIssueSet('assignee', new ListUser({
id: user.id, id: user.id,
username: user.username, username: user.username,
...@@ -248,6 +252,9 @@ ...@@ -248,6 +252,9 @@
}, },
opened: function(e) { opened: function(e) {
const $el = $(e.currentTarget); const $el = $(e.currentTarget);
if ($dropdown.hasClass('js-issue-board-sidebar')) {
selectedId = parseInt($dropdown[0].dataset.selected, 10) || selectedIdDefault;
}
$el.find('.is-active').removeClass('is-active'); $el.find('.is-active').removeClass('is-active');
$el.find(`li[data-user-id="${selectedId}"] .dropdown-menu-user-link`).addClass('is-active'); $el.find(`li[data-user-id="${selectedId}"] .dropdown-menu-user-link`).addClass('is-active');
}, },
......
...@@ -10,13 +10,18 @@ export default { ...@@ -10,13 +10,18 @@ export default {
pipelines: { pipelines: {
type: Array, type: Array,
required: true, required: true,
default: () => ([]),
}, },
service: { service: {
type: Object, type: Object,
required: true, required: true,
}, },
updateGraphDropdown: {
type: Boolean,
required: false,
default: false,
},
}, },
components: { components: {
...@@ -40,7 +45,9 @@ export default { ...@@ -40,7 +45,9 @@ export default {
v-bind:model="model"> v-bind:model="model">
<tr is="pipelines-table-row-component" <tr is="pipelines-table-row-component"
:pipeline="model" :pipeline="model"
:service="service"></tr> :service="service"
:update-graph-dropdown="updateGraphDropdown"
/>
</template> </template>
</tbody> </tbody>
</table> </table>
......
...@@ -3,7 +3,7 @@ import AsyncButtonComponent from '../../pipelines/components/async_button.vue'; ...@@ -3,7 +3,7 @@ import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions'; import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions';
import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts'; import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
import PipelinesStatusComponent from '../../pipelines/components/status'; import PipelinesStatusComponent from '../../pipelines/components/status';
import PipelinesStageComponent from '../../pipelines/components/stage'; import PipelinesStageComponent from '../../pipelines/components/stage.vue';
import PipelinesUrlComponent from '../../pipelines/components/pipeline_url'; import PipelinesUrlComponent from '../../pipelines/components/pipeline_url';
import PipelinesTimeagoComponent from '../../pipelines/components/time_ago'; import PipelinesTimeagoComponent from '../../pipelines/components/time_ago';
import CommitComponent from './commit'; import CommitComponent from './commit';
...@@ -24,6 +24,12 @@ export default { ...@@ -24,6 +24,12 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
updateGraphDropdown: {
type: Boolean,
required: false,
default: false,
},
}, },
components: { components: {
...@@ -213,7 +219,10 @@ export default { ...@@ -213,7 +219,10 @@ export default {
<div class="stage-container dropdown js-mini-pipeline-graph" <div class="stage-container dropdown js-mini-pipeline-graph"
v-if="pipeline.details.stages.length > 0" v-if="pipeline.details.stages.length > 0"
v-for="stage in pipeline.details.stages"> v-for="stage in pipeline.details.stages">
<dropdown-stage :stage="stage"/>
<dropdown-stage
:stage="stage"
:update-dropdown="updateGraphDropdown"/>
</div> </div>
</td> </td>
......
...@@ -227,8 +227,8 @@ ...@@ -227,8 +227,8 @@
.award-control-icon-positive, .award-control-icon-positive,
.award-control-icon-super-positive { .award-control-icon-super-positive {
position: absolute; position: absolute;
left: 7px; left: 11px;
bottom: 9px; bottom: 7px;
opacity: 0; opacity: 0;
@include transition(opacity, transform); @include transition(opacity, transform);
} }
......
...@@ -254,6 +254,63 @@ ...@@ -254,6 +254,63 @@
padding: 10px 0; padding: 10px 0;
} }
.landing {
margin-bottom: $gl-padding;
overflow: hidden;
display: flex;
position: relative;
border: 1px solid $blue-300;
border-radius: $border-radius-default;
background-color: $blue-25;
justify-content: center;
.dismiss-button {
position: absolute;
right: 6px;
top: 6px;
cursor: pointer;
color: $blue-300;
z-index: 1;
border: none;
background-color: transparent;
&:hover,
&:focus {
border: none;
color: $blue-400;
}
}
.svg-container {
align-self: center;
}
.inner-content {
text-align: left;
white-space: nowrap;
h4 {
color: $gl-text-color;
font-size: 17px;
}
p {
color: $gl-text-color;
margin-bottom: $gl-padding;
}
}
@media (max-width: $screen-sm-min) {
flex-direction: column;
.inner-content {
white-space: normal;
padding: 0 28px;
text-align: center;
}
}
}
.empty-state { .empty-state {
margin: 100px 0 0; margin: 100px 0 0;
......
...@@ -424,6 +424,11 @@ table { ...@@ -424,6 +424,11 @@ table {
} }
} }
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.str-truncated { .str-truncated {
&-60 { &-60 {
@include str-truncated(60%); @include str-truncated(60%);
......
...@@ -390,7 +390,8 @@ ...@@ -390,7 +390,8 @@
&::before { &::before {
position: absolute; position: absolute;
left: 6px; left: 6px;
top: 6px; top: 50%;
transform: translateY(-50%);
font: normal normal normal 14px/1 FontAwesome; font: normal normal normal 14px/1 FontAwesome;
font-size: inherit; font-size: inherit;
text-rendering: auto; text-rendering: auto;
......
...@@ -61,11 +61,13 @@ ...@@ -61,11 +61,13 @@
.file-content { .file-content {
background: $white-light; background: $white-light;
&.image_file { &.image_file,
&.video {
background: $file-image-bg; background: $file-image-bg;
text-align: center; text-align: center;
img { img,
video {
padding: 20px; padding: 20px;
max-width: 80%; max-width: 80%;
} }
......
...@@ -93,11 +93,6 @@ ...@@ -93,11 +93,6 @@
top: $gl-padding-top; top: $gl-padding-top;
} }
.bordered-box {
border: 1px solid $border-color;
border-radius: $border-radius-default;
}
.content-list { .content-list {
li { li {
padding: 18px $gl-padding $gl-padding; padding: 18px $gl-padding $gl-padding;
...@@ -139,42 +134,9 @@ ...@@ -139,42 +134,9 @@
} }
} }
.landing { .landing svg {
margin-bottom: $gl-padding; width: 136px;
overflow: hidden; height: 136px;
.dismiss-icon {
position: absolute;
right: $cycle-analytics-box-padding;
cursor: pointer;
color: $cycle-analytics-dismiss-icon-color;
}
.svg-container {
text-align: center;
svg {
width: 136px;
height: 136px;
}
}
.inner-content {
@media (max-width: $screen-xs-max) {
padding: 0 28px;
text-align: center;
}
h4 {
color: $gl-text-color;
font-size: 17px;
}
p {
color: $cycle-analytics-box-text-color;
margin-bottom: $gl-padding;
}
}
} }
.fa-spinner { .fa-spinner {
......
...@@ -425,12 +425,6 @@ ...@@ -425,12 +425,6 @@
float: right; float: right;
} }
.diffs {
.content-block {
border-bottom: none;
}
}
.files-changed { .files-changed {
border-bottom: none; border-bottom: none;
} }
......
...@@ -157,7 +157,8 @@ ...@@ -157,7 +157,8 @@
.prometheus-graph { .prometheus-graph {
text { text {
fill: $stat-graph-axis-fill; fill: $gl-text-color;
stroke-width: 0;
} }
.label-axis-text, .label-axis-text,
...@@ -210,27 +211,33 @@ ...@@ -210,27 +211,33 @@
.rect-text-metric { .rect-text-metric {
fill: $white-light; fill: $white-light;
stroke-width: 1; stroke-width: 1;
stroke: $black; stroke: $gray-darkest;
} }
.rect-axis-text { .rect-axis-text {
fill: $white-light; fill: $white-light;
} }
.text-metric, .text-metric {
.text-median-metric, font-weight: 600;
.text-metric-usage,
.text-metric-date {
fill: $black;
} }
.text-metric-date { .selected-metric-line {
font-weight: 200; stroke: $gl-gray-dark;
stroke-width: 1;
} }
.selected-metric-line { .deployment-line {
stroke: $black; stroke: $black;
stroke-width: 1; stroke-width: 2;
}
.deploy-info-text {
dominant-baseline: text-before-edge;
}
.text-metric-bold {
font-weight: 600;
} }
.prometheus-state { .prometheus-state {
......
...@@ -88,3 +88,26 @@ ...@@ -88,3 +88,26 @@
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
margin-top: 10px; margin-top: 10px;
} }
.explore-groups.landing {
margin-top: 10px;
.inner-content {
padding: 0;
p {
margin: 7px 0 0;
max-width: 480px;
padding: 0 $gl-padding;
@media (max-width: $screen-sm-min) {
margin: 0 auto;
}
}
}
svg {
width: 62px;
height: 50px;
}
}
...@@ -6,7 +6,13 @@ ...@@ -6,7 +6,13 @@
} }
.limit-container-width { .limit-container-width {
.detail-page-header { .detail-page-header,
.page-content-header,
.commit-box,
.info-well,
.notes,
.commit-ci-menu,
.files-changed {
@extend .fixed-width-container; @extend .fixed-width-container;
} }
...@@ -36,8 +42,7 @@ ...@@ -36,8 +42,7 @@
} }
.diffs { .diffs {
.mr-version-controls, .mr-version-controls {
.files-changed {
@extend .fixed-width-container; @extend .fixed-width-container;
} }
} }
......
...@@ -161,3 +161,86 @@ ul.related-merge-requests > li { ...@@ -161,3 +161,86 @@ ul.related-merge-requests > li {
.recaptcha { .recaptcha {
margin-bottom: 30px; margin-bottom: 30px;
} }
.new-branch-col {
padding-top: 10px;
}
.create-mr-dropdown-wrap {
.btn-group:not(.hide) {
display: flex;
}
.js-create-merge-request {
flex-grow: 1;
flex-shrink: 0;
}
.dropdown-menu {
width: 300px;
opacity: 1;
visibility: visible;
transform: translateY(0);
display: none;
}
.dropdown-toggle {
.fa-caret-down {
pointer-events: none;
margin-left: 0;
color: inherit;
margin-left: 0;
}
}
li:not(.divider) {
padding: 6px;
cursor: pointer;
&:hover,
&:focus {
background-color: $dropdown-hover-color;
color: $white-light;
}
&.droplab-item-selected {
.icon-container {
i {
visibility: visible;
}
}
}
.icon-container {
float: left;
padding-left: 6px;
i {
visibility: hidden;
}
}
.description {
padding-left: 30px;
font-size: 13px;
strong {
display: block;
font-weight: 600;
}
}
}
}
@media (min-width: $screen-sm-min) {
.new-branch-col {
padding-top: 0;
text-align: right;
}
.create-mr-dropdown-wrap {
.btn-group:not(.hide) {
display: inline-block;
}
}
}
...@@ -133,3 +133,55 @@ ...@@ -133,3 +133,55 @@
right: 160px; right: 160px;
} }
} }
.flex-project-members-panel {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
@media (max-width: $screen-sm-min) {
display: block;
.flex-project-title {
vertical-align: top;
display: inline-block;
max-width: 90%;
}
}
.flex-project-title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.badge {
height: 17px;
line-height: 16px;
margin-right: 5px;
padding-top: 1px;
padding-bottom: 1px;
}
.flex-project-members-form {
flex-wrap: nowrap;
white-space: nowrap;
margin-left: auto;
}
}
.panel {
.panel-heading {
.badge {
margin-top: 0;
}
@media (max-width: $screen-sm-min) {
.badge {
margin-right: 0;
margin-left: 0;
}
}
}
}
\ No newline at end of file
...@@ -511,7 +511,6 @@ ...@@ -511,7 +511,6 @@
.mr-version-controls { .mr-version-controls {
background: $gray-light; background: $gray-light;
border-bottom: 1px solid $border-color;
color: $gl-text-color; color: $gl-text-color;
.mr-version-menus-container { .mr-version-menus-container {
......
...@@ -67,7 +67,7 @@ ul.notes { ...@@ -67,7 +67,7 @@ ul.notes {
} }
} }
&.is-editting { &.is-editing {
.note-header, .note-header,
.note-text, .note-text,
.edited-text { .edited-text {
......
...@@ -781,16 +781,11 @@ ...@@ -781,16 +781,11 @@
} }
.scrollable-menu { .scrollable-menu {
padding: 0;
max-height: 245px; max-height: 245px;
overflow: auto; overflow: auto;
} }
// Loading icon
.builds-dropdown-loading {
margin: 0 auto;
width: 20px;
}
// Action icon on the right // Action icon on the right
a.ci-action-icon-wrapper { a.ci-action-icon-wrapper {
color: $action-icon-color; color: $action-icon-color;
...@@ -893,30 +888,29 @@ ...@@ -893,30 +888,29 @@
* Top arrow in the dropdown in the mini pipeline graph * Top arrow in the dropdown in the mini pipeline graph
*/ */
.mini-pipeline-graph-dropdown-menu { .mini-pipeline-graph-dropdown-menu {
.arrow-up {
&::before,
&::after {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: -6px;
left: 2px;
border-width: 0 5px 6px;
}
&::before { &::before,
border-width: 0 5px 5px; &::after {
border-bottom-color: $border-color; content: '';
} display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: -6px;
left: 2px;
border-width: 0 5px 6px;
}
&::after { &::before {
margin-top: 1px; border-width: 0 5px 5px;
border-bottom-color: $white-light; border-bottom-color: $border-color;
} }
&::after {
margin-top: 1px;
border-bottom-color: $white-light;
} }
} }
......
...@@ -71,7 +71,6 @@ ...@@ -71,7 +71,6 @@
.nav-controls { .nav-controls {
width: auto; width: auto;
min-width: 50%; min-width: 50%;
white-space: nowrap;
} }
} }
......
class Admin::HooksController < Admin::ApplicationController class Admin::HooksController < Admin::ApplicationController
before_action :hook, only: :edit
def index def index
@hooks = SystemHook.all @hooks = SystemHook.all
@hook = SystemHook.new @hook = SystemHook.new
...@@ -15,15 +17,25 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -15,15 +17,25 @@ class Admin::HooksController < Admin::ApplicationController
end end
end end
def edit
end
def update
if hook.update_attributes(hook_params)
flash[:notice] = 'System hook was successfully updated.'
redirect_to admin_hooks_path
else
render 'edit'
end
end
def destroy def destroy
@hook = SystemHook.find(params[:id]) hook.destroy
@hook.destroy
redirect_to admin_hooks_path redirect_to admin_hooks_path
end end
def test def test
@hook = SystemHook.find(params[:hook_id])
data = { data = {
event_name: "project_create", event_name: "project_create",
name: "Ruby", name: "Ruby",
...@@ -32,11 +44,17 @@ class Admin::HooksController < Admin::ApplicationController ...@@ -32,11 +44,17 @@ class Admin::HooksController < Admin::ApplicationController
owner_name: "Someone", owner_name: "Someone",
owner_email: "example@gitlabhq.com" owner_email: "example@gitlabhq.com"
} }
@hook.execute(data, 'system_hooks') hook.execute(data, 'system_hooks')
redirect_back_or_default redirect_back_or_default
end end
private
def hook
@hook ||= SystemHook.find(params[:id])
end
def hook_params def hook_params
params.require(:hook).permit( params.require(:hook).permit(
:enable_ssl_verification, :enable_ssl_verification,
......
module MilestoneActions
extend ActiveSupport::Concern
def merge_requests
respond_to do |format|
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", {
merge_requests: @milestone.merge_requests,
show_project_name: true
})
end
end
end
def participants
respond_to do |format|
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_participants_tab", {
users: @milestone.participants
})
end
end
end
def labels
respond_to do |format|
format.html { redirect_to milestone_redirect_path }
format.json do
render json: tabs_json("shared/milestones/_labels_tab", {
labels: @milestone.labels
})
end
end
end
private
def tabs_json(partial, data = {})
{
html: view_to_html_string(partial, data)
}
end
def milestone_redirect_path
if @project
namespace_project_milestone_path(@project.namespace, @project, @milestone)
else
group_milestone_path(@group, @milestone.safe_title, title: @milestone.title)
end
end
end
module NotesActions
include RendersNotes
extend ActiveSupport::Concern
included do
before_action :authorize_admin_note!, only: [:update, :destroy]
end
def index
current_fetched_at = Time.now.to_i
notes_json = { notes: [], last_fetched_at: current_fetched_at }
@notes = notes_finder.execute.inc_relations_for_view
@notes = prepare_notes_for_rendering(@notes)
@notes.each do |note|
next if note.cross_reference_not_visible_for?(current_user)
notes_json[:notes] << note_json(note)
end
render json: notes_json
end
def create
create_params = note_params.merge(
merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
in_reply_to_discussion_id: params[:in_reply_to_discussion_id]
)
@note = Notes::CreateService.new(project, current_user, create_params).execute
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
end
end
def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
Banzai::NoteRenderer.render([@note], @project, current_user)
end
respond_to do |format|
format.json { render json: note_json(@note) }
format.html { redirect_back_or_default }
end
end
def destroy
if note.editable?
Notes::DestroyService.new(project, current_user).execute(note)
end
respond_to do |format|
format.js { head :ok }
end
end
private
def note_json(note)
attrs = {
commands_changes: note.commands_changes
}
if note.persisted?
attrs.merge!(
valid: true,
id: note.id,
discussion_id: note.discussion_id(noteable),
html: note_html(note),
note: note.note
)
discussion = note.to_discussion(noteable)
unless discussion.individual_note?
attrs.merge!(
discussion_resolvable: discussion.resolvable?,
diff_discussion_html: diff_discussion_html(discussion),
discussion_html: discussion_html(discussion)
)
end
else
attrs.merge!(
valid: false,
errors: note.errors
)
end
attrs
end
def authorize_admin_note!
return access_denied! unless can?(current_user, :admin_note, note)
end
def note_params
params.require(:note).permit(
:project_id,
:noteable_type,
:noteable_id,
:commit_id,
:noteable,
:type,
:note,
:attachment,
# LegacyDiffNote
:line_code,
# DiffNote
:position
)
end
def noteable
@noteable ||= notes_finder.target
end
def last_fetched_at
request.headers['X-Last-Fetched-At']
end
def notes_finder
@notes_finder ||= NotesFinder.new(project, current_user, finder_params)
end
end
...@@ -14,4 +14,8 @@ module RendersBlob ...@@ -14,4 +14,8 @@ module RendersBlob
html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_asynchronously: false) html: view_to_html_string("projects/blob/_viewer", viewer: viewer, load_asynchronously: false)
} }
end end
def override_max_blob_size(blob)
blob.override_max_size! if params[:override_max_size] == 'true'
end
end end
...@@ -10,6 +10,8 @@ module RendersNotes ...@@ -10,6 +10,8 @@ module RendersNotes
private private
def preload_max_access_for_authors(notes, project) def preload_max_access_for_authors(notes, project)
return nil unless project
user_ids = notes.map(&:author_id) user_ids = notes.map(&:author_id)
project.team.max_member_access_for_user_ids(user_ids) project.team.max_member_access_for_user_ids(user_ids)
end end
......
...@@ -38,6 +38,7 @@ module ServiceParams ...@@ -38,6 +38,7 @@ module ServiceParams
:new_issue_url, :new_issue_url,
:notify, :notify,
:notify_only_broken_pipelines, :notify_only_broken_pipelines,
:notify_only_default_branch,
:password, :password,
:priority, :priority,
:project_key, :project_key,
......
...@@ -5,10 +5,12 @@ module SnippetsActions ...@@ -5,10 +5,12 @@ module SnippetsActions
end end
def raw def raw
disposition = params[:inline] == 'false' ? 'attachment' : 'inline'
send_data( send_data(
convert_line_endings(@snippet.content), convert_line_endings(@snippet.content),
type: 'text/plain; charset=utf-8', type: 'text/plain; charset=utf-8',
disposition: 'inline', disposition: disposition,
filename: @snippet.sanitized_file_name filename: @snippet.sanitized_file_name
) )
end end
......
...@@ -22,7 +22,8 @@ module ToggleAwardEmoji ...@@ -22,7 +22,8 @@ module ToggleAwardEmoji
def to_todoable(awardable) def to_todoable(awardable)
case awardable case awardable
when Note when Note
awardable.noteable # we don't create todos for personal snippet comments for now
awardable.for_personal_snippet? ? nil : awardable.noteable
when MergeRequest, Issue when MergeRequest, Issue
awardable awardable
when Snippet when Snippet
......
module UploadsActions
def create
link_to_file = UploadService.new(model, params[:file], uploader_class).execute
respond_to do |format|
if link_to_file
format.json do
render json: { link: link_to_file }
end
else
format.json do
render json: 'Invalid file.', status: :unprocessable_entity
end
end
end
end
def show
return render_404 unless uploader.exists?
disposition = uploader.image_or_video? ? 'inline' : 'attachment'
expires_in 0.seconds, must_revalidate: true, private: true
send_file uploader.file.path, disposition: disposition
end
end
class Groups::MilestonesController < Groups::ApplicationController class Groups::MilestonesController < Groups::ApplicationController
include MilestoneActions
before_action :group_projects before_action :group_projects
before_action :milestone, only: [:show, :update] before_action :milestone, only: [:show, :update, :merge_requests, :participants, :labels]
before_action :authorize_admin_milestones!, only: [:new, :create, :update] before_action :authorize_admin_milestones!, only: [:new, :create, :update]
def index def index
......
...@@ -89,4 +89,8 @@ class Projects::ApplicationController < ApplicationController ...@@ -89,4 +89,8 @@ class Projects::ApplicationController < ApplicationController
def builds_enabled def builds_enabled
return render_404 unless @project.feature_available?(:builds, current_user) return render_404 unless @project.feature_available?(:builds, current_user)
end end
def require_pages_enabled!
not_found unless Gitlab.config.pages.enabled
end
end end
...@@ -16,7 +16,8 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -16,7 +16,8 @@ class Projects::ArtifactsController < Projects::ApplicationController
end end
def browse def browse
directory = params[:path] ? "#{params[:path]}/" : '' @path = params[:path]
directory = @path ? "#{@path}/" : ''
@entry = build.artifacts_metadata_entry(directory) @entry = build.artifacts_metadata_entry(directory)
render_404 unless @entry.exists? render_404 unless @entry.exists?
...@@ -60,7 +61,10 @@ class Projects::ArtifactsController < Projects::ApplicationController ...@@ -60,7 +61,10 @@ class Projects::ArtifactsController < Projects::ApplicationController
end end
def build def build
@build ||= build_from_id || build_from_ref @build ||= begin
build = build_from_id || build_from_ref
build&.present(current_user: current_user)
end
end end
def build_from_id def build_from_id
......
...@@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -35,7 +35,7 @@ class Projects::BlobController < Projects::ApplicationController
end end
def show def show
@blob.override_max_size! if params[:override_max_size] == 'true' override_max_blob_size(@blob)
respond_to do |format| respond_to do |format|
format.html do format.html do
......
...@@ -46,20 +46,28 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -46,20 +46,28 @@ class Projects::BranchesController < Projects::ApplicationController
SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue
end end
if result[:status] == :success respond_to do |format|
@branch = result[:branch] format.html do
if result[:status] == :success
if redirect_to_autodeploy if redirect_to_autodeploy
redirect_to( redirect_to url_to_autodeploy_setup(project, branch_name),
url_to_autodeploy_setup(project, branch_name), notice: view_context.autodeploy_flash_notice(branch_name)
notice: view_context.autodeploy_flash_notice(branch_name)) else
else redirect_to namespace_project_tree_path(@project.namespace, @project, branch_name)
redirect_to namespace_project_tree_path(@project.namespace, @project, end
@branch.name) else
@error = result[:message]
render action: 'new'
end
end
format.json do
if result[:status] == :success
render json: { name: branch_name, url: namespace_project_tree_url(@project.namespace, @project, branch_name) }
else
render json: result[:messsage], status: :unprocessable_entity
end
end end
else
@error = result[:message]
render action: 'new'
end end
end end
......
class Projects::DeploymentsController < Projects::ApplicationController
before_action :authorize_read_environment!
before_action :authorize_read_deployment!
def index
deployments = environment.deployments.reorder(created_at: :desc)
deployments = deployments.where('created_at > ?', params[:after].to_time) if params[:after]&.to_time
render json: { deployments: DeploymentSerializer.new(user: @current_user, project: project)
.represent_concise(deployments) }
end
private
def environment
@environment ||= project.environments.find(params[:environment_id])
end
end
class Projects::HooksController < Projects::ApplicationController class Projects::HooksController < Projects::ApplicationController
# Authorize # Authorize
before_action :authorize_admin_project! before_action :authorize_admin_project!
before_action :hook, only: :edit
respond_to :html respond_to :html
...@@ -17,6 +18,18 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -17,6 +18,18 @@ class Projects::HooksController < Projects::ApplicationController
redirect_to namespace_project_settings_integrations_path(@project.namespace, @project) redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
end end
def edit
end
def update
if hook.update_attributes(hook_params)
flash[:notice] = 'Hook was successfully updated.'
redirect_to namespace_project_settings_integrations_path(@project.namespace, @project)
else
render 'edit'
end
end
def test def test
if !@project.empty_repo? if !@project.empty_repo?
status, message = TestHookService.new.execute(hook, current_user) status, message = TestHookService.new.execute(hook, current_user)
......
...@@ -11,7 +11,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -11,7 +11,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :redirect_to_external_issue_tracker, only: [:index, :new]
before_action :module_enabled before_action :module_enabled
before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests,
:related_branches, :can_create_branch, :rendered_title] :related_branches, :can_create_branch, :rendered_title, :create_merge_request]
# Allow read any issue # Allow read any issue
before_action :authorize_read_issue!, only: [:show, :rendered_title] before_action :authorize_read_issue!, only: [:show, :rendered_title]
...@@ -22,6 +22,9 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -22,6 +22,9 @@ class Projects::IssuesController < Projects::ApplicationController
# Allow modify issue # Allow modify issue
before_action :authorize_update_issue!, only: [:edit, :update] before_action :authorize_update_issue!, only: [:edit, :update]
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request]
respond_to :html respond_to :html
def index def index
...@@ -191,7 +194,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -191,7 +194,7 @@ class Projects::IssuesController < Projects::ApplicationController
respond_to do |format| respond_to do |format|
format.json do format.json do
render json: { can_create_branch: can_create } render json: { can_create_branch: can_create, has_related_branch: @issue.has_related_branch? }
end end
end end
end end
...@@ -201,6 +204,16 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -201,6 +204,16 @@ class Projects::IssuesController < Projects::ApplicationController
render json: { title: view_context.markdown_field(@issue, :title) } render json: { title: view_context.markdown_field(@issue, :title) }
end end
def create_merge_request
result = MergeRequests::CreateFromIssueService.new(project, current_user, issue_iid: issue.iid).execute
if result[:status] == :success
render json: MergeRequestCreateSerializer.new.represent(result[:merge_request])
else
render json: result[:messsage], status: :unprocessable_entity
end
end
protected protected
def issue def issue
...@@ -224,6 +237,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -224,6 +237,10 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless can?(current_user, :admin_issue, @project) return render_404 unless can?(current_user, :admin_issue, @project)
end end
def authorize_create_merge_request!
return render_404 unless can?(current_user, :push_code, @project) && @issue.can_be_worked_on?(current_user)
end
def module_enabled def module_enabled
return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker? return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
end end
......
...@@ -120,7 +120,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -120,7 +120,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
define_diff_comment_vars define_diff_comment_vars
else else
build_merge_request build_merge_request
@diffs = @merge_request.diffs(diff_options) @compare = @merge_request
@diffs = @compare.diffs(diff_options)
@diff_notes_disabled = true @diff_notes_disabled = true
end end
...@@ -584,12 +585,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -584,12 +585,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
end end
@diffs = @compare =
if @start_sha if @start_sha
@merge_request_diff.compare_with(@start_sha).diffs(diff_options) @merge_request_diff.compare_with(@start_sha)
else else
@merge_request_diff.diffs(diff_options) @merge_request_diff
end end
@diffs = @compare.diffs(diff_options)
end end
def define_diff_comment_vars def define_diff_comment_vars
...@@ -598,11 +601,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -598,11 +601,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
noteable_id: @merge_request.id noteable_id: @merge_request.id
} }
@diff_notes_disabled = !@merge_request_diff.latest? || @start_sha @diff_notes_disabled = false
@use_legacy_diff_notes = !@merge_request.has_complete_diff_refs? @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
@grouped_diff_discussions = @merge_request.grouped_diff_discussions(@merge_request_diff.diff_refs) @grouped_diff_discussions = @merge_request.grouped_diff_discussions(@compare.diff_refs)
@notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes)) @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
end end
......
class Projects::MilestonesController < Projects::ApplicationController class Projects::MilestonesController < Projects::ApplicationController
include MilestoneActions
before_action :module_enabled before_action :module_enabled
before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests] before_action :milestone, only: [:edit, :update, :destroy, :show, :sort_issues, :sort_merge_requests, :merge_requests, :participants, :labels]
# Allow read any milestone # Allow read any milestone
before_action :authorize_read_milestone! before_action :authorize_read_milestone!
# Allow admin milestone # Allow admin milestone
before_action :authorize_admin_milestone!, except: [:index, :show] before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels]
respond_to :html respond_to :html
......
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.
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.
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.
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.
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