New upstream version 11.3.10+dfsg
|
@ -33,6 +33,15 @@ rules:
|
||||||
- error
|
- error
|
||||||
- max: 1
|
- max: 1
|
||||||
promise/catch-or-return: error
|
promise/catch-or-return: error
|
||||||
|
no-param-reassign:
|
||||||
|
- error
|
||||||
|
- props: true
|
||||||
|
ignorePropertyModificationsFor:
|
||||||
|
- "acc" # for reduce accumulators
|
||||||
|
- "accumulator" # for reduce accumulators
|
||||||
|
- "el" # for DOM elements
|
||||||
|
- "element" # for DOM elements
|
||||||
|
- "state" # for Vuex mutations
|
||||||
no-underscore-dangle:
|
no-underscore-dangle:
|
||||||
- error
|
- error
|
||||||
- allow:
|
- allow:
|
||||||
|
|
34
.flayignore
|
@ -1,34 +0,0 @@
|
||||||
*.erb
|
|
||||||
lib/gitlab/sanitizers/svg/whitelist.rb
|
|
||||||
lib/gitlab/diff/position_tracer.rb
|
|
||||||
app/controllers/projects/approver_groups_controller.rb
|
|
||||||
app/controllers/projects/approvers_controller.rb
|
|
||||||
app/controllers/projects/protected_branches/merge_access_levels_controller.rb
|
|
||||||
app/controllers/projects/protected_branches/push_access_levels_controller.rb
|
|
||||||
app/controllers/projects/protected_tags/create_access_levels_controller.rb
|
|
||||||
app/policies/project_policy.rb
|
|
||||||
app/models/concerns/relative_positioning.rb
|
|
||||||
app/workers/stuck_merge_jobs_worker.rb
|
|
||||||
lib/gitlab/redis/*.rb
|
|
||||||
lib/gitlab/gitaly_client/operation_service.rb
|
|
||||||
app/models/project_services/packagist_service.rb
|
|
||||||
lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
|
|
||||||
lib/gitlab/background_migration/*
|
|
||||||
app/models/project_services/kubernetes_service.rb
|
|
||||||
lib/gitlab/workhorse.rb
|
|
||||||
lib/gitlab/ci/trace/chunked_io.rb
|
|
||||||
lib/gitlab/gitaly_client/ref_service.rb
|
|
||||||
lib/gitlab/gitaly_client/commit_service.rb
|
|
||||||
lib/gitlab/git/commit.rb
|
|
||||||
lib/gitlab/git/tag.rb
|
|
||||||
|
|
||||||
ee/db/**/*
|
|
||||||
ee/app/serializers/ee/merge_request_widget_entity.rb
|
|
||||||
ee/lib/api/epics.rb
|
|
||||||
ee/lib/api/geo_nodes.rb
|
|
||||||
ee/lib/ee/api/group_boards.rb
|
|
||||||
ee/lib/ee/api/boards.rb
|
|
||||||
ee/lib/ee/gitlab/ldap/sync/admin_users.rb
|
|
||||||
ee/app/workers/geo/file_download_dispatch_worker/job_artifact_job_finder.rb
|
|
||||||
ee/app/workers/geo/file_download_dispatch_worker/lfs_object_job_finder.rb
|
|
||||||
ee/spec/**/*
|
|
1
.gitignore
vendored
|
@ -77,3 +77,4 @@ eslint-report.html
|
||||||
/plugins/*
|
/plugins/*
|
||||||
/.gitlab_pages_secret
|
/.gitlab_pages_secret
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
/junit_rspec.xml
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
|
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-69.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
|
||||||
|
|
||||||
.dedicated-runner: &dedicated-runner
|
.dedicated-runner: &dedicated-runner
|
||||||
retry: 1
|
retry: 1
|
||||||
|
@ -131,7 +131,7 @@ stages:
|
||||||
.single-script-job: &single-script-job
|
.single-script-job: &single-script-job
|
||||||
image: ruby:2.4-alpine
|
image: ruby:2.4-alpine
|
||||||
before_script: []
|
before_script: []
|
||||||
stage: build
|
stage: test
|
||||||
cache: {}
|
cache: {}
|
||||||
dependencies: []
|
dependencies: []
|
||||||
variables: &single-script-job-variables
|
variables: &single-script-job-variables
|
||||||
|
@ -171,7 +171,7 @@ stages:
|
||||||
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
|
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
|
||||||
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
|
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
|
||||||
- scripts/gitaly-test-spawn
|
- scripts/gitaly-test-spawn
|
||||||
- knapsack rspec "--color --format documentation"
|
- knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml"
|
||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 31d
|
expire_in: 31d
|
||||||
when: always
|
when: always
|
||||||
|
@ -180,6 +180,8 @@ stages:
|
||||||
- knapsack/
|
- knapsack/
|
||||||
- rspec_flaky/
|
- rspec_flaky/
|
||||||
- tmp/capybara/
|
- tmp/capybara/
|
||||||
|
reports:
|
||||||
|
junit: junit_rspec.xml
|
||||||
|
|
||||||
.rspec-metadata-pg: &rspec-metadata-pg
|
.rspec-metadata-pg: &rspec-metadata-pg
|
||||||
<<: *rspec-metadata
|
<<: *rspec-metadata
|
||||||
|
@ -311,10 +313,13 @@ review-docs-cleanup:
|
||||||
environment:
|
environment:
|
||||||
name: review-docs/$CI_COMMIT_REF_SLUG
|
name: review-docs/$CI_COMMIT_REF_SLUG
|
||||||
action: stop
|
action: stop
|
||||||
when: manual
|
|
||||||
script:
|
script:
|
||||||
- gem install gitlab --no-ri --no-rdoc
|
- gem install gitlab --no-ri --no-rdoc
|
||||||
- ./$SCRIPT_NAME cleanup
|
- ./$SCRIPT_NAME cleanup
|
||||||
|
when: manual
|
||||||
|
only:
|
||||||
|
- branches@gitlab-org/gitlab-ce
|
||||||
|
- branches@gitlab-org/gitlab-ee
|
||||||
|
|
||||||
##
|
##
|
||||||
# Trigger a docker image build in CNG (Cloud Native GitLab) repository
|
# Trigger a docker image build in CNG (Cloud Native GitLab) repository
|
||||||
|
@ -322,7 +327,7 @@ review-docs-cleanup:
|
||||||
cloud-native-image:
|
cloud-native-image:
|
||||||
image: ruby:2.4-alpine
|
image: ruby:2.4-alpine
|
||||||
before_script: []
|
before_script: []
|
||||||
stage: build
|
stage: test
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
variables:
|
variables:
|
||||||
GIT_DEPTH: "1"
|
GIT_DEPTH: "1"
|
||||||
|
@ -703,7 +708,6 @@ gitlab:assets:compile:
|
||||||
SETUP_DB: "false"
|
SETUP_DB: "false"
|
||||||
SKIP_STORAGE_VALIDATION: "true"
|
SKIP_STORAGE_VALIDATION: "true"
|
||||||
WEBPACK_REPORT: "true"
|
WEBPACK_REPORT: "true"
|
||||||
NO_COMPRESSION: "true"
|
|
||||||
# we override the max_old_space_size to prevent OOM errors
|
# we override the max_old_space_size to prevent OOM errors
|
||||||
NODE_OPTIONS: --max_old_space_size=3584
|
NODE_OPTIONS: --max_old_space_size=3584
|
||||||
script:
|
script:
|
||||||
|
@ -717,6 +721,7 @@ gitlab:assets:compile:
|
||||||
expire_in: 31d
|
expire_in: 31d
|
||||||
paths:
|
paths:
|
||||||
- webpack-report/
|
- webpack-report/
|
||||||
|
- public/assets/
|
||||||
|
|
||||||
karma:
|
karma:
|
||||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||||
|
@ -739,7 +744,7 @@ karma:
|
||||||
- chrome_debug.log
|
- chrome_debug.log
|
||||||
- coverage-javascript/
|
- coverage-javascript/
|
||||||
|
|
||||||
codequality:
|
code_quality:
|
||||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||||
image: docker:stable
|
image: docker:stable
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
@ -757,9 +762,13 @@ codequality:
|
||||||
script:
|
script:
|
||||||
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
|
# Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
|
||||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||||
- docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
- docker run
|
||||||
|
--env SOURCE_CODE="$PWD"
|
||||||
|
--volume "$PWD":/code
|
||||||
|
--volume /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
||||||
artifacts:
|
artifacts:
|
||||||
paths: [codeclimate.json]
|
paths: [gl-code-quality-report.json]
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
|
|
||||||
sast:
|
sast:
|
||||||
|
|
17
.gitlab/CODEOWNERS
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# Backend Maintainers are the default for all ruby files
|
||||||
|
*.rb @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
|
||||||
|
*.rake @ayufan @DouweM @dzaporozhets @grzesiek @nick.thomas @rspeicher @rymai @smcgivern
|
||||||
|
|
||||||
|
# Technical writing team are the default reviewers for everything in `doc/`
|
||||||
|
/doc/ @axil @marcia
|
||||||
|
|
||||||
|
# Frontend maintainers should see everything in `app/assets/`
|
||||||
|
app/assets/ @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann
|
||||||
|
|
||||||
|
# Someone from the database team should review changes in `db/`
|
||||||
|
db/ @abrandl @NikolayS
|
||||||
|
|
||||||
|
# Feature specific owners
|
||||||
|
/ee/lib/gitlab/code_owners/ @reprazent
|
||||||
|
/ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono
|
||||||
|
/lib/gitlab/auth/ldap/ @dblessing @mkozono
|
100
.gitlab/issue_templates/Acceptance_Testing.md
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
## Details
|
||||||
|
- **Feature Toggle Name**: `FEATURE_NAME`
|
||||||
|
- **Required GitLab Version**: `vX.X`
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
## 1. Preparation
|
||||||
|
|
||||||
|
- [ ] **Controllers and workers**:
|
||||||
|
1. Please link to dashboards of the workers, and the controllers and actions that can be impacted
|
||||||
|
2. ...
|
||||||
|
3. ...
|
||||||
|
|
||||||
|
## 2. Development Trial
|
||||||
|
|
||||||
|
#### Check Dev Server Versions
|
||||||
|
- [ ] GitLab: https://dev.gitlab.org/help
|
||||||
|
|
||||||
|
#### Enable on `dev.gitlab.org`:
|
||||||
|
- [ ] `/chatops feature set FEATURE_NAME true --dev` in [`#dev-gitlab`](https://gitlab.slack.com/messages/C6WQ87MU3)
|
||||||
|
|
||||||
|
Then leave running while monitoring and performing some testing through web, api or SSH.
|
||||||
|
|
||||||
|
#### Monitor
|
||||||
|
|
||||||
|
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
|
||||||
|
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
|
||||||
|
- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved)
|
||||||
|
|
||||||
|
## 2. Staging Trial
|
||||||
|
|
||||||
|
#### Check Staging Server Versions
|
||||||
|
- [ ] GitLab: https://staging.gitlab.com/help
|
||||||
|
|
||||||
|
#### Enable on `staging.gitlab.com`
|
||||||
|
- [ ] `/chatops run feature set FEATURE_NAME true --staging` in [`#development`](https://gitlab.slack.com/messages/C02PF508L/)
|
||||||
|
|
||||||
|
Then leave running while monitoring for at least **15 minutes** while performing some testing through web, api or SSH.
|
||||||
|
|
||||||
|
#### Monitor
|
||||||
|
|
||||||
|
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
|
||||||
|
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
|
||||||
|
- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
|
||||||
|
|
||||||
|
## 4. Production Server Version Check
|
||||||
|
|
||||||
|
- [ ] GitLab: https://gitlab.com/help
|
||||||
|
|
||||||
|
## 5. Initial Impact Check
|
||||||
|
|
||||||
|
- [ ] Enable for a subset of users, when using percentage gates: 1%.
|
||||||
|
|
||||||
|
Then leave running while monitoring for at least **15 minutes** while performing some testing through web, api or SSH.
|
||||||
|
|
||||||
|
#### Monitor
|
||||||
|
|
||||||
|
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
|
||||||
|
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
|
||||||
|
- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
|
||||||
|
|
||||||
|
## 6. Low Impact Check
|
||||||
|
|
||||||
|
- [ ] Enable for a bigger subset of users, when using percentage gates: 10%.
|
||||||
|
|
||||||
|
Then leave running while monitoring for at least **30 minutes** while performing some testing through web, api or SSH.
|
||||||
|
|
||||||
|
#### Monitor
|
||||||
|
|
||||||
|
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
|
||||||
|
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
|
||||||
|
- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
|
||||||
|
|
||||||
|
## 7. Mid Impact Trial
|
||||||
|
|
||||||
|
- [ ] Enable for a big subset of users, when using percentage gates: 50%.
|
||||||
|
|
||||||
|
Then leave running while monitoring for at least **12 hours** while performing some testing through web, api or SSH.
|
||||||
|
|
||||||
|
#### Monitor
|
||||||
|
|
||||||
|
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
|
||||||
|
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
|
||||||
|
- [ ] [Check for errors in GitLab Sentry](https://sentry.gitlap.com/gitlab/gitlabcom/?query=is%3Aunresolved)
|
||||||
|
|
||||||
|
## 8. Full Impact Trial
|
||||||
|
|
||||||
|
- [ ] Enable for all users: `/chatops run feature set FEATURE_NAME true
|
||||||
|
|
||||||
|
Then leave running while monitoring for at least **1 week**.
|
||||||
|
|
||||||
|
#### Monitor
|
||||||
|
|
||||||
|
- [ ] [Monitor Using Grafana](https://dashboards.gitlab.net)
|
||||||
|
- [ ] [Inspect logs in ELK](https://log.gitlab.net/app/kibana)
|
||||||
|
- [ ] [Check for errors in GitLab Dev Sentry](https://sentry.gitlap.com/gitlab/devgitlaborg/?query=is%3Aunresolved)
|
||||||
|
|
||||||
|
#### Success?
|
||||||
|
|
||||||
|
- [ ] Remove the feature gate from the code, and close this issue with that MR.
|
54
.gitlab/issue_templates/Documentation.md
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<!--See the general documentation guidelines https://docs.gitlab.com/ee/development/documentation -->
|
||||||
|
|
||||||
|
<!-- Mention "documentation" or "docs" in the issue title -->
|
||||||
|
|
||||||
|
<!-- Use this description template for new docs or updates to existing docs. -->
|
||||||
|
|
||||||
|
<!-- Check the documentation structure guidelines for guidance: https://docs.gitlab.com/ee/development/documentation/structure.html-->
|
||||||
|
|
||||||
|
- [ ] Documents Feature A <!-- feature name -->
|
||||||
|
- [ ] Follow-up from: #XXX, !YYY <!-- Mention related issues, MRs, and epics when available -->
|
||||||
|
|
||||||
|
## New doc or update?
|
||||||
|
|
||||||
|
<!-- Mark either of these boxes: -->
|
||||||
|
|
||||||
|
- [ ] New documentation
|
||||||
|
- [ ] Update existing documentation
|
||||||
|
|
||||||
|
## Checklists
|
||||||
|
|
||||||
|
### Product Manager
|
||||||
|
|
||||||
|
<!-- Reference: https://docs.gitlab.com/ee/development/documentation/workflow.html#1-product-manager-s-role-in-the-documentation-process -->
|
||||||
|
|
||||||
|
- [ ] Add the correct labels
|
||||||
|
- [ ] Add the correct milestone
|
||||||
|
- [ ] Indicate the correct document/directory for this feature <!-- (ping the tech writers for help if you're not sure) -->
|
||||||
|
- [ ] Fill the doc blurb below
|
||||||
|
|
||||||
|
#### Documentation blurb
|
||||||
|
|
||||||
|
<!-- Documentation template: https://docs.gitlab.com/ee/development/documentation/structure.html#documentation-template-for-new-docs -->
|
||||||
|
|
||||||
|
- Doc **title**
|
||||||
|
|
||||||
|
<!-- write the doc title here -->
|
||||||
|
|
||||||
|
- Feature **overview/description**
|
||||||
|
|
||||||
|
<!-- Write the feature overview here -->
|
||||||
|
|
||||||
|
- Feature **use cases**
|
||||||
|
|
||||||
|
<!-- Write the use cases here -->
|
||||||
|
|
||||||
|
### Developer
|
||||||
|
|
||||||
|
<!-- Reference: https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process -->
|
||||||
|
|
||||||
|
- [ ] Copy the doc blurb above and paste it into the doc
|
||||||
|
- [ ] Write the tutorial - explain how to use the feature
|
||||||
|
- [ ] Submit the MR using the appropriate MR description template
|
||||||
|
|
||||||
|
/label ~Documentation
|
|
@ -22,13 +22,13 @@ Set the title to: `[Security] Description of the original issue`
|
||||||
|
|
||||||
- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases
|
- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases
|
||||||
- [ ] At this point, it might be easy to squash the commits from the MR into one
|
- [ ] At this point, it might be easy to squash the commits from the MR into one
|
||||||
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [seckpick documentation]
|
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
|
||||||
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
|
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
|
||||||
- [ ] Create each MR targetting the security branch `security-X-Y`
|
- [ ] Create each MR targetting the security branch `security-X-Y`
|
||||||
- [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR
|
- [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR
|
||||||
- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager.
|
- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager.
|
||||||
|
|
||||||
[seckpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script
|
[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script
|
||||||
|
|
||||||
#### Documentation and final details
|
#### Documentation and final details
|
||||||
|
|
||||||
|
|
96
.gitlab/issue_templates/Test plan.md
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
# Test Plan
|
||||||
|
|
||||||
|
<!-- This issue outlines testing activities related to a particular issue or epic.
|
||||||
|
|
||||||
|
[Here is an example test plan](https://gitlab.com/gitlab-org/gitlab-ce/issues/50353)
|
||||||
|
|
||||||
|
This and other comments should be removed as you write the plan -->
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
<!-- Briefly outline what is being tested
|
||||||
|
|
||||||
|
Mention the issue(s) this test plan is related to -->
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
<!-- State any limits on aspects of the feature being tested
|
||||||
|
Outline the types of data to be included
|
||||||
|
Outline the types of tests to be performed (functional, security, performance,
|
||||||
|
database, automated, etc) -->
|
||||||
|
|
||||||
|
## ACC Matrix
|
||||||
|
|
||||||
|
<!-- Use the matrix below as a template to identify the Attributes, Components, and
|
||||||
|
Capabilities relevant to the scope of this test plan. Add or remove Attributes
|
||||||
|
and Components as required and list Capabilities in the next section
|
||||||
|
|
||||||
|
Attributes (columns) are adverbs or adjectives that describe (at a high level)
|
||||||
|
the qualities testing is meant to ensure Components have.
|
||||||
|
|
||||||
|
Components (rows) are nouns that define major parts of the product being tested.
|
||||||
|
|
||||||
|
Capabilities link Attributes and Components. They are what your product needs to
|
||||||
|
do to make sure a Component fulfills an Attribute
|
||||||
|
|
||||||
|
For more information see the [Google Testing Blog article about the 10 minute
|
||||||
|
test plan](https://testing.googleblog.com/2011/09/10-minute-test-plan.html) and
|
||||||
|
[this wiki page from an open-source tool that implements the ACC
|
||||||
|
model](https://code.google.com/archive/p/test-analytics/wikis/AccExplained.wiki). -->
|
||||||
|
|
||||||
|
| | Simple | Secure | Responsive | Obvious | Stable |
|
||||||
|
|------------|:------:|:------:|:----------:|:-------:|:------:|
|
||||||
|
| Admin | | | | | |
|
||||||
|
| Groups | | | | | |
|
||||||
|
| Project | | | | | |
|
||||||
|
| Repository | | | | | |
|
||||||
|
| Issues | | | | | |
|
||||||
|
| MRs | | | | | |
|
||||||
|
| CI/CD | | | | | |
|
||||||
|
| Ops | | | | | |
|
||||||
|
| Registry | | | | | |
|
||||||
|
| Wiki | | | | | |
|
||||||
|
| Snippets | | | | | |
|
||||||
|
| Settings | | | | | |
|
||||||
|
| Tracking | | | | | |
|
||||||
|
| API | | | | | |
|
||||||
|
|
||||||
|
## Capabilities
|
||||||
|
|
||||||
|
<!-- Use the ACC matrix above to help you identify Capabilities at each relevant
|
||||||
|
intersection of Components and Attributes.
|
||||||
|
|
||||||
|
Some features might be simple enough that they only involve one Component, while
|
||||||
|
more complex features could involve multiple or even all.
|
||||||
|
|
||||||
|
Example (from https://gitlab.com/gitlab-org/gitlab-ce/issues/50353):
|
||||||
|
* Respository is
|
||||||
|
* Simple
|
||||||
|
* It's easy to select the desired file template
|
||||||
|
* It doesn't require unnecessary actions to save the change
|
||||||
|
* It's easy to undo the change after selecting a template
|
||||||
|
* Responsive
|
||||||
|
* The list of templates can be restricted to allow a user to find a specific template among many
|
||||||
|
* Once a template is selected the file content updates quickly and smoothly
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Test Plan
|
||||||
|
|
||||||
|
<!-- If the scope is small enough you may not need to write a list of tests to
|
||||||
|
perform. It might be enough to use the Capabilities to guide your testing.
|
||||||
|
|
||||||
|
If the feature is more complex, especially if it involves multiple Components,
|
||||||
|
briefly outline a set of tests here. When identifying tests to perform be sure
|
||||||
|
to consider risk. Note inherent/known levels of risk so that testing can focus
|
||||||
|
on high risk areas first.
|
||||||
|
|
||||||
|
New end-to-end and integration tests (Selenium and API) should be added to the
|
||||||
|
[Test Coverage sheet](https://docs.google.com/spreadsheets/d/1RlLfXGboJmNVIPP9jgFV5sXIACGfdcFq1tKd7xnlb74/)
|
||||||
|
|
||||||
|
Please note if automated tests already exist.
|
||||||
|
|
||||||
|
When adding new automated tests, please keep [testing levels](https://docs.gitlab.com/ce/development/testing_guide/testing_levels.html)
|
||||||
|
in mind.
|
||||||
|
-->
|
||||||
|
|
||||||
|
/label ~Quality
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!--See the general Documentation guidelines https://docs.gitlab.com/ee/development/documentation/ -->
|
||||||
|
|
||||||
|
<!-- Use this description template for changing documentation location. For new docs or updates to existing docs, use the "Documentation" template -->
|
||||||
|
|
||||||
|
## What does this MR do?
|
||||||
|
|
||||||
|
<!-- Briefly describe what this MR is about -->
|
||||||
|
|
||||||
|
## Related issues
|
||||||
|
|
||||||
|
<!-- Mention the issue(s) this MR closes or is related to -->
|
||||||
|
|
||||||
|
Closes
|
||||||
|
|
||||||
|
## Moving docs to a new location?
|
||||||
|
|
||||||
|
Read the guidelines:
|
||||||
|
https://docs.gitlab.com/ce/development/documentation/index.html#changing-document-location
|
||||||
|
|
||||||
|
- [ ] Make sure the old link is not removed and has its contents replaced with
|
||||||
|
a link to the new location.
|
||||||
|
- [ ] Make sure internal links pointing to the document in question are not broken.
|
||||||
|
- [ ] Search and replace any links referring to old docs in GitLab Rails app,
|
||||||
|
specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
|
||||||
|
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ce/development/writing_documentation.html#redirections-for-pages-with-disqus-comments)
|
||||||
|
to the new document if there are any Disqus comments on the old document thread.
|
||||||
|
- [ ] Update the link in `features.yml` (if applicable)
|
||||||
|
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
|
||||||
|
with the changes as well (https://docs.gitlab.com/ce/development/writing_documentation.html#cherry-picking-from-ce-to-ee).
|
||||||
|
- [ ] Ping one of the technical writers for review.
|
||||||
|
|
||||||
|
/label ~Documentation
|
|
@ -1,4 +1,8 @@
|
||||||
<!--See the general Documentation guidelines https://docs.gitlab.com/ee/development/documentation/index.html -->
|
<!--See the general documentation guidelines https://docs.gitlab.com/ee/development/documentation -->
|
||||||
|
|
||||||
|
<!-- Mention "documentation" or "docs" in the MR title -->
|
||||||
|
|
||||||
|
<!-- Use this description template for new docs or updates to existing docs. For changing documentation location use the "Change documentation location" template -->
|
||||||
|
|
||||||
## What does this MR do?
|
## What does this MR do?
|
||||||
|
|
||||||
|
@ -10,20 +14,19 @@
|
||||||
|
|
||||||
Closes
|
Closes
|
||||||
|
|
||||||
## Moving docs to a new location?
|
## Author's checklist
|
||||||
|
|
||||||
Read the guidelines:
|
- [ ] [Apply the correct labels and milestone](https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process)
|
||||||
https://docs.gitlab.com/ee/development/documentation/#changing-document-location
|
- [ ] Crosslink the document from the higher-level index
|
||||||
|
- [ ] Crosslink the document from other subject-related docs
|
||||||
|
- [ ] Correctly apply the product [badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) and [tiers](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers)
|
||||||
|
- [ ] [Port the MR to EE (or backport from CE)](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee): _always recommended, required when the `ee-compat-check` job fails_
|
||||||
|
|
||||||
- [ ] Make sure the old link is not removed and has its contents replaced with
|
## Review checklist
|
||||||
a link to the new location.
|
|
||||||
- [ ] Make sure internal links pointing to the document in question are not broken.
|
- [ ] Your team's review (required)
|
||||||
- [ ] Search and replace any links referring to the old docs in the GitLab Rails app,
|
- [ ] PM's review (recommended, but not a blocker)
|
||||||
specifically under the `app/views/` and `ee/app/views` (for GitLab EE) directories.
|
- [ ] Technical writer's review (required)
|
||||||
- [ ] Make sure to add [`redirect_from`](https://docs.gitlab.com/ee/development/documentation/index.html#redirections-for-pages-with-disqus-comments)
|
- [ ] Merge the EE-MR first, CE-MR afterwards
|
||||||
to the new document if there are any Disqus comments on the old document thread.
|
|
||||||
- [ ] If working on CE and the `ee-compat-check` jobs fails, [submit an MR to EE
|
|
||||||
with the changes](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee) as well.
|
|
||||||
- [ ] Ping one of the technical writers for review.
|
|
||||||
|
|
||||||
/label ~Documentation
|
/label ~Documentation
|
||||||
|
|
|
@ -70,14 +70,15 @@ linters:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
RuboCop:
|
RuboCop:
|
||||||
enabled: false
|
enabled: true
|
||||||
# These cops are incredibly noisy when it comes to HAML templates, so we
|
# These cops are incredibly noisy when it comes to HAML templates, so we
|
||||||
# ignore them.
|
# ignore them.
|
||||||
ignored_cops:
|
ignored_cops:
|
||||||
- Lint/BlockAlignment
|
- Layout/BlockAlignment
|
||||||
- Lint/EndAlignment
|
- Layout/EndAlignment
|
||||||
- Lint/Void
|
- Lint/Void
|
||||||
- Metrics/LineLength
|
- Metrics/LineLength
|
||||||
|
- Naming/FileName
|
||||||
- Style/AlignParameters
|
- Style/AlignParameters
|
||||||
- Style/BlockNesting
|
- Style/BlockNesting
|
||||||
- Style/ElseAlignment
|
- Style/ElseAlignment
|
||||||
|
@ -91,6 +92,51 @@ linters:
|
||||||
- Style/TrailingWhitespace
|
- Style/TrailingWhitespace
|
||||||
- Style/WhileUntilModifier
|
- Style/WhileUntilModifier
|
||||||
|
|
||||||
|
# These cops should eventually get enabled
|
||||||
|
- Cop/LineBreakAfterGuardClauses
|
||||||
|
- Cop/LineBreakAroundConditionalBlock
|
||||||
|
- Cop/ProjectPathHelper
|
||||||
|
- GitlabSecurity/PublicSend
|
||||||
|
- Layout/LeadingCommentSpace
|
||||||
|
- Layout/SpaceAfterColon
|
||||||
|
- Layout/SpaceAfterComma
|
||||||
|
- Layout/SpaceAroundOperators
|
||||||
|
- Layout/SpaceBeforeBlockBraces
|
||||||
|
- Layout/SpaceBeforeComma
|
||||||
|
- Layout/SpaceBeforeFirstArg
|
||||||
|
- Layout/SpaceInsideArrayLiteralBrackets
|
||||||
|
- Layout/SpaceInsideHashLiteralBraces
|
||||||
|
- Layout/SpaceInsideStringInterpolation
|
||||||
|
- Layout/TrailingBlankLines
|
||||||
|
- Lint/BooleanSymbol
|
||||||
|
- Lint/LiteralInInterpolation
|
||||||
|
- Lint/ParenthesesAsGroupedExpression
|
||||||
|
- Lint/RedundantWithIndex
|
||||||
|
- Lint/Syntax
|
||||||
|
- Metrics/BlockNesting
|
||||||
|
- Naming/VariableName
|
||||||
|
- Performance/RedundantMatch
|
||||||
|
- Performance/StringReplacement
|
||||||
|
- Rails/Presence
|
||||||
|
- Rails/RequestReferer
|
||||||
|
- Style/AndOr
|
||||||
|
- Style/ColonMethodCall
|
||||||
|
- Style/ConditionalAssignment
|
||||||
|
- Style/HashSyntax
|
||||||
|
- Style/IdenticalConditionalBranches
|
||||||
|
- Style/NegatedIf
|
||||||
|
- Style/NestedTernaryOperator
|
||||||
|
- Style/Not
|
||||||
|
- Style/ParenthesesAroundCondition
|
||||||
|
- Style/RedundantParentheses
|
||||||
|
- Style/SelfAssignment
|
||||||
|
- Style/Semicolon
|
||||||
|
- Style/TernaryParentheses
|
||||||
|
- Style/TrailingCommaInHashLiteral
|
||||||
|
- Style/UnlessElse
|
||||||
|
- Style/WordArray
|
||||||
|
- Style/ZeroLengthPredicate
|
||||||
|
|
||||||
RubyComments:
|
RubyComments:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,10 @@ Style/MutableConstant:
|
||||||
- 'ee/db/post_migrate/**/*'
|
- 'ee/db/post_migrate/**/*'
|
||||||
- 'ee/db/geo/migrate/**/*'
|
- 'ee/db/geo/migrate/**/*'
|
||||||
|
|
||||||
|
# TODO: Move this to gitlab-styles
|
||||||
|
Style/SafeNavigation:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Naming/FileName:
|
Naming/FileName:
|
||||||
ExpectMatchingDefinition: true
|
ExpectMatchingDefinition: true
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -44,6 +48,8 @@ Naming/FileName:
|
||||||
- 'qa/bin/*'
|
- 'qa/bin/*'
|
||||||
- 'config/**/*'
|
- 'config/**/*'
|
||||||
- 'lib/generators/**/*'
|
- 'lib/generators/**/*'
|
||||||
|
- 'locale/unfound_translations.rb'
|
||||||
|
- 'ee/locale/unfound_translations.rb'
|
||||||
- 'ee/lib/generators/**/*'
|
- 'ee/lib/generators/**/*'
|
||||||
IgnoreExecutableScripts: true
|
IgnoreExecutableScripts: true
|
||||||
AllowedAcronyms:
|
AllowedAcronyms:
|
||||||
|
|
|
@ -706,12 +706,6 @@ Style/RescueModifier:
|
||||||
Style/RescueStandardError:
|
Style/RescueStandardError:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
# Offense count: 92
|
|
||||||
# Cop supports --auto-correct.
|
|
||||||
# Configuration parameters: ConvertCodeThatCanStartToReturnNil.
|
|
||||||
Style/SafeNavigation:
|
|
||||||
Enabled: false
|
|
||||||
|
|
||||||
# Offense count: 8
|
# Offense count: 8
|
||||||
# Cop supports --auto-correct.
|
# Cop supports --auto-correct.
|
||||||
Style/SelfAssignment:
|
Style/SelfAssignment:
|
||||||
|
|
339
CHANGELOG.md
|
@ -2,38 +2,76 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
## 11.2.8 (2018-10-31)
|
## 11.3.10 (2018-11-18)
|
||||||
|
|
||||||
|
### Security (1 change)
|
||||||
|
|
||||||
|
- Escape user fullname while rendering autocomplete template to prevent XSS.
|
||||||
|
|
||||||
|
|
||||||
|
## 11.3.9 (2018-10-31)
|
||||||
|
|
||||||
### Security (1 change)
|
### Security (1 change)
|
||||||
|
|
||||||
- Monkey kubeclient to not follow any redirects.
|
- Monkey kubeclient to not follow any redirects.
|
||||||
|
|
||||||
|
|
||||||
## 11.2.7 (2018-10-27)
|
## 11.3.8 (2018-10-27)
|
||||||
|
|
||||||
- No changes.
|
- No changes.
|
||||||
|
|
||||||
## 11.2.6 (2018-10-26)
|
## 11.3.7 (2018-10-26)
|
||||||
|
|
||||||
### Security (5 changes)
|
### Security (6 changes)
|
||||||
|
|
||||||
- Escape entity title while autocomplete template rendering to prevent XSS. !2558
|
- Escape entity title while autocomplete template rendering to prevent XSS. !2557
|
||||||
|
- Persist only SHA digest of PersonalAccessToken#token.
|
||||||
- Fix XSS in merge request source branch name.
|
- Fix XSS in merge request source branch name.
|
||||||
- Redact personal tokens in unsubscribe links.
|
- Redact personal tokens in unsubscribe links.
|
||||||
- Persist only SHA digest of PersonalAccessToken#token.
|
|
||||||
- Prevent SSRF attacks in HipChat integration.
|
- Prevent SSRF attacks in HipChat integration.
|
||||||
|
- Validate Wiki attachments are valid temporary files.
|
||||||
|
|
||||||
|
|
||||||
## 11.2.5 (2018-10-05)
|
## 11.3.6 (2018-10-17)
|
||||||
|
|
||||||
|
- No changes.
|
||||||
|
|
||||||
|
## 11.3.5 (2018-10-15)
|
||||||
|
|
||||||
|
### Fixed (2 changes)
|
||||||
|
|
||||||
|
- Fix loading issue on some merge request discussion. !21982
|
||||||
|
- Fix project deletion when there is a export available. !22276
|
||||||
|
|
||||||
|
|
||||||
|
## 11.3.4 (2018-10-05)
|
||||||
|
|
||||||
### Security (3 changes)
|
### Security (3 changes)
|
||||||
|
|
||||||
- Filter user sensitive data from discussions JSON. !2538
|
- Filter user sensitive data from discussions JSON. !2537
|
||||||
- Properly filter private references from system notes.
|
- Properly filter private references from system notes.
|
||||||
- Markdown API no longer displays confidential title references unless authorized.
|
- Markdown API no longer displays confidential title references unless authorized.
|
||||||
|
|
||||||
|
|
||||||
## 11.2.4 (2018-09-26)
|
## 11.3.3 (2018-10-04)
|
||||||
|
|
||||||
|
- No changes.
|
||||||
|
|
||||||
|
## 11.3.2 (2018-10-03)
|
||||||
|
|
||||||
|
### Fixed (4 changes)
|
||||||
|
|
||||||
|
- Fix NULL pipeline import problem and pipeline user mapping issue. !21875
|
||||||
|
- Fix migration to avoid an exception during upgrade. !22055
|
||||||
|
- Fixes admin runners table not wrapping content.
|
||||||
|
- Fix Error 500 when forking projects with Gravatar disabled.
|
||||||
|
|
||||||
|
### Other (1 change)
|
||||||
|
|
||||||
|
- Removes the 'required' attribute from the 'project name' field. !21770
|
||||||
|
|
||||||
|
|
||||||
|
## 11.3.1 (2018-09-26)
|
||||||
|
|
||||||
### Security (6 changes)
|
### Security (6 changes)
|
||||||
|
|
||||||
|
@ -45,9 +83,258 @@ entry.
|
||||||
- Block loopback addresses in UrlBlocker.
|
- Block loopback addresses in UrlBlocker.
|
||||||
|
|
||||||
|
|
||||||
|
## 11.3.0 (2018-09-22)
|
||||||
|
|
||||||
|
### Security (5 changes, 1 of them is from the community)
|
||||||
|
|
||||||
|
- Disable the Sidekiq Admin Rack session. !21441
|
||||||
|
- Set issuable_sort, diff_view, and perf_bar_enabled cookies to secure when possible. !21442
|
||||||
|
- Update rubyzip to 1.2.2 (CVE-2018-1000544). !21460 (Takuya Noguchi)
|
||||||
|
- Fixed persistent XSS rendering/escaping of diff location lines.
|
||||||
|
- Block link-local addresses in URLBlocker.
|
||||||
|
|
||||||
|
### Removed (1 change)
|
||||||
|
|
||||||
|
- Remove Gemnasium service. !21185
|
||||||
|
|
||||||
|
### Fixed (83 changes, 24 of them are from the community)
|
||||||
|
|
||||||
|
- Hide PAT creation advice for HTTP clone if PAT exists. !18208 (George Thomas @thegeorgeous)
|
||||||
|
- Allow spaces in wiki markdown links when using CommonMark. !20417
|
||||||
|
- disable_statement_timeout no longer leak to other migrations. !20503
|
||||||
|
- Events API now requires the read_user or api scope. !20627 (Warren Parad)
|
||||||
|
- Fix If-Check the result that a function was executed several times. !20640 (Max Dicker)
|
||||||
|
- Add migration to cleanup internal_ids inconsistency. !20926
|
||||||
|
- Fix fallback logic for automatic MR title assignment. !20930 (Franz Liedke)
|
||||||
|
- Fixed bug when the project logo file is stored in LFS. !20948
|
||||||
|
- Fix buttons on the new file page wrapping outside of the container. !21015
|
||||||
|
- Solve tooltip appears under modal. !21017
|
||||||
|
- Fix Bitbucket Cloud importer omitting replies. !21076
|
||||||
|
- Fix pipeline fixture seeder. !21088
|
||||||
|
- Fix blocked user card style. !21095
|
||||||
|
- Fix empty merge requests not opening in the Web IDE. !21102
|
||||||
|
- Fix label list item container height when there is no label description. !21106
|
||||||
|
- Fixes input alignment in user admin form with errors. !21108 (Jacopo Beschi @jacopo-beschi)
|
||||||
|
- Rails5 fix specs duplicate key value violates unique constraint 'index_gpg_signatures_on_commit_sha'. !21119 (Jasper Maes)
|
||||||
|
- Add gitlab theme to spam logs pagination. !21145
|
||||||
|
- Split remembering sorting for issues and merge requests. !21153 (Jacopo Beschi @jacopo-beschi)
|
||||||
|
- Fix git submodule link for subgroup projects with relative path. !21154
|
||||||
|
- Fix: Project deletion may not log audit events during group deletion. !21162
|
||||||
|
- Fix 1px cutoff of emojis. !21180 (gfyoung)
|
||||||
|
- Auto-DevOps.gitlab-ci.yml: update glibc package to 2.28. !21191 (sgerrand)
|
||||||
|
- Show google icon in audit log. !21207 (Jan Beckmann)
|
||||||
|
- Fix bin/secpick error and security branch prefixing. !21210
|
||||||
|
- Importing a project no longer fails when visibility level holds a string value type. !21242
|
||||||
|
- Fix attachments not displaying inline with Google Cloud Storage. !21265
|
||||||
|
- Fix IDE issues with persistent banners. !21283
|
||||||
|
- Fix "Confidential comments" button not saving in project hooks. !21289
|
||||||
|
- Bump fog-google to 1.7.0 and google-api-client to 0.23.0. !21295
|
||||||
|
- Don't use arguments keyword in gettext script. !21296 (gfyoung)
|
||||||
|
- Fix breadcrumb link to issues on new issue page. !21305 (J.D. Bean)
|
||||||
|
- Show '< 1%' when percent value evaluated is less than 1 on Stacked Progress Bar. !21306
|
||||||
|
- API: Catch empty commit messages. !21322 (Robert Schilling)
|
||||||
|
- Fix SQL error when sorting 2FA-enabled users by name in admin area. !21324
|
||||||
|
- API: Catch empty code content for project snippets. !21325 (Robert Schilling)
|
||||||
|
- Avoid nil safe message. !21326 (Yi Siliang)
|
||||||
|
- Allow date parameters on Issues, Notes, and Discussions API for group owners. !21342 (Florent Dubois)
|
||||||
|
- Fix remote mirrors failing if Git remotes have not been added. !21351
|
||||||
|
- Removing a group no longer triggers hooks for project deletion twice. !21366
|
||||||
|
- Use slugs for default project path and sanitize names before import. !21367
|
||||||
|
- Vertically centres landscape avatars. !21371 (Vicary Archangel)
|
||||||
|
- Fix Web IDE unable to commit to same file twice. !21372
|
||||||
|
- Fix project transfer name validation issues causing a redirect loop. !21408
|
||||||
|
- Fix Error 500s due to encoding issues when Wiki hooks fire. !21414
|
||||||
|
- Rails 5: include opclasses in rails 5 schema dump. !21416 (Jasper Maes)
|
||||||
|
- Bump GitLab Pages to v1.1.0. !21419
|
||||||
|
- Fix links in RSS feed elements. !21424 (Marc Schwede)
|
||||||
|
- Allow gaps in multiseries metrics charts. !21427
|
||||||
|
- Auto-DevOps.gitlab-ci.yml: fix redeploying deleted app gives helm error. !21429
|
||||||
|
- Use sample data for push event when no commits created. !21440 (Takuya Noguchi)
|
||||||
|
- Fix importers not assigning a new default group. !21456
|
||||||
|
- Fix edge cases of JUnitParser. !21469
|
||||||
|
- Fix breadcrumb link to merge requests on new merge request page. !21502 (J.D. Bean)
|
||||||
|
- Handle database statement timeouts in usage ping. !21523
|
||||||
|
- Handles exception during file upload - replaces the stack trace with a small error message. !21528
|
||||||
|
- Fix closing issue default pattern. !21531 (Samuele Kaplun)
|
||||||
|
- Fix outdated discussions being shown on Merge Request Changes tab. !21543
|
||||||
|
- Remove orphaned label links. !21552
|
||||||
|
- Delete a container registry asynchronously. !21553
|
||||||
|
- Make MR diff file filter input Clear button functional. !21556
|
||||||
|
- Replace white spaces in wiki attachments file names. !21569
|
||||||
|
- API: Use find_branch! in all places. !21614 (Robert Schilling)
|
||||||
|
- Fixes double +/- on inline diff view. !21634
|
||||||
|
- Fix broken exports when they include a projet avatar. !21649
|
||||||
|
- Fix workhorse temp path for namespace uploads. !21650
|
||||||
|
- Fixed resolved discussions not toggling expanded state on changes tab. !21676
|
||||||
|
- Update GitLab Shell to v8.3.2. !21701
|
||||||
|
- Fix absent Click to Expand link on diffs not rendered on first load of Merge Requests Changes tab. !21716
|
||||||
|
- Update GitLab Shell to v8.3.3. !21750
|
||||||
|
- Fix import error when archive does not have the correct extension. !21765
|
||||||
|
- Fixed IDE deleting new files creating wrong state.
|
||||||
|
- Does not collapse runners section when using pagination.
|
||||||
|
- Fix Emojis cutting in the right way. (Alexander Popov)
|
||||||
|
- Fix NamespaceUploader.base_dir for remote uploads.
|
||||||
|
- Increase width of checkout branch modal box.
|
||||||
|
- Fixes SVGs for empty states in job page overflowing on mobile.
|
||||||
|
- Fix checkboxes on runner admin settings - The labels are now clickable.
|
||||||
|
- Fixed IDE file row scrolling into view when hovering.
|
||||||
|
- Accept upload files in public/uplaods/tmp when using accelerated uploads.
|
||||||
|
- Include correct CSS file for xterm in environments page.
|
||||||
|
- Increase padding in code blocks.
|
||||||
|
- Fix: Project deletion may not log audit events during user deletion.
|
||||||
|
|
||||||
|
### Changed (32 changes, 5 of them are from the community)
|
||||||
|
|
||||||
|
- Add default avatar to group. !17271 (George Tsiolis)
|
||||||
|
- Allow project owners to set up forking relation through API. !18104
|
||||||
|
- Limit navbar search for current project or group for small viewports. !18634 (George Tsiolis)
|
||||||
|
- Add Noto Color Emoji font support. !19036 (Alexander Popov)
|
||||||
|
- Update design of project overview page. !20536
|
||||||
|
- Improve visuals of language bar on projects. !21006
|
||||||
|
- Migrate NULL wiki_access_level to correct number so we count active wikis correctly. !21030
|
||||||
|
- Support a custom action, such as proxying to another server, after /api/v4/internal/allowed check succeeds. !21034
|
||||||
|
- Remove storage path dependency of gitaly install task. !21101
|
||||||
|
- Support Kubernetes RBAC for GitLab Managed Apps when adding a existing cluster. !21127
|
||||||
|
- Change 'Backlog' list title to 'Open' in Issue Boards. !21131
|
||||||
|
- Enable Auto DevOps Instance Wide Default. !21157
|
||||||
|
- Allow author to vote on their own issue and MRs. !21203
|
||||||
|
- Truncate branch names and update "commits behind" text in MR page. !21206
|
||||||
|
- Adds count for different board list types (label lists, assignee lists, and milestone lists) to usage statistics. !21208
|
||||||
|
- Render files (`.md`) and wikis using CommonMark. !21228
|
||||||
|
- Show deprecation message on project milestone page for category tabs. !21236
|
||||||
|
- Remove redundant header from metrics page. !21282
|
||||||
|
- Add default parameter to branches API. !21294 (Riccardo Padovani)
|
||||||
|
- Restrict reopening locked issues for non authorized issue authors. !21299
|
||||||
|
- Send back required object storage PUT headers in /uploads/authorize API. !21319
|
||||||
|
- Display default status emoji if only message is entered. !21330
|
||||||
|
- Move badge settings to general settings. !21333
|
||||||
|
- Move project settings for default branch under "Repository". !21380
|
||||||
|
- Import all common metrics into database. !21459
|
||||||
|
- Improved commit panel in Web IDE. !21471
|
||||||
|
- Administrative cleanup rake tasks now leverage Gitaly. !21588
|
||||||
|
- Remove health check feature flag in BackgroundMigrationWorker.
|
||||||
|
- Expose user's id in /admin/users/ show page. (Eva Kadlecova)
|
||||||
|
- Improved styling of top bar in IDE job trace pane.
|
||||||
|
- Make terminal button more visible.
|
||||||
|
- Shows download artifacts button for pipelines on small screens.
|
||||||
|
|
||||||
|
### Performance (13 changes, 2 of them are from the community)
|
||||||
|
|
||||||
|
- Enable frozen string in rest of app/models/**/*.rb.
|
||||||
|
- Add background migrations for legacy artifacts. !18615
|
||||||
|
- Optimize querying User#manageable_groups. !21050
|
||||||
|
- Incremental rendering with Vue on merge request page. !21063
|
||||||
|
- Remove redundant ci_builds (status) index. !21070
|
||||||
|
- Enable frozen in app/mailers/**/*.rb. !21147 (gfyoung)
|
||||||
|
- Improve performance when fetching related merge requests for an issue. !21237
|
||||||
|
- Speed up diff comparisons by limiting number of commit messages rendered. !21335
|
||||||
|
- Write diff highlighting cache upon MR creation (refactors caching). !21489
|
||||||
|
- Bulk-render commit titles in the tree view to improve performance. !21500
|
||||||
|
- Enable frozen string in vestigial app files. (gfyoung)
|
||||||
|
- Disable project avatar validation if avatar has not changed.
|
||||||
|
- Bitbucket Server importer: Eliminate most idle-in-transaction issues.
|
||||||
|
|
||||||
|
### Added (41 changes, 17 of them are from the community)
|
||||||
|
|
||||||
|
- API: Protected tags. !14986 (Robert Schilling)
|
||||||
|
- Include private contributions to contributions calendar. !17296 (George Tsiolis)
|
||||||
|
- Add an option to whitelist users based on email address as internal when the "New user set to external" setting is enabled. !17711 (Roger Rüttimann)
|
||||||
|
- Overhaul listing of projects in the group overview page. !20262
|
||||||
|
- Add the ability to reference projects in comments and other markdown text. !20285 (Reuben Pereira)
|
||||||
|
- Add branch filter to project webhooks. !20338 (Duana Saskia)
|
||||||
|
- Allows to cancel a Created job. !20635 (Jacopo Beschi @jacopo-beschi)
|
||||||
|
- First Improvements made to the contributor on-boarding experience. !20682 (Eddie Stubbington)
|
||||||
|
- `/tag` quick action on Commit comments. !20694 (Peter Leitzen)
|
||||||
|
- Allow admins to configure the maximum Git push size. !20758
|
||||||
|
- Expose all artifacts sizes in jobs api. !20821 (Peter Marko)
|
||||||
|
- Get the merge base of two refs through the API. !20929
|
||||||
|
- Add ability to suppress the global "You won't be able to use SSH" message. !21027 (Ævar Arnfjörð Bjarmason)
|
||||||
|
- API: Add expiration date for shared projects to the project entity. !21104 (Robert Schilling)
|
||||||
|
- Added tooltips to tree list header. !21138
|
||||||
|
- #47845 Add failure_reason to job webhook. !21143 (matemaciek)
|
||||||
|
- Vendor Auto-DevOps.gitlab-ci.yml with new proxy env vars passed through to docker. !21159 (kinolaev)
|
||||||
|
- Disable Auto DevOps for project upon first pipeline failure. !21172
|
||||||
|
- Add rake command to migrate archived traces from local storage to object storage. !21193
|
||||||
|
- Add Czech as an available language. !21201
|
||||||
|
- Add Galician as an available language. !21202
|
||||||
|
- Add support for extendable CI/CD config with. !21243
|
||||||
|
- Disable Web IDE button if user is not allowed to push the source branch. !21288
|
||||||
|
- Feature flag to disable Hashed Storage migration when renaming a repository. !21291
|
||||||
|
- Store wiki uploads inside git repository. !21362
|
||||||
|
- Adds Rubocop rule to enforce class_methods over module ClassMethods. !21379 (Jacopo Beschi @jacopo-beschi)
|
||||||
|
- Merge request copies all associated issue labels and milestone on creation. !21383
|
||||||
|
- Add group name badge under group milestone. !21384
|
||||||
|
- Adds diverged_commits_count field to GET api/v4/projects/:project_id/merge_requests/:merge_request_iid. !21405 (Jacopo Beschi @jacopo-beschi)
|
||||||
|
- Update Import/Export to only use new storage uploaders logic. !21409
|
||||||
|
- Ask user explicitly about usage stats agreement on single user deployments. !21423
|
||||||
|
- Added atom feed for tags. !21428
|
||||||
|
- Add search to a group labels page. !21480
|
||||||
|
- Display banner on project page if AutoDevOps is implicitly enabled. !21503
|
||||||
|
- Recognize 'UNLICENSE' license files. !21508 (J.D. Bean)
|
||||||
|
- Add git_v2 feature flag. !21520
|
||||||
|
- Added file templates to the Web IDE.
|
||||||
|
- Enabled multiple file uploads in the Web IDE.
|
||||||
|
- Allow to delete group milestones.
|
||||||
|
- Use separate model for tracking resource label changes and render label system notes based on data from this model.
|
||||||
|
- Add system note when due date is changed. (Eva Kadlecova)
|
||||||
|
|
||||||
|
### Other (48 changes, 16 of them are from the community)
|
||||||
|
|
||||||
|
- Remove extra spaces from MR discussion notes. !18946 (Takuya Noguchi)
|
||||||
|
- Add an example of the configuration of archive trace cron worker in gitlab.yml.example. !20583
|
||||||
|
- Add target branch name to cherrypick confirmation message. !20846 (George Andrinopoulos)
|
||||||
|
- CE Port of Protected Environments backend. !20859
|
||||||
|
- Added missing i18n strings to issue boards lables dropdown. !21081
|
||||||
|
- Combines emoji award spec files into single user_interacts_with_awards_in_issue_spec.rb file. !21126 (Nate Geslin)
|
||||||
|
- Clarify current runners online text. !21151 (Ben Bodenmiller)
|
||||||
|
- Rails5: Enable verbose query logs. !21231 (Jasper Maes)
|
||||||
|
- Update presentation for SSO providers on log in page. !21233
|
||||||
|
- Make margin of user status emoji consistent. !21268
|
||||||
|
- Move usage ping payload from User Cohorts page to admin application settings. !21343
|
||||||
|
- Add JSON logging for Bitbucket Server importer. !21378
|
||||||
|
- Re-add project name field on "Create new project" page. !21386
|
||||||
|
- Rails 5: replace removed silence_stream. !21387 (Jasper Maes)
|
||||||
|
- Rails5 update Gemfile.rails5.lock. !21388 (Jasper Maes)
|
||||||
|
- Rails5: fix can't quote ActiveSupport::HashWithIndifferentAccess. !21397 (Jasper Maes)
|
||||||
|
- Don't show flash messages for performance bar errors. !21411
|
||||||
|
- Backport schema_changed.sh from EE which prints the diff if the schema is different. !21422 (Jasper Maes)
|
||||||
|
- Remove unused CSS part in mobile framework. !21439 (Takuya Noguchi)
|
||||||
|
- Bump unauthenticated session time from 1 hour to 2 hours. !21453
|
||||||
|
- Run review-docs-cleanup job for gitlab-org repos only. !21463 (Takuya Noguchi)
|
||||||
|
- Rails 5: support schema t.index for mysql. !21485 (Jasper Maes)
|
||||||
|
- Add route information to lograge structured logging for API logs. !21487
|
||||||
|
- Add gitaly_calls attribute to API logs. !21496
|
||||||
|
- Ignore irrelevant sql commands in metrics. !21498
|
||||||
|
- Rails 5: fix hashed_path? method that looks up file_location that doesn't exist when running certain migration specs. !21510 (Jasper Maes)
|
||||||
|
- Explicit hashed path check for trace, prevents background migration from accessing file_location column that doesn't exist. !21533 (Jasper Maes)
|
||||||
|
- Add terminal_path to job API response. !21537
|
||||||
|
- Add User-Agent to production_json.log. !21546
|
||||||
|
- Make cluster page settings easier to read. !21550
|
||||||
|
- Remove striped table styling of Find files and Admin Area Applications views. !21560 (Andreas Kämmerle)
|
||||||
|
- Update ffi to 1.9.25. !21561 (Takuya Noguchi)
|
||||||
|
- Send max_patch_bytes to Gitaly via Gitaly::CommitDiffRequest. !21575
|
||||||
|
- Add margin between username and subsequent text in issuable header. !21697
|
||||||
|
- Send artifact information in job API. !50460
|
||||||
|
- Reduce differences between CE and EE code base in reports components.
|
||||||
|
- Move project services log to a separate file.
|
||||||
|
- Creates vue component for job log top bar with controllers.
|
||||||
|
- Creates Vue component for trigger variables block in job log page.
|
||||||
|
- Creates Vvue component for warning block about stuck runners.
|
||||||
|
- Creates vue component for job log trace.
|
||||||
|
- Creates vue component for erased block on job view.
|
||||||
|
- Creates vue component for environments information in job log view.
|
||||||
|
- Upgrade Monaco editor.
|
||||||
|
- Creates empty state vue component for job view.
|
||||||
|
- Creates vue component for commit block in job log page.
|
||||||
|
- Creates vue components for stage dropdowns and job list container for job log view.
|
||||||
|
- Creates Vue component for artifacts block on job page.
|
||||||
|
|
||||||
|
|
||||||
## 11.2.3 (2018-08-28)
|
## 11.2.3 (2018-08-28)
|
||||||
|
|
||||||
- No changes.
|
### Fixed (1 change)
|
||||||
|
|
||||||
|
- Fixed cache invalidation issue with diff lines from 11.2.2.
|
||||||
|
|
||||||
## 11.2.2 (2018-08-27)
|
## 11.2.2 (2018-08-27)
|
||||||
|
|
||||||
|
@ -310,6 +597,25 @@ entry.
|
||||||
- Moves help_popover component to a common location.
|
- Moves help_popover component to a common location.
|
||||||
|
|
||||||
|
|
||||||
|
## 11.1.6 (2018-08-28)
|
||||||
|
|
||||||
|
### Fixed (1 change)
|
||||||
|
|
||||||
|
- Fixed cache invalidation issue with diff lines from 11.2.2.
|
||||||
|
|
||||||
|
## 11.1.5 (2018-08-27)
|
||||||
|
|
||||||
|
### Security (3 changes)
|
||||||
|
|
||||||
|
- Fixed persistent XSS rendering/escaping of diff location lines.
|
||||||
|
- Adding CSRF protection to Hooks resend action.
|
||||||
|
- Block link-local addresses in URLBlocker.
|
||||||
|
|
||||||
|
### Fixed (1 change, 1 of them is from the community)
|
||||||
|
|
||||||
|
- Sanitize git URL in import errors. (Jamie Schembri)
|
||||||
|
|
||||||
|
|
||||||
## 11.1.4 (2018-07-30)
|
## 11.1.4 (2018-07-30)
|
||||||
|
|
||||||
### Fixed (4 changes, 1 of them is from the community)
|
### Fixed (4 changes, 1 of them is from the community)
|
||||||
|
@ -592,6 +898,19 @@ entry.
|
||||||
- Use monospaced font for MR diff commit link ref on GFM.
|
- Use monospaced font for MR diff commit link ref on GFM.
|
||||||
|
|
||||||
|
|
||||||
|
## 11.0.6 (2018-08-27)
|
||||||
|
|
||||||
|
### Security (3 changes)
|
||||||
|
|
||||||
|
- Fixed persistent XSS rendering/escaping of diff location lines.
|
||||||
|
- Adding CSRF protection to Hooks resend action.
|
||||||
|
- Block link-local addresses in URLBlocker.
|
||||||
|
|
||||||
|
### Fixed (1 change, 1 of them is from the community)
|
||||||
|
|
||||||
|
- Sanitize git URL in import errors. (Jamie Schembri)
|
||||||
|
|
||||||
|
|
||||||
## 11.0.5 (2018-07-26)
|
## 11.0.5 (2018-07-26)
|
||||||
|
|
||||||
### Security (4 changes)
|
### Security (4 changes)
|
||||||
|
|
817
CONTRIBUTING.md
|
@ -21,48 +21,54 @@ _This notice should stay as the first item in the CONTRIBUTING.md file._
|
||||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||||
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
|
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
|
||||||
|
|
||||||
|
- [Contributing Documentation has been moved](#contributing-documentation-has-been-moved)
|
||||||
- [Contribute to GitLab](#contribute-to-gitlab)
|
- [Contribute to GitLab](#contribute-to-gitlab)
|
||||||
- [Security vulnerability disclosure](#security-vulnerability-disclosure)
|
- [Security vulnerability disclosure](#security-vulnerability-disclosure)
|
||||||
|
- [Code of conduct](#code-of-conduct)
|
||||||
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
|
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
|
||||||
- [Helping others](#helping-others)
|
- [Helping others](#helping-others)
|
||||||
- [I want to contribute!](#i-want-to-contribute)
|
- [I want to contribute!](#i-want-to-contribute)
|
||||||
|
- [Contribution Flow](#contribution-flow)
|
||||||
- [Workflow labels](#workflow-labels)
|
- [Workflow labels](#workflow-labels)
|
||||||
- [Type labels](#type-labels)
|
- [Type labels](#type-labels)
|
||||||
- [Subject labels](#subject-labels)
|
- [Subject labels](#subject-labels)
|
||||||
- [Team labels](#team-labels)
|
- [Team labels](#team-labels)
|
||||||
- [Release Scoping labels](#release-scoping-labels)
|
- [Release Scoping labels](#release-scoping-labels)
|
||||||
- [Bug Priority labels](#bug-priority-labels)
|
- [Priority labels](#priority-labels)
|
||||||
- [Bug Severity labels](#bug-severity-labels)
|
- [Severity labels](#severity-labels)
|
||||||
- [Severity impact guidance](#severity-impact-guidance)
|
- [Severity impact guidance](#severity-impact-guidance)
|
||||||
- [Label for community contributors](#label-for-community-contributors)
|
- [Label for community contributors](#label-for-community-contributors)
|
||||||
- [Implement design & UI elements](#implement-design-ui-elements)
|
- [Implement design & UI elements](#implement-design--ui-elements)
|
||||||
- [Issue tracker](#issue-tracker)
|
- [Issue tracker](#issue-tracker)
|
||||||
- [Issue triaging](#issue-triaging)
|
- [Issue triaging](#issue-triaging)
|
||||||
- [Feature proposals](#feature-proposals)
|
- [Feature proposals](#feature-proposals)
|
||||||
- [Issue tracker guidelines](#issue-tracker-guidelines)
|
- [Issue tracker guidelines](#issue-tracker-guidelines)
|
||||||
- [Issue weight](#issue-weight)
|
- [Issue weight](#issue-weight)
|
||||||
- [Regression issues](#regression-issues)
|
- [Regression issues](#regression-issues)
|
||||||
- [Technical and UX debt](#technical-and-ux-debt)
|
- [Technical and UX debt](#technical-and-ux-debt)
|
||||||
- [Stewardship](#stewardship)
|
- [Stewardship](#stewardship)
|
||||||
- [Merge requests](#merge-requests)
|
- [Merge requests](#merge-requests)
|
||||||
- [Merge request guidelines](#merge-request-guidelines)
|
- [Merge request guidelines](#merge-request-guidelines)
|
||||||
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
|
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
|
||||||
- [Definition of done](#definition-of-done)
|
- [Definition of done](#definition-of-done)
|
||||||
- [Style guides](#style-guides)
|
- [Style guides](#style-guides)
|
||||||
- [Code of conduct](#code-of-conduct)
|
|
||||||
- [Contribution Flow](#contribution-flow)
|
|
||||||
|
|
||||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Contributing Documentation has been moved
|
||||||
|
|
||||||
|
As of July 2018, all the documentation for contributing to the GitLab project has been moved to a new location.
|
||||||
|
[view the new documentation](doc/development/contributing/index.md) to find the latest information.
|
||||||
|
|
||||||
## Contribute to GitLab
|
## Contribute to GitLab
|
||||||
|
|
||||||
For a first-time step-by-step guide to the contribution process, see
|
|
||||||
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
|
|
||||||
|
|
||||||
Thank you for your interest in contributing to GitLab. This guide details how
|
Thank you for your interest in contributing to GitLab. This guide details how
|
||||||
to contribute to GitLab in a way that is efficient for everyone.
|
to contribute to GitLab in a way that is easy for everyone.
|
||||||
|
|
||||||
|
For a first-time step-by-step guide to the contribution process, please see
|
||||||
|
["Contributing to GitLab"](https://about.gitlab.com/contributing/).
|
||||||
|
|
||||||
Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute).
|
Looking for something to work on? Look for issues with the label [Accepting Merge Requests](#i-want-to-contribute).
|
||||||
|
|
||||||
|
@ -71,10 +77,10 @@ source edition, and GitLab Enterprise Edition (EE) which is our commercial
|
||||||
edition. Throughout this guide you will see references to CE and EE for
|
edition. Throughout this guide you will see references to CE and EE for
|
||||||
abbreviation.
|
abbreviation.
|
||||||
|
|
||||||
If you have read this guide and want to know how the GitLab [core team]
|
If you want to know how the GitLab [core team]
|
||||||
operates please see [the GitLab contributing process](PROCESS.md).
|
operates please see [the GitLab contributing process](PROCESS.md).
|
||||||
|
|
||||||
- [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/)
|
||||||
|
|
||||||
## Security vulnerability disclosure
|
## Security vulnerability disclosure
|
||||||
|
|
||||||
|
@ -84,6 +90,36 @@ Please report suspected security vulnerabilities in private to
|
||||||
Please do **NOT** create publicly viewable issues for suspected security
|
Please do **NOT** create publicly viewable issues for suspected security
|
||||||
vulnerabilities.
|
vulnerabilities.
|
||||||
|
|
||||||
|
## Code of conduct
|
||||||
|
|
||||||
|
As contributors and maintainers of this project, we pledge to respect all
|
||||||
|
people who contribute through reporting issues, posting feature requests,
|
||||||
|
updating documentation, submitting pull requests or patches, and other
|
||||||
|
activities.
|
||||||
|
|
||||||
|
We are committed to making participation in this project a harassment-free
|
||||||
|
experience for everyone, regardless of level of experience, gender, gender
|
||||||
|
identity and expression, sexual orientation, disability, personal appearance,
|
||||||
|
body size, race, ethnicity, age, or religion.
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include the use of sexual
|
||||||
|
language or imagery, derogatory comments or personal attacks, trolling, public
|
||||||
|
or private harassment, insults, or other unprofessional conduct.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct. Project maintainers who do not
|
||||||
|
follow the Code of Conduct may be removed from the project team.
|
||||||
|
|
||||||
|
This code of conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community.
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior can be
|
||||||
|
reported by emailing `contact@gitlab.com`.
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
|
||||||
|
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
|
||||||
|
|
||||||
## Closing policy for issues and merge requests
|
## Closing policy for issues and merge requests
|
||||||
|
|
||||||
GitLab is a popular open source project and the capacity to deal with issues
|
GitLab is a popular open source project and the capacity to deal with issues
|
||||||
|
@ -123,623 +159,6 @@ learn how to communicate with GitLab. If you're looking for a Gitter or Slack ch
|
||||||
please consider we favor
|
please consider we favor
|
||||||
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
|
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
|
||||||
|
|
||||||
## Workflow labels
|
|
||||||
|
|
||||||
To allow for asynchronous issue handling, we use [milestones][milestones-page]
|
|
||||||
and [labels][labels-page]. Leads and product managers handle most of the
|
|
||||||
scheduling into milestones. Labelling is a task for everyone.
|
|
||||||
|
|
||||||
Most issues will have labels for at least one of the following:
|
|
||||||
|
|
||||||
- Type: ~"feature proposal", ~bug, ~customer, etc.
|
|
||||||
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
|
|
||||||
- Team: ~"CI/CD", ~Plan, ~Manage, ~Quality, etc.
|
|
||||||
- Release Scoping: ~Deliverable, ~Stretch, ~"Next Patch Release"
|
|
||||||
- Priority: ~P1, ~P2, ~P3, ~P4
|
|
||||||
- Severity: ~S1, ~S2, ~S3, ~S4
|
|
||||||
|
|
||||||
All labels, their meaning and priority are defined on the
|
|
||||||
[labels page][labels-page].
|
|
||||||
|
|
||||||
If you come across an issue that has none of these, and you're allowed to set
|
|
||||||
labels, you can _always_ add the team and type, and often also the subject.
|
|
||||||
|
|
||||||
[milestones-page]: https://gitlab.com/gitlab-org/gitlab-ce/milestones
|
|
||||||
[labels-page]: https://gitlab.com/gitlab-org/gitlab-ce/labels
|
|
||||||
|
|
||||||
### Type labels
|
|
||||||
|
|
||||||
Type labels are very important. They define what kind of issue this is. Every
|
|
||||||
issue should have one or more.
|
|
||||||
|
|
||||||
Examples of type labels are ~"feature proposal", ~bug, ~customer, ~security,
|
|
||||||
and ~"direction".
|
|
||||||
|
|
||||||
A number of type labels have a priority assigned to them, which automatically
|
|
||||||
makes them float to the top, depending on their importance.
|
|
||||||
|
|
||||||
Type labels are always lowercase, and can have any color, besides blue (which is
|
|
||||||
already reserved for subject labels).
|
|
||||||
|
|
||||||
The descriptions on the [labels page][labels-page] explain what falls under each type label.
|
|
||||||
|
|
||||||
### Subject labels
|
|
||||||
|
|
||||||
Subject labels are labels that define what area or feature of GitLab this issue
|
|
||||||
hits. They are not always necessary, but very convenient.
|
|
||||||
|
|
||||||
Examples of subject labels are ~wiki, ~ldap, ~api,
|
|
||||||
~issues, ~"merge requests", ~labels, and ~"container registry".
|
|
||||||
|
|
||||||
If you are an expert in a particular area, it makes it easier to find issues to
|
|
||||||
work on. You can also subscribe to those labels to receive an email each time an
|
|
||||||
issue is labeled with a subject label corresponding to your expertise.
|
|
||||||
|
|
||||||
Subject labels are always all-lowercase.
|
|
||||||
|
|
||||||
### Team labels
|
|
||||||
|
|
||||||
Team labels specify what team is responsible for this issue.
|
|
||||||
Assigning a team label makes sure issues get the attention of the appropriate
|
|
||||||
people.
|
|
||||||
|
|
||||||
The current team labels are:
|
|
||||||
|
|
||||||
- ~Configuration
|
|
||||||
- ~"CI/CD"
|
|
||||||
- ~Create
|
|
||||||
- ~Distribution
|
|
||||||
- ~Documentation
|
|
||||||
- ~Geo
|
|
||||||
- ~Gitaly
|
|
||||||
- ~Manage
|
|
||||||
- ~Monitoring
|
|
||||||
- ~Plan
|
|
||||||
- ~Quality
|
|
||||||
- ~Release
|
|
||||||
- ~Secure
|
|
||||||
- ~UX
|
|
||||||
|
|
||||||
The descriptions on the [labels page][labels-page] explain what falls under the
|
|
||||||
responsibility of each team.
|
|
||||||
|
|
||||||
Within those team labels, we also have the ~backend and ~frontend labels to
|
|
||||||
indicate if an issue needs backend work, frontend work, or both.
|
|
||||||
|
|
||||||
Team labels are always capitalized so that they show up as the first label for
|
|
||||||
any issue.
|
|
||||||
|
|
||||||
### Release Scoping labels
|
|
||||||
|
|
||||||
Release Scoping labels help us clearly communicate expectations of the work for the
|
|
||||||
release. There are three levels of Release Scoping labels:
|
|
||||||
|
|
||||||
- ~Deliverable: Issues that are expected to be delivered in the current
|
|
||||||
milestone.
|
|
||||||
- ~Stretch: Issues that are a stretch goal for delivering in the current
|
|
||||||
milestone. If these issues are not done in the current release, they will
|
|
||||||
strongly be considered for the next release.
|
|
||||||
- ~"Next Patch Release": Issues to put in the next patch release. Work on these
|
|
||||||
first, and add the "Pick Into X" label to the merge request, along with the
|
|
||||||
appropriate milestone.
|
|
||||||
|
|
||||||
Each issue scheduled for the current milestone should be labeled ~Deliverable
|
|
||||||
or ~"Stretch". Any open issue for a previous milestone should be labeled
|
|
||||||
~"Next Patch Release", or otherwise rescheduled to a different milestone.
|
|
||||||
|
|
||||||
### Priority labels
|
|
||||||
|
|
||||||
Priority labels help us define the time a ~bug fix should be completed. Priority determines how quickly the defect turnaround time must be.
|
|
||||||
If there are multiple defects, the priority decides which defect has to be fixed immediately versus later.
|
|
||||||
This label documents the planned timeline & urgency which is used to measure against our actual SLA on delivering ~bug fixes.
|
|
||||||
|
|
||||||
| Label | Meaning | Estimate time to fix |
|
|
||||||
|-------|-----------------|------------------------------------------------------------------|
|
|
||||||
| ~P1 | Urgent Priority | The current release + potentially immediate hotfix to GitLab.com |
|
|
||||||
| ~P2 | High Priority | The next release |
|
|
||||||
| ~P3 | Medium Priority | Within the next 3 releases (approx one quarter) |
|
|
||||||
| ~P4 | Low Priority | Anything outside the next 3 releases (approx beyond one quarter) |
|
|
||||||
|
|
||||||
### Severity labels
|
|
||||||
|
|
||||||
Severity labels help us clearly communicate the impact of a ~bug on users.
|
|
||||||
|
|
||||||
| Label | Meaning | Impact on Functionality | Example |
|
|
||||||
|-------|-------------------|-------------------------------------------------------|---------|
|
|
||||||
| ~S1 | Blocker | Outage, broken feature with no workaround | Unable to create an issue. Data corruption/loss. Security breach. |
|
|
||||||
| ~S2 | Critical Severity | Broken Feature, workaround too complex & unacceptable | Can push commits, but only via the command line. |
|
|
||||||
| ~S3 | Major Severity | Broken Feature, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue. |
|
|
||||||
| ~S4 | Low Severity | Functionality inconvenience or cosmetic issue | Label colors are incorrect / not being displayed. |
|
|
||||||
|
|
||||||
#### Severity impact guidance
|
|
||||||
|
|
||||||
Severity levels can be applied further depending on the facet of the impact; e.g. Affected customers, GitLab.com availability, performance and etc. The below is a guideline.
|
|
||||||
|
|
||||||
| Severity | Affected Customers/Users | GitLab.com Availability | Performance Degradation |
|
|
||||||
|----------|---------------------------------------------------------------------|----------------------------------------------------|------------------------------|
|
|
||||||
| ~S1 | >50% users affected (possible company extinction level event) | Significant impact on all of GitLab.com | |
|
|
||||||
| ~S2 | Many users or multiple paid customers affected (but not apocalyptic)| Significant impact on large portions of GitLab.com | Degradation is guaranteed to occur in the near future |
|
|
||||||
| ~S3 | A few users or a single paid customer affected | Limited impact on important portions of GitLab.com | Degradation is likely to occur in the near future |
|
|
||||||
| ~S4 | No paid users/customer affected, or expected to in the near future | Minor impact on on GitLab.com | Degradation _may_ occur but it's not likely |
|
|
||||||
|
|
||||||
### Label for community contributors
|
|
||||||
|
|
||||||
Issues that are beneficial to our users, 'nice to haves', that we currently do
|
|
||||||
not have the capacity for or want to give the priority to, are labeled as
|
|
||||||
~"Accepting Merge Requests", so the community can make a contribution.
|
|
||||||
|
|
||||||
Community contributors can submit merge requests for any issue they want, but
|
|
||||||
the ~"Accepting Merge Requests" label has a special meaning. It points to
|
|
||||||
changes that:
|
|
||||||
|
|
||||||
1. We already agreed on,
|
|
||||||
1. Are well-defined,
|
|
||||||
1. Are likely to get accepted by a maintainer.
|
|
||||||
|
|
||||||
We want to avoid a situation when a contributor picks an
|
|
||||||
~"Accepting Merge Requests" issue and then their merge request gets closed,
|
|
||||||
because we realize that it does not fit our vision, or we want to solve it in a
|
|
||||||
different way.
|
|
||||||
|
|
||||||
We add the ~"Accepting Merge Requests" label to:
|
|
||||||
|
|
||||||
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
|
|
||||||
solve in the ~"Next Patch Release")
|
|
||||||
- Small ~"feature proposal"
|
|
||||||
- Small ~"technical debt" issues
|
|
||||||
|
|
||||||
After adding the ~"Accepting Merge Requests" label, we try to estimate the
|
|
||||||
[weight](#issue-weight) of the issue. We use issue weight to let contributors
|
|
||||||
know how difficult the issue is. Additionally:
|
|
||||||
|
|
||||||
- We advertise ["Accepting Merge Requests" issues with weight < 5][up-for-grabs]
|
|
||||||
as suitable for people that have never contributed to GitLab before on the
|
|
||||||
[Up For Grabs campaign](http://up-for-grabs.net)
|
|
||||||
- We encourage people that have never contributed to any open source project to
|
|
||||||
look for ["Accepting Merge Requests" issues with a weight of 1][firt-timers]
|
|
||||||
|
|
||||||
If you've decided that you would like to work on an issue, please @-mention
|
|
||||||
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
|
|
||||||
as soon as possible. The product manager will then pull in appropriate GitLab team
|
|
||||||
members to further discuss scope, design, and technical considerations. This will
|
|
||||||
ensure that that your contribution is aligned with the GitLab product and minimize
|
|
||||||
any rework and delay in getting it merged into master.
|
|
||||||
|
|
||||||
GitLab team members who apply the ~"Accepting Merge Requests" label to an issue
|
|
||||||
should update the issue description with a responsible product manager, inviting
|
|
||||||
any potential community contributor to @-mention per above.
|
|
||||||
|
|
||||||
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests&scope=all&sort=weight_asc&state=opened
|
|
||||||
[firt-timers]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=Accepting+Merge+Requests&scope=all&sort=upvotes_desc&state=opened&weight=1
|
|
||||||
|
|
||||||
## Implement design & UI elements
|
|
||||||
|
|
||||||
For guidance on UX implementation at GitLab, please refer to our [Design System](https://design.gitlab.com/).
|
|
||||||
|
|
||||||
The UX team uses labels to manage their workflow.
|
|
||||||
|
|
||||||
The ~"UX" label on an issue is a signal to the UX team that it will need UX attention.
|
|
||||||
To better understand the priority by which UX tackles issues, see the [UX section](https://about.gitlab.com/handbook/engineering/ux) of the handbook.
|
|
||||||
|
|
||||||
Once an issue has been worked on and is ready for development, a UXer removes the ~"UX" label and applies the ~"UX ready" label to that issue.
|
|
||||||
|
|
||||||
The UX team has a special type label called ~"design artifact". This label indicates that the final output
|
|
||||||
for an issue is a UX solution/design. The solution will be developed by frontend and/or backend in a subsequent milestone.
|
|
||||||
Any issue labeled ~"design artifact" should not also be labeled ~"frontend" or ~"backend" since no development is
|
|
||||||
needed until the solution has been decided.
|
|
||||||
|
|
||||||
~"design artifact" issues are like any other issue and should contain a milestone label, ~"Deliverable" or ~"Stretch", when scheduled in the current milestone.
|
|
||||||
|
|
||||||
To prevent the misunderstanding that a feature will be be delivered in the
|
|
||||||
assigned milestone, when only UX design is planned for that milestone, the
|
|
||||||
Product Manager should create a separate issue for the ~"design artifact",
|
|
||||||
assign the ~UX, ~"design artifact" and ~"Deliverable" labels, add a milestone
|
|
||||||
and use a title that makes it clear that the scheduled issue is design only
|
|
||||||
(e.g. `Design exploration for XYZ`).
|
|
||||||
|
|
||||||
When the ~"design artifact" issue has been completed, the UXer removes the ~UX
|
|
||||||
label, adds the ~"UX ready" label and closes the issue. This indicates the
|
|
||||||
design artifact is complete. The UXer will also copy the designs to related
|
|
||||||
issues for implementation in an upcoming milestone.
|
|
||||||
|
|
||||||
## Issue tracker
|
|
||||||
|
|
||||||
To get support for your particular problem please use the
|
|
||||||
[getting help channels](https://about.gitlab.com/getting-help/).
|
|
||||||
|
|
||||||
The [GitLab CE issue tracker on GitLab.com][ce-tracker] is for bugs concerning
|
|
||||||
the latest GitLab release and [feature proposals](#feature-proposals).
|
|
||||||
|
|
||||||
When submitting an issue please conform to the issue submission guidelines
|
|
||||||
listed below. Not all issues will be addressed and your issue is more likely to
|
|
||||||
be addressed if you submit a merge request which partially or fully solves
|
|
||||||
the issue.
|
|
||||||
|
|
||||||
If you're unsure where to post, post to the [mailing list][google-group] or
|
|
||||||
[Stack Overflow][stackoverflow] first. There are a lot of helpful GitLab users
|
|
||||||
there who may be able to help you quickly. If your particular issue turns out
|
|
||||||
to be a bug, it will find its way from there.
|
|
||||||
|
|
||||||
If it happens that you know the solution to an existing bug, please first
|
|
||||||
open the issue in order to keep track of it and then open the relevant merge
|
|
||||||
request that potentially fixes it.
|
|
||||||
|
|
||||||
### 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 the
|
|
||||||
[GitLab team][team]. If there is nobody mentioned with that expertise look in
|
|
||||||
the commit history for the affected files to find someone.
|
|
||||||
|
|
||||||
We also use [GitLab Triage] to automate some triaging policies. This is
|
|
||||||
currently setup as a [scheduled pipeline] running on [quality/triage-ops]
|
|
||||||
project.
|
|
||||||
|
|
||||||
[described in our handbook]: https://about.gitlab.com/handbook/engineering/issue-triage/
|
|
||||||
[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815
|
|
||||||
[GitLab Triage]: https://gitlab.com/gitlab-org/gitlab-triage
|
|
||||||
[scheduled pipeline]: https://gitlab.com/gitlab-org/quality/triage-ops/pipeline_schedules/10512/edit
|
|
||||||
[quality/triage-ops]: https://gitlab.com/gitlab-org/quality/triage-ops
|
|
||||||
|
|
||||||
### Feature proposals
|
|
||||||
|
|
||||||
To create a feature proposal for CE, open an issue on the
|
|
||||||
[issue tracker of CE][ce-tracker].
|
|
||||||
|
|
||||||
For feature proposals for EE, open an issue on the
|
|
||||||
[issue tracker of EE][ee-tracker].
|
|
||||||
|
|
||||||
In order to help track the feature proposals, we have created a
|
|
||||||
[`feature proposal`][fpl] label. For the time being, users that are not members
|
|
||||||
of the project cannot add labels. You can instead ask one of the [core team]
|
|
||||||
members to add the label ~"feature proposal" to the issue or add the following
|
|
||||||
code snippet right after your description in a new line: `~"feature proposal"`.
|
|
||||||
|
|
||||||
Please keep feature proposals as small and simple as possible, complex ones
|
|
||||||
might be edited to make them small and simple.
|
|
||||||
|
|
||||||
Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
|
|
||||||
|
|
||||||
For changes in the interface, it is helpful to include a mockup. Issues that add to, or change, the interface should
|
|
||||||
be given the ~"UX" label. This will allow the UX team to provide input and guidance. You may
|
|
||||||
need to ask one of the [core team] members to add the label, if you do not have permissions to do it by yourself.
|
|
||||||
|
|
||||||
If you want to create something yourself, consider opening an issue first to
|
|
||||||
discuss whether it is interesting to include this in GitLab.
|
|
||||||
|
|
||||||
### Issue tracker guidelines
|
|
||||||
|
|
||||||
**[Search the issue tracker][ce-tracker]** for similar entries before
|
|
||||||
submitting your own, there's a good chance somebody else had the same issue or
|
|
||||||
feature proposal. Show your support with an award emoji and/or join the
|
|
||||||
discussion.
|
|
||||||
|
|
||||||
Please submit bugs using the ['Bug' issue template](.gitlab/issue_templates/Bug.md) provided on the issue tracker.
|
|
||||||
The text in the parenthesis is there to help you with what to include. Omit it
|
|
||||||
when submitting the actual issue. You can copy-paste it and then edit as you
|
|
||||||
see fit.
|
|
||||||
|
|
||||||
### Issue weight
|
|
||||||
|
|
||||||
Issue weight allows us to get an idea of the amount of work required to solve
|
|
||||||
one or multiple issues. This makes it possible to schedule work more accurately.
|
|
||||||
|
|
||||||
You are encouraged to set the weight of any issue. Following the guidelines
|
|
||||||
below will make it easy to manage this, without unnecessary overhead.
|
|
||||||
|
|
||||||
1. Set weight for any issue at the earliest possible convenience
|
|
||||||
1. If you don't agree with a set weight, discuss with other developers until
|
|
||||||
consensus is reached about the weight
|
|
||||||
1. Issue weights are an abstract measurement of complexity of the issue. Do not
|
|
||||||
relate issue weight directly to time. This is called [anchoring](https://en.wikipedia.org/wiki/Anchoring)
|
|
||||||
and something you want to avoid.
|
|
||||||
1. Something that has a weight of 1 (or no weight) is really small and simple.
|
|
||||||
Something that is 9 is rewriting a large fundamental part of GitLab,
|
|
||||||
which might lead to many hard problems to solve. Changing some text in GitLab
|
|
||||||
is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
|
|
||||||
1. If something is very large, it should probably be split up in multiple
|
|
||||||
issues or chunks. You can simply not set the weight of a parent issue and set
|
|
||||||
weights to children issues.
|
|
||||||
|
|
||||||
### Regression issues
|
|
||||||
|
|
||||||
Every monthly release has a corresponding issue on the CE issue tracker to keep
|
|
||||||
track of functionality broken by that release and any fixes that need to be
|
|
||||||
included in a patch release (see [8.3 Regressions] as an example).
|
|
||||||
|
|
||||||
As outlined in the issue description, the intended workflow is to post one note
|
|
||||||
with a reference to an issue describing the regression, and then to update that
|
|
||||||
note with a reference to the merge request that fixes it as it becomes available.
|
|
||||||
|
|
||||||
If you're a contributor who doesn't have the required permissions to update
|
|
||||||
other users' notes, please post a new note with a reference to both the issue
|
|
||||||
and the merge request.
|
|
||||||
|
|
||||||
The release manager will [update the notes] in the regression issue as fixes are
|
|
||||||
addressed.
|
|
||||||
|
|
||||||
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
|
|
||||||
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
|
|
||||||
|
|
||||||
### Technical and UX debt
|
|
||||||
|
|
||||||
In order to track things that can be improved in GitLab's codebase,
|
|
||||||
we use the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
|
|
||||||
For user experience improvements, we use the ~"UX debt" label.
|
|
||||||
|
|
||||||
These labels should be added to issues that describe things that can be improved,
|
|
||||||
shortcuts that have been taken, features that need additional attention, and all
|
|
||||||
other things that have been left behind due to high velocity of development.
|
|
||||||
For example, code that needs refactoring should use the ~"technical debt" label,
|
|
||||||
user experience refinements should use the ~"UX debt" label.
|
|
||||||
|
|
||||||
Everyone can create an issue, though you may need to ask for adding a specific
|
|
||||||
label, if you do not have permissions to do it by yourself. Additional labels
|
|
||||||
can be combined with these labels, to make it easier to schedule
|
|
||||||
the improvements for a release.
|
|
||||||
|
|
||||||
Issues tagged with these labels have the same priority like issues
|
|
||||||
that describe a new feature to be introduced in GitLab, and should be scheduled
|
|
||||||
for a release by the appropriate person.
|
|
||||||
|
|
||||||
Make sure to mention the merge request that the ~"technical debt" issue or
|
|
||||||
~"UX debt" issue is associated with in the description of the issue.
|
|
||||||
|
|
||||||
### Stewardship
|
|
||||||
|
|
||||||
For issues related to the open source stewardship of GitLab,
|
|
||||||
there is the ~"stewardship" label.
|
|
||||||
|
|
||||||
This label is to be used for issues in which the stewardship of GitLab
|
|
||||||
is a topic of discussion. For instance if GitLab Inc. is planning to add
|
|
||||||
features from GitLab EE to GitLab CE, related issues would be labelled with
|
|
||||||
~"stewardship".
|
|
||||||
|
|
||||||
A recent example of this was the issue for
|
|
||||||
[bringing the time tracking API to GitLab CE][time-tracking-issue].
|
|
||||||
|
|
||||||
[time-tracking-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/25517#note_20019084
|
|
||||||
|
|
||||||
## Merge requests
|
|
||||||
|
|
||||||
We welcome merge requests with fixes and improvements to GitLab code, tests,
|
|
||||||
and/or documentation. The issues that are specifically suitable for
|
|
||||||
community contributions are listed with the label
|
|
||||||
[`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce]
|
|
||||||
and [EE][accepting-mrs-ee], but you are free to contribute to any other issue
|
|
||||||
you want.
|
|
||||||
|
|
||||||
Please note that if an issue is marked for the current milestone either before
|
|
||||||
or while you are working on it, a team member may take over the merge request
|
|
||||||
in order to ensure the work is finished before the release date.
|
|
||||||
|
|
||||||
If you want to add a new feature that is not labeled it is best to first create
|
|
||||||
a feedback issue (if there isn't one already) and leave a comment asking for it
|
|
||||||
to be marked as `Accepting Merge Requests`. Please include screenshots or
|
|
||||||
wireframes if the feature will also change the UI.
|
|
||||||
|
|
||||||
Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
|
|
||||||
|
|
||||||
If you are new to GitLab development (or web development in general), see the
|
|
||||||
[I want to contribute!](#i-want-to-contribute) section to get you started with
|
|
||||||
some potentially easy issues.
|
|
||||||
|
|
||||||
To start with GitLab development download the [GitLab Development Kit][gdk] and
|
|
||||||
see the [Development section](doc/development/README.md) for some guidelines.
|
|
||||||
|
|
||||||
### Merge request guidelines
|
|
||||||
|
|
||||||
If you can, please submit a merge request with the fix or improvements
|
|
||||||
including tests. If you don't know how to fix the issue but can write a test
|
|
||||||
that exposes the issue we will accept that as well. In general bug fixes that
|
|
||||||
include a regression test are merged quickly while new features without proper
|
|
||||||
tests are least likely to receive timely feedback. The workflow to make a merge
|
|
||||||
request is as follows:
|
|
||||||
|
|
||||||
1. Fork the project into your personal space on GitLab.com
|
|
||||||
1. Create a feature branch, branch away from `master`
|
|
||||||
1. Write [tests](https://docs.gitlab.com/ee/development/rake_tasks.html#run-tests) and code
|
|
||||||
1. [Generate a changelog entry with `bin/changelog`][changelog]
|
|
||||||
1. If you are writing documentation, make sure to follow the
|
|
||||||
[documentation guidelines][doc-guidelines]
|
|
||||||
1. If you have multiple commits please combine them into a few logically
|
|
||||||
organized commits by [squashing them][git-squash]
|
|
||||||
1. Push the commit(s) to your fork
|
|
||||||
1. Submit a merge request (MR) to the `master` branch
|
|
||||||
1. Your merge request needs at least 1 approval but feel free to require more.
|
|
||||||
For instance if you're touching backend and frontend code, it's a good idea
|
|
||||||
to require 2 approvals: 1 from a backend maintainer and 1 from a frontend
|
|
||||||
maintainer
|
|
||||||
1. You don't have to select any approvers, but you can if you really want
|
|
||||||
specific people to approve your merge request
|
|
||||||
1. The MR title should describe the change you want to make
|
|
||||||
1. The MR description should give a motive for your change and the method you
|
|
||||||
used to achieve it.
|
|
||||||
1. If you are contributing code, fill in the template already provided in the
|
|
||||||
"Description" field.
|
|
||||||
1. If you are contributing documentation, choose `Documentation` from the
|
|
||||||
"Choose a template" menu and fill in the template.
|
|
||||||
1. Mention the issue(s) your merge request solves, using the `Solves #XXX` or
|
|
||||||
`Closes #XXX` syntax to auto-close the issue(s) once the merge request will
|
|
||||||
be merged.
|
|
||||||
1. If you're allowed to, set a relevant milestone and labels
|
|
||||||
1. If the MR changes the UI it should include *Before* and *After* screenshots
|
|
||||||
1. If the MR changes CSS classes please include the list of affected pages,
|
|
||||||
`grep css-class ./app -R`
|
|
||||||
1. Be prepared to answer questions and incorporate feedback even if requests
|
|
||||||
for this arrive weeks or months after your MR submission
|
|
||||||
1. If a discussion has been addressed, select the "Resolve discussion" button
|
|
||||||
beneath it to mark it resolved.
|
|
||||||
1. If your MR touches code that executes shell commands, reads or opens files or
|
|
||||||
handles paths to files on disk, make sure it adheres to the
|
|
||||||
[shell command guidelines](doc/development/shell_commands.md)
|
|
||||||
1. If your code creates new files on disk please read the
|
|
||||||
[shared files guidelines](doc/development/shared_files.md).
|
|
||||||
1. When writing commit messages please follow
|
|
||||||
[these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
|
||||||
[guidelines](http://chris.beams.io/posts/git-commit/).
|
|
||||||
1. If your merge request adds one or more migrations, make sure to execute all
|
|
||||||
migrations on a fresh database before the MR is reviewed. If the review leads
|
|
||||||
to large changes in the MR, do this again once the review is complete.
|
|
||||||
1. For more complex migrations, write tests.
|
|
||||||
1. Merge requests **must** adhere to the [merge request performance
|
|
||||||
guidelines](doc/development/merge_request_performance_guidelines.md).
|
|
||||||
1. For tests that use Capybara or PhantomJS, see this [article on how
|
|
||||||
to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
|
|
||||||
|
|
||||||
Please keep the change in a single MR **as small as possible**. If you want to
|
|
||||||
contribute a large feature think very hard what the minimum viable change is.
|
|
||||||
Can you split the functionality? Can you only submit the backend/API code? Can
|
|
||||||
you start with a very simple UI? Can you do part of the refactor? The increased
|
|
||||||
reviewability of small MRs that leads to higher code quality is more important
|
|
||||||
to us than having a minimal commit log. The smaller an MR is the more likely it
|
|
||||||
is it will be merged (quickly). After that you can send more MRs to enhance it.
|
|
||||||
The ['How to get faster PR reviews' document of Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) also has some great points regarding this.
|
|
||||||
|
|
||||||
For examples of feedback on merge requests please look at already
|
|
||||||
[closed merge requests][closed-merge-requests]. If you would like quick feedback
|
|
||||||
on your merge request feel free to mention someone from the [core team] or one
|
|
||||||
of the [Merge request coaches][team].
|
|
||||||
Please ensure that your merge request meets the contribution acceptance criteria.
|
|
||||||
|
|
||||||
When having your code reviewed and when reviewing merge requests please take the
|
|
||||||
[code review guidelines](doc/development/code_review.md) into account.
|
|
||||||
|
|
||||||
### Contribution acceptance criteria
|
|
||||||
|
|
||||||
1. The change is as small as possible
|
|
||||||
1. Include proper tests and make all tests pass (unless it contains a test
|
|
||||||
exposing a bug in existing code). Every new class should have corresponding
|
|
||||||
unit tests, even if the class is exercised at a higher level, such as a feature test.
|
|
||||||
1. If you suspect a failing CI build is unrelated to your contribution, you may
|
|
||||||
try and restart the failing CI job or ask a developer to fix the
|
|
||||||
aforementioned failing test
|
|
||||||
1. Your MR initially contains a single commit (please use `git rebase -i` to
|
|
||||||
squash commits)
|
|
||||||
1. Your changes can merge without problems (if not please rebase if you're the
|
|
||||||
only one working on your feature branch, otherwise, merge `master`)
|
|
||||||
1. Does not break any existing functionality
|
|
||||||
1. Fixes one specific issue or implements one specific feature (do not combine
|
|
||||||
things, send separate merge requests if needed)
|
|
||||||
1. Migrations should do only one thing (e.g., either create a table, move data
|
|
||||||
to a new table or remove an old table) to aid retrying on failure
|
|
||||||
1. Keeps the GitLab code base clean and well structured
|
|
||||||
1. Contains functionality we think other users will benefit from too
|
|
||||||
1. Doesn't add configuration options or settings options since they complicate
|
|
||||||
making and testing future changes
|
|
||||||
1. Changes do not adversely degrade performance.
|
|
||||||
- Avoid repeated polling of endpoints that require a significant amount of overhead
|
|
||||||
- Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
|
|
||||||
- Avoid repeated access of filesystem
|
|
||||||
1. If you need polling to support real-time features, please use
|
|
||||||
[polling with ETag caching][polling-etag].
|
|
||||||
1. Changes after submitting the merge request should be in separate commits
|
|
||||||
(no squashing).
|
|
||||||
1. It conforms to the [style guides](#style-guides) and the following:
|
|
||||||
- If your change touches a line that does not follow the style, modify the
|
|
||||||
entire line to follow it. This prevents linting tools from generating warnings.
|
|
||||||
- Don't touch neighbouring lines. As an exception, automatic mass
|
|
||||||
refactoring modifications may leave style non-compliant.
|
|
||||||
1. If the merge request adds any new libraries (gems, JavaScript libraries,
|
|
||||||
etc.), they should conform to our [Licensing guidelines][license-finder-doc].
|
|
||||||
See the instructions in that document for help if your MR fails the
|
|
||||||
"license-finder" test with a "Dependencies that need approval" error.
|
|
||||||
1. The merge request meets the [definition of done](#definition-of-done).
|
|
||||||
|
|
||||||
## Definition of done
|
|
||||||
|
|
||||||
If you contribute to GitLab please know that changes involve more than just
|
|
||||||
code. We have the following [definition of done][definition-of-done]. Please ensure you support
|
|
||||||
the feature you contribute through all of these steps.
|
|
||||||
|
|
||||||
1. Description explaining the relevancy (see following item)
|
|
||||||
1. Working and clean code that is commented where needed
|
|
||||||
1. [Unit, integration, and system tests][testing] that pass on the CI server
|
|
||||||
1. Performance/scalability implications have been considered, addressed, and tested
|
|
||||||
1. [Documented][doc-guidelines] in the `/doc` directory
|
|
||||||
1. [Changelog entry added][changelog], if necessary
|
|
||||||
1. Reviewed and any concerns are addressed
|
|
||||||
1. Merged by a project maintainer
|
|
||||||
1. Added to the release blog article, if relevant
|
|
||||||
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/), if relevant
|
|
||||||
1. Community questions answered
|
|
||||||
1. Answers to questions radiated (in docs/wiki/support etc.)
|
|
||||||
|
|
||||||
If you add a dependency in GitLab (such as an operating system package) please
|
|
||||||
consider updating the following and note the applicability of each in your
|
|
||||||
merge request:
|
|
||||||
|
|
||||||
1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
|
|
||||||
1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
|
|
||||||
1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
|
|
||||||
1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
|
|
||||||
1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
|
|
||||||
1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
|
|
||||||
1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
|
|
||||||
|
|
||||||
## Style guides
|
|
||||||
|
|
||||||
1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
|
|
||||||
Important sections include [Source Code Layout][rss-source] and
|
|
||||||
[Naming][rss-naming]. Use:
|
|
||||||
- multi-line method chaining style **Option A**: dot `.` on the second line
|
|
||||||
- string literal quoting style **Option A**: single quoted by default
|
|
||||||
1. [Rails](https://github.com/bbatsov/rails-style-guide)
|
|
||||||
1. [Newlines styleguide][newlines-styleguide]
|
|
||||||
1. [Testing][testing]
|
|
||||||
1. [JavaScript styleguide][js-styleguide]
|
|
||||||
1. [SCSS styleguide][scss-styleguide]
|
|
||||||
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
|
|
||||||
contributors to enhance security
|
|
||||||
1. [Database Migrations](doc/development/migration_style_guide.md)
|
|
||||||
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
|
|
||||||
1. [Documentation styleguide](https://docs.gitlab.com/ee/development/documentation/styleguide.html)
|
|
||||||
1. Interface text should be written subjectively instead of objectively. It
|
|
||||||
should be the GitLab core team addressing a person. It should be written in
|
|
||||||
present time and never use past tense (has been/was). For example instead
|
|
||||||
of _prohibited this user from being saved due to the following errors:_ the
|
|
||||||
text should be _sorry, we could not create your account because:_
|
|
||||||
1. Code should be written in [US English][us-english]
|
|
||||||
|
|
||||||
This is also the style used by linting tools such as
|
|
||||||
[RuboCop](https://github.com/bbatsov/rubocop),
|
|
||||||
[PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com).
|
|
||||||
|
|
||||||
## Code of conduct
|
|
||||||
|
|
||||||
As contributors and maintainers of this project, we pledge to respect all
|
|
||||||
people who contribute through reporting issues, posting feature requests,
|
|
||||||
updating documentation, submitting pull requests or patches, and other
|
|
||||||
activities.
|
|
||||||
|
|
||||||
We are committed to making participation in this project a harassment-free
|
|
||||||
experience for everyone, regardless of level of experience, gender, gender
|
|
||||||
identity and expression, sexual orientation, disability, personal appearance,
|
|
||||||
body size, race, ethnicity, age, or religion.
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include the use of sexual
|
|
||||||
language or imagery, derogatory comments or personal attacks, trolling, public
|
|
||||||
or private harassment, insults, or other unprofessional conduct.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or
|
|
||||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
||||||
that are not aligned to this Code of Conduct. Project maintainers who do not
|
|
||||||
follow the Code of Conduct may be removed from the project team.
|
|
||||||
|
|
||||||
This code of conduct applies both within project spaces and in public spaces
|
|
||||||
when an individual is representing the project or its community.
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior can be
|
|
||||||
reported by emailing `contact@gitlab.com`.
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
|
|
||||||
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
|
|
||||||
|
|
||||||
## Contribution Flow
|
## Contribution Flow
|
||||||
|
|
||||||
When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty.
|
When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty.
|
||||||
|
@ -789,3 +208,115 @@ When your code contains more than 500 changes, any major breaking changes, or an
|
||||||
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
|
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
|
||||||
[testing]: doc/development/testing_guide/index.md
|
[testing]: doc/development/testing_guide/index.md
|
||||||
[us-english]: https://en.wikipedia.org/wiki/American_English
|
[us-english]: https://en.wikipedia.org/wiki/American_English
|
||||||
|
|
||||||
|
|
||||||
|
## Workflow labels
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Type labels
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Subject labels
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Team labels
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Release Scoping labels
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Priority labels
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Severity labels
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
#### Severity impact guidance
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Label for community contributors
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
## Implement design & UI elements
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/design.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
## Issue tracker
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
### Issue triaging
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Feature proposals
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
### Issue tracker guidelines
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Issue weight
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Regression issues
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Technical and UX debt
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Stewardship
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
## Merge requests
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Merge request guidelines
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
### Contribution acceptance criteria
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
## Definition of done
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/merge_request_workflow.md) has been moved.
|
||||||
|
|
||||||
|
|
||||||
|
## Style guides
|
||||||
|
|
||||||
|
This [documentation](doc/development/contributing/design.md) has been moved.
|
||||||
|
|
|
@ -4,4 +4,5 @@ danger.import_dangerfile(path: 'danger/changelog')
|
||||||
danger.import_dangerfile(path: 'danger/specs')
|
danger.import_dangerfile(path: 'danger/specs')
|
||||||
danger.import_dangerfile(path: 'danger/gemfile')
|
danger.import_dangerfile(path: 'danger/gemfile')
|
||||||
danger.import_dangerfile(path: 'danger/database')
|
danger.import_dangerfile(path: 'danger/database')
|
||||||
|
danger.import_dangerfile(path: 'danger/documentation')
|
||||||
danger.import_dangerfile(path: 'danger/frozen_string')
|
danger.import_dangerfile(path: 'danger/frozen_string')
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
0.117.3
|
0.120.1
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
1.0.0
|
1.1.0
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
8.1.1
|
8.3.3
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
5.1.0
|
6.1.0
|
||||||
|
|
24
Gemfile
|
@ -68,7 +68,7 @@ gem 'u2f', '~> 0.2.1'
|
||||||
gem 'validates_hostname', '~> 1.0.6'
|
gem 'validates_hostname', '~> 1.0.6'
|
||||||
|
|
||||||
# Browser detection
|
# Browser detection
|
||||||
gem 'browser', '~> 2.2'
|
gem 'browser', '~> 2.5'
|
||||||
|
|
||||||
# GPG
|
# GPG
|
||||||
gem 'gpgme'
|
gem 'gpgme'
|
||||||
|
@ -107,7 +107,9 @@ gem 'kaminari', '~> 1.0'
|
||||||
gem 'hamlit', '~> 2.8.8'
|
gem 'hamlit', '~> 2.8.8'
|
||||||
|
|
||||||
# Files attachments
|
# Files attachments
|
||||||
gem 'carrierwave', '~> 1.2'
|
# Locked until https://github.com/carrierwaveuploader/carrierwave/pull/2332/files is merged.
|
||||||
|
# config/initializers/carrierwave_patch.rb can be removed once that change is released.
|
||||||
|
gem 'carrierwave', '= 1.2.3'
|
||||||
gem 'mini_magick'
|
gem 'mini_magick'
|
||||||
|
|
||||||
# Drag and Drop UI
|
# Drag and Drop UI
|
||||||
|
@ -116,14 +118,14 @@ gem 'dropzonejs-rails', '~> 0.7.1'
|
||||||
# for backups
|
# for backups
|
||||||
gem 'fog-aws', '~> 2.0.1'
|
gem 'fog-aws', '~> 2.0.1'
|
||||||
gem 'fog-core', '~> 1.44'
|
gem 'fog-core', '~> 1.44'
|
||||||
gem 'fog-google', '~> 1.3.3'
|
gem 'fog-google', '~> 1.7.1'
|
||||||
gem 'fog-local', '~> 0.3'
|
gem 'fog-local', '~> 0.3'
|
||||||
gem 'fog-openstack', '~> 0.1'
|
gem 'fog-openstack', '~> 0.1'
|
||||||
gem 'fog-rackspace', '~> 0.1.1'
|
gem 'fog-rackspace', '~> 0.1.1'
|
||||||
gem 'fog-aliyun', '~> 0.2.0'
|
gem 'fog-aliyun', '~> 0.2.0'
|
||||||
|
|
||||||
# for Google storage
|
# for Google storage
|
||||||
gem 'google-api-client', '~> 0.19.8'
|
gem 'google-api-client', '~> 0.23'
|
||||||
|
|
||||||
# for aws storage
|
# for aws storage
|
||||||
gem 'unf', '~> 0.1.4'
|
gem 'unf', '~> 0.1.4'
|
||||||
|
@ -180,7 +182,7 @@ gem 'rufus-scheduler', '~> 3.4'
|
||||||
gem 'httparty', '~> 0.13.3'
|
gem 'httparty', '~> 0.13.3'
|
||||||
|
|
||||||
# Colored output to console
|
# Colored output to console
|
||||||
gem 'rainbow', '~> 2.2'
|
gem 'rainbow', '~> 3.0'
|
||||||
|
|
||||||
# Progress bar
|
# Progress bar
|
||||||
gem 'ruby-progressbar'
|
gem 'ruby-progressbar'
|
||||||
|
@ -195,6 +197,9 @@ gem 're2', '~> 1.1.1'
|
||||||
|
|
||||||
gem 'version_sorter', '~> 2.1.0'
|
gem 'version_sorter', '~> 2.1.0'
|
||||||
|
|
||||||
|
# Export Ruby Regex to Javascript
|
||||||
|
gem 'js_regex', '~> 2.2.1'
|
||||||
|
|
||||||
# User agent parsing
|
# User agent parsing
|
||||||
gem 'device_detector'
|
gem 'device_detector'
|
||||||
|
|
||||||
|
@ -214,9 +219,6 @@ gem 'jira-ruby', '~> 1.4'
|
||||||
# Flowdock integration
|
# Flowdock integration
|
||||||
gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
|
gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
|
||||||
|
|
||||||
# Gemnasium integration
|
|
||||||
gem 'gemnasium-gitlab-service', '~> 0.2'
|
|
||||||
|
|
||||||
# Slack integration
|
# Slack integration
|
||||||
gem 'slack-notifier', '~> 1.5.1'
|
gem 'slack-notifier', '~> 1.5.1'
|
||||||
|
|
||||||
|
@ -363,12 +365,11 @@ group :development, :test do
|
||||||
gem 'scss_lint', '~> 0.56.0', require: false
|
gem 'scss_lint', '~> 0.56.0', require: false
|
||||||
gem 'haml_lint', '~> 0.26.0', require: false
|
gem 'haml_lint', '~> 0.26.0', require: false
|
||||||
gem 'simplecov', '~> 0.14.0', require: false
|
gem 'simplecov', '~> 0.14.0', require: false
|
||||||
gem 'flay', '~> 2.10.0', require: false
|
|
||||||
gem 'bundler-audit', '~> 0.5.0', require: false
|
gem 'bundler-audit', '~> 0.5.0', require: false
|
||||||
|
|
||||||
gem 'benchmark-ips', '~> 2.3.0', require: false
|
gem 'benchmark-ips', '~> 2.3.0', require: false
|
||||||
|
|
||||||
gem 'license_finder', '~> 3.1', require: false
|
gem 'license_finder', '~> 5.4', require: false
|
||||||
gem 'knapsack', '~> 1.16'
|
gem 'knapsack', '~> 1.16'
|
||||||
|
|
||||||
gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
|
gem 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
|
||||||
|
@ -390,6 +391,7 @@ group :test do
|
||||||
gem 'sham_rack', '~> 1.3.6'
|
gem 'sham_rack', '~> 1.3.6'
|
||||||
gem 'concurrent-ruby', '~> 1.0.5'
|
gem 'concurrent-ruby', '~> 1.0.5'
|
||||||
gem 'test-prof', '~> 0.2.5'
|
gem 'test-prof', '~> 0.2.5'
|
||||||
|
gem 'rspec_junit_formatter'
|
||||||
end
|
end
|
||||||
|
|
||||||
gem 'octokit', '~> 4.9'
|
gem 'octokit', '~> 4.9'
|
||||||
|
@ -423,7 +425,7 @@ group :ed25519 do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Gitaly GRPC client
|
# Gitaly GRPC client
|
||||||
gem 'gitaly-proto', '~> 0.113.0', require: 'gitaly'
|
gem 'gitaly-proto', '~> 0.117.0', require: 'gitaly'
|
||||||
gem 'grpc', '~> 1.11.0'
|
gem 'grpc', '~> 1.11.0'
|
||||||
|
|
||||||
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
||||||
|
|
84
Gemfile.lock
|
@ -86,12 +86,11 @@ GEM
|
||||||
bindata (2.4.3)
|
bindata (2.4.3)
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.2)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
blankslate (2.1.2.4)
|
|
||||||
bootsnap (1.3.1)
|
bootsnap (1.3.1)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
bootstrap_form (2.7.0)
|
bootstrap_form (2.7.0)
|
||||||
brakeman (4.2.1)
|
brakeman (4.2.1)
|
||||||
browser (2.2.0)
|
browser (2.5.3)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
bullet (5.5.1)
|
bullet (5.5.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
@ -123,10 +122,10 @@ GEM
|
||||||
numerizer (~> 0.1.1)
|
numerizer (~> 0.1.1)
|
||||||
chunky_png (1.3.5)
|
chunky_png (1.3.5)
|
||||||
citrus (3.0.2)
|
citrus (3.0.2)
|
||||||
coderay (1.1.1)
|
coderay (1.1.2)
|
||||||
coercible (1.0.0)
|
coercible (1.0.0)
|
||||||
descendants_tracker (~> 0.0.1)
|
descendants_tracker (~> 0.0.1)
|
||||||
commonmarker (0.17.8)
|
commonmarker (0.17.13)
|
||||||
ruby-enum (~> 0.5)
|
ruby-enum (~> 0.5)
|
||||||
concord (0.1.5)
|
concord (0.1.5)
|
||||||
adamantium (~> 0.2.0)
|
adamantium (~> 0.2.0)
|
||||||
|
@ -209,12 +208,7 @@ GEM
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
fast_gettext (1.6.0)
|
fast_gettext (1.6.0)
|
||||||
ffaker (2.4.0)
|
ffaker (2.4.0)
|
||||||
ffi (1.9.18)
|
ffi (1.9.25)
|
||||||
flay (2.10.0)
|
|
||||||
erubis (~> 2.7.0)
|
|
||||||
path_expander (~> 1.0)
|
|
||||||
ruby_parser (~> 3.0)
|
|
||||||
sexp_processor (~> 4.0)
|
|
||||||
flipper (0.13.0)
|
flipper (0.13.0)
|
||||||
flipper-active_record (0.13.0)
|
flipper-active_record (0.13.0)
|
||||||
activerecord (>= 3.2, < 6)
|
activerecord (>= 3.2, < 6)
|
||||||
|
@ -239,11 +233,11 @@ GEM
|
||||||
builder
|
builder
|
||||||
excon (~> 0.58)
|
excon (~> 0.58)
|
||||||
formatador (~> 0.2)
|
formatador (~> 0.2)
|
||||||
fog-google (1.3.3)
|
fog-google (1.7.1)
|
||||||
fog-core
|
fog-core
|
||||||
fog-json
|
fog-json
|
||||||
fog-xml
|
fog-xml
|
||||||
google-api-client (~> 0.19.1)
|
google-api-client (~> 0.23.0)
|
||||||
fog-json (1.0.2)
|
fog-json (1.0.2)
|
||||||
fog-core (~> 1.0)
|
fog-core (~> 1.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
|
@ -269,8 +263,6 @@ GEM
|
||||||
fuubar (2.2.0)
|
fuubar (2.2.0)
|
||||||
rspec-core (~> 3.0)
|
rspec-core (~> 3.0)
|
||||||
ruby-progressbar (~> 1.4)
|
ruby-progressbar (~> 1.4)
|
||||||
gemnasium-gitlab-service (0.2.6)
|
|
||||||
rugged (~> 0.21)
|
|
||||||
gemojione (3.3.0)
|
gemojione (3.3.0)
|
||||||
json
|
json
|
||||||
get_process_mem (0.2.0)
|
get_process_mem (0.2.0)
|
||||||
|
@ -284,7 +276,7 @@ GEM
|
||||||
gettext_i18n_rails (>= 0.7.1)
|
gettext_i18n_rails (>= 0.7.1)
|
||||||
po_to_json (>= 1.0.0)
|
po_to_json (>= 1.0.0)
|
||||||
rails (>= 3.2.0)
|
rails (>= 3.2.0)
|
||||||
gitaly-proto (0.113.0)
|
gitaly-proto (0.117.0)
|
||||||
google-protobuf (~> 3.1)
|
google-protobuf (~> 3.1)
|
||||||
grpc (~> 1.10)
|
grpc (~> 1.10)
|
||||||
github-linguist (5.3.3)
|
github-linguist (5.3.3)
|
||||||
|
@ -331,7 +323,7 @@ GEM
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
multi_json
|
multi_json
|
||||||
request_store (>= 1.0)
|
request_store (>= 1.0)
|
||||||
google-api-client (0.19.8)
|
google-api-client (0.23.4)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.5, < 0.7.0)
|
googleauth (>= 0.5, < 0.7.0)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.0)
|
||||||
|
@ -430,6 +422,8 @@ GEM
|
||||||
multipart-post
|
multipart-post
|
||||||
oauth (~> 0.5, >= 0.5.0)
|
oauth (~> 0.5, >= 0.5.0)
|
||||||
jquery-atwho-rails (1.3.2)
|
jquery-atwho-rails (1.3.2)
|
||||||
|
js_regex (2.2.1)
|
||||||
|
regexp_parser (>= 0.4.11, <= 0.5.0)
|
||||||
json (1.8.6)
|
json (1.8.6)
|
||||||
json-jwt (1.9.4)
|
json-jwt (1.9.4)
|
||||||
activesupport
|
activesupport
|
||||||
|
@ -465,13 +459,12 @@ GEM
|
||||||
actionmailer (>= 3.2)
|
actionmailer (>= 3.2)
|
||||||
letter_opener (~> 1.0)
|
letter_opener (~> 1.0)
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
license_finder (3.1.1)
|
license_finder (5.4.0)
|
||||||
bundler
|
bundler
|
||||||
httparty
|
|
||||||
rubyzip
|
rubyzip
|
||||||
thor
|
thor
|
||||||
toml (= 0.1.2)
|
toml (= 0.2.0)
|
||||||
with_env (> 1.0)
|
with_env (= 1.1.0)
|
||||||
xml-simple
|
xml-simple
|
||||||
licensee (8.9.2)
|
licensee (8.9.2)
|
||||||
rugged (~> 0.24)
|
rugged (~> 0.24)
|
||||||
|
@ -494,7 +487,7 @@ GEM
|
||||||
memoist (0.16.0)
|
memoist (0.16.0)
|
||||||
memoizable (0.4.2)
|
memoizable (0.4.2)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
method_source (0.8.2)
|
method_source (0.9.0)
|
||||||
mime-types (3.1)
|
mime-types (3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
|
@ -589,9 +582,7 @@ GEM
|
||||||
parallel (1.12.1)
|
parallel (1.12.1)
|
||||||
parser (2.5.1.0)
|
parser (2.5.1.0)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
parslet (1.5.0)
|
parslet (1.8.2)
|
||||||
blankslate (~> 2.0)
|
|
||||||
path_expander (1.0.2)
|
|
||||||
peek (1.0.1)
|
peek (1.0.1)
|
||||||
concurrent-ruby (>= 0.9.0)
|
concurrent-ruby (>= 0.9.0)
|
||||||
concurrent-ruby-ext (>= 0.9.0)
|
concurrent-ruby-ext (>= 0.9.0)
|
||||||
|
@ -636,12 +627,11 @@ GEM
|
||||||
unparser
|
unparser
|
||||||
procto (0.0.3)
|
procto (0.0.3)
|
||||||
prometheus-client-mmap (0.9.4)
|
prometheus-client-mmap (0.9.4)
|
||||||
pry (0.10.4)
|
pry (0.11.3)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.8.1)
|
method_source (~> 0.9.0)
|
||||||
slop (~> 3.4)
|
pry-byebug (3.4.3)
|
||||||
pry-byebug (3.4.2)
|
byebug (>= 9.0, < 9.1)
|
||||||
byebug (~> 9.0)
|
|
||||||
pry (~> 0.10)
|
pry (~> 0.10)
|
||||||
pry-rails (0.3.5)
|
pry-rails (0.3.5)
|
||||||
pry (>= 0.9.10)
|
pry (>= 0.9.10)
|
||||||
|
@ -692,8 +682,7 @@ GEM
|
||||||
activesupport (= 4.2.10)
|
activesupport (= 4.2.10)
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.2.2)
|
rainbow (3.0.0)
|
||||||
rake
|
|
||||||
raindrops (0.18.0)
|
raindrops (0.18.0)
|
||||||
rake (12.3.1)
|
rake (12.3.1)
|
||||||
rb-fsevent (0.10.2)
|
rb-fsevent (0.10.2)
|
||||||
|
@ -730,6 +719,7 @@ GEM
|
||||||
redis-store (>= 1.2, < 2)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-store (1.4.1)
|
redis-store (1.4.1)
|
||||||
redis (>= 2.2, < 5)
|
redis (>= 2.2, < 5)
|
||||||
|
regexp_parser (0.5.0)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
declarative-option (< 0.2.0)
|
||||||
|
@ -742,7 +732,7 @@ GEM
|
||||||
http-cookie (>= 1.0.2, < 2.0)
|
http-cookie (>= 1.0.2, < 2.0)
|
||||||
mime-types (>= 1.16, < 4.0)
|
mime-types (>= 1.16, < 4.0)
|
||||||
netrc (~> 0.8)
|
netrc (~> 0.8)
|
||||||
retriable (3.1.1)
|
retriable (3.1.2)
|
||||||
rinku (2.0.0)
|
rinku (2.0.0)
|
||||||
rotp (2.1.2)
|
rotp (2.1.2)
|
||||||
rouge (3.2.1)
|
rouge (3.2.1)
|
||||||
|
@ -780,6 +770,9 @@ GEM
|
||||||
rspec-core
|
rspec-core
|
||||||
rspec-set (0.1.3)
|
rspec-set (0.1.3)
|
||||||
rspec-support (3.7.1)
|
rspec-support (3.7.1)
|
||||||
|
rspec_junit_formatter (0.2.3)
|
||||||
|
builder (< 4)
|
||||||
|
rspec-core (>= 2, < 4, != 2.12.0)
|
||||||
rspec_profiling (0.0.5)
|
rspec_profiling (0.0.5)
|
||||||
activerecord
|
activerecord
|
||||||
pg
|
pg
|
||||||
|
@ -808,7 +801,7 @@ GEM
|
||||||
sexp_processor (~> 4.1)
|
sexp_processor (~> 4.1)
|
||||||
rubyntlm (0.6.2)
|
rubyntlm (0.6.2)
|
||||||
rubypants (0.2.0)
|
rubypants (0.2.0)
|
||||||
rubyzip (1.2.1)
|
rubyzip (1.2.2)
|
||||||
rufus-scheduler (3.4.0)
|
rufus-scheduler (3.4.0)
|
||||||
et-orbi (~> 1.0)
|
et-orbi (~> 1.0)
|
||||||
rugged (0.27.4)
|
rugged (0.27.4)
|
||||||
|
@ -872,7 +865,6 @@ GEM
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.0)
|
simplecov-html (0.10.0)
|
||||||
slack-notifier (1.5.1)
|
slack-notifier (1.5.1)
|
||||||
slop (3.6.0)
|
|
||||||
spring (2.0.1)
|
spring (2.0.1)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
spring-commands-rspec (1.0.4)
|
spring-commands-rspec (1.0.4)
|
||||||
|
@ -912,8 +904,8 @@ GEM
|
||||||
tilt (2.0.8)
|
tilt (2.0.8)
|
||||||
timecop (0.8.1)
|
timecop (0.8.1)
|
||||||
timfel-krb5-auth (0.8.3)
|
timfel-krb5-auth (0.8.3)
|
||||||
toml (0.1.2)
|
toml (0.2.0)
|
||||||
parslet (~> 1.5.0)
|
parslet (~> 1.8.0)
|
||||||
toml-rb (1.0.0)
|
toml-rb (1.0.0)
|
||||||
citrus (~> 3.0, > 3.0)
|
citrus (~> 3.0, > 3.0)
|
||||||
trollop (2.1.3)
|
trollop (2.1.3)
|
||||||
|
@ -999,12 +991,12 @@ DEPENDENCIES
|
||||||
bootsnap (~> 1.3)
|
bootsnap (~> 1.3)
|
||||||
bootstrap_form (~> 2.7.0)
|
bootstrap_form (~> 2.7.0)
|
||||||
brakeman (~> 4.2)
|
brakeman (~> 4.2)
|
||||||
browser (~> 2.2)
|
browser (~> 2.5)
|
||||||
bullet (~> 5.5.0)
|
bullet (~> 5.5.0)
|
||||||
bundler-audit (~> 0.5.0)
|
bundler-audit (~> 0.5.0)
|
||||||
capybara (~> 2.15)
|
capybara (~> 2.15)
|
||||||
capybara-screenshot (~> 1.0.0)
|
capybara-screenshot (~> 1.0.0)
|
||||||
carrierwave (~> 1.2)
|
carrierwave (= 1.2.3)
|
||||||
charlock_holmes (~> 0.7.5)
|
charlock_holmes (~> 0.7.5)
|
||||||
chronic (~> 0.10.2)
|
chronic (~> 0.10.2)
|
||||||
chronic_duration (~> 0.10.6)
|
chronic_duration (~> 0.10.6)
|
||||||
|
@ -1029,26 +1021,24 @@ DEPENDENCIES
|
||||||
faraday (~> 0.12)
|
faraday (~> 0.12)
|
||||||
fast_blank
|
fast_blank
|
||||||
ffaker (~> 2.4)
|
ffaker (~> 2.4)
|
||||||
flay (~> 2.10.0)
|
|
||||||
flipper (~> 0.13.0)
|
flipper (~> 0.13.0)
|
||||||
flipper-active_record (~> 0.13.0)
|
flipper-active_record (~> 0.13.0)
|
||||||
flipper-active_support_cache_store (~> 0.13.0)
|
flipper-active_support_cache_store (~> 0.13.0)
|
||||||
fog-aliyun (~> 0.2.0)
|
fog-aliyun (~> 0.2.0)
|
||||||
fog-aws (~> 2.0.1)
|
fog-aws (~> 2.0.1)
|
||||||
fog-core (~> 1.44)
|
fog-core (~> 1.44)
|
||||||
fog-google (~> 1.3.3)
|
fog-google (~> 1.7.1)
|
||||||
fog-local (~> 0.3)
|
fog-local (~> 0.3)
|
||||||
fog-openstack (~> 0.1)
|
fog-openstack (~> 0.1)
|
||||||
fog-rackspace (~> 0.1.1)
|
fog-rackspace (~> 0.1.1)
|
||||||
font-awesome-rails (~> 4.7)
|
font-awesome-rails (~> 4.7)
|
||||||
foreman (~> 0.84.0)
|
foreman (~> 0.84.0)
|
||||||
fuubar (~> 2.2.0)
|
fuubar (~> 2.2.0)
|
||||||
gemnasium-gitlab-service (~> 0.2)
|
|
||||||
gemojione (~> 3.3)
|
gemojione (~> 3.3)
|
||||||
gettext (~> 3.2.2)
|
gettext (~> 3.2.2)
|
||||||
gettext_i18n_rails (~> 1.8.0)
|
gettext_i18n_rails (~> 1.8.0)
|
||||||
gettext_i18n_rails_js (~> 1.3)
|
gettext_i18n_rails_js (~> 1.3)
|
||||||
gitaly-proto (~> 0.113.0)
|
gitaly-proto (~> 0.117.0)
|
||||||
github-linguist (~> 5.3.3)
|
github-linguist (~> 5.3.3)
|
||||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||||
gitlab-gollum-lib (~> 4.2)
|
gitlab-gollum-lib (~> 4.2)
|
||||||
|
@ -1057,7 +1047,7 @@ DEPENDENCIES
|
||||||
gitlab-styles (~> 2.4)
|
gitlab-styles (~> 2.4)
|
||||||
gitlab_omniauth-ldap (~> 2.0.4)
|
gitlab_omniauth-ldap (~> 2.0.4)
|
||||||
gon (~> 6.2)
|
gon (~> 6.2)
|
||||||
google-api-client (~> 0.19.8)
|
google-api-client (~> 0.23)
|
||||||
google-protobuf (= 3.5.1)
|
google-protobuf (= 3.5.1)
|
||||||
gpgme
|
gpgme
|
||||||
grape (~> 1.0)
|
grape (~> 1.0)
|
||||||
|
@ -1080,13 +1070,14 @@ DEPENDENCIES
|
||||||
influxdb (~> 0.2)
|
influxdb (~> 0.2)
|
||||||
jira-ruby (~> 1.4)
|
jira-ruby (~> 1.4)
|
||||||
jquery-atwho-rails (~> 1.3.2)
|
jquery-atwho-rails (~> 1.3.2)
|
||||||
|
js_regex (~> 2.2.1)
|
||||||
json-schema (~> 2.8.0)
|
json-schema (~> 2.8.0)
|
||||||
jwt (~> 1.5.6)
|
jwt (~> 1.5.6)
|
||||||
kaminari (~> 1.0)
|
kaminari (~> 1.0)
|
||||||
knapsack (~> 1.16)
|
knapsack (~> 1.16)
|
||||||
kubeclient (~> 3.1.0)
|
kubeclient (~> 3.1.0)
|
||||||
letter_opener_web (~> 1.3.0)
|
letter_opener_web (~> 1.3.0)
|
||||||
license_finder (~> 3.1)
|
license_finder (~> 5.4)
|
||||||
licensee (~> 8.9)
|
licensee (~> 8.9)
|
||||||
lograge (~> 0.5)
|
lograge (~> 0.5)
|
||||||
loofah (~> 2.2)
|
loofah (~> 2.2)
|
||||||
|
@ -1136,7 +1127,7 @@ DEPENDENCIES
|
||||||
rails (= 4.2.10)
|
rails (= 4.2.10)
|
||||||
rails-deprecated_sanitizer (~> 1.0.3)
|
rails-deprecated_sanitizer (~> 1.0.3)
|
||||||
rails-i18n (~> 4.0.9)
|
rails-i18n (~> 4.0.9)
|
||||||
rainbow (~> 2.2)
|
rainbow (~> 3.0)
|
||||||
raindrops (~> 0.18)
|
raindrops (~> 0.18)
|
||||||
rblineprof (~> 0.3.6)
|
rblineprof (~> 0.3.6)
|
||||||
rbtrace (~> 0.4)
|
rbtrace (~> 0.4)
|
||||||
|
@ -1155,6 +1146,7 @@ DEPENDENCIES
|
||||||
rspec-rails (~> 3.7.0)
|
rspec-rails (~> 3.7.0)
|
||||||
rspec-retry (~> 0.4.5)
|
rspec-retry (~> 0.4.5)
|
||||||
rspec-set (~> 0.1.3)
|
rspec-set (~> 0.1.3)
|
||||||
|
rspec_junit_formatter
|
||||||
rspec_profiling (~> 0.0.5)
|
rspec_profiling (~> 0.0.5)
|
||||||
rubocop (~> 0.54.0)
|
rubocop (~> 0.54.0)
|
||||||
rubocop-rspec (~> 1.22.1)
|
rubocop-rspec (~> 1.22.1)
|
||||||
|
@ -1207,4 +1199,4 @@ DEPENDENCIES
|
||||||
wikicloth (= 0.8.1)
|
wikicloth (= 0.8.1)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.16.2
|
1.16.4
|
||||||
|
|
|
@ -89,12 +89,11 @@ GEM
|
||||||
bindata (2.4.3)
|
bindata (2.4.3)
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.2)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
blankslate (2.1.2.4)
|
|
||||||
bootsnap (1.3.1)
|
bootsnap (1.3.1)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
bootstrap_form (2.7.0)
|
bootstrap_form (2.7.0)
|
||||||
brakeman (4.2.1)
|
brakeman (4.2.1)
|
||||||
browser (2.2.0)
|
browser (2.5.3)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
bullet (5.5.1)
|
bullet (5.5.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
@ -126,7 +125,7 @@ GEM
|
||||||
numerizer (~> 0.1.1)
|
numerizer (~> 0.1.1)
|
||||||
chunky_png (1.3.5)
|
chunky_png (1.3.5)
|
||||||
citrus (3.0.2)
|
citrus (3.0.2)
|
||||||
coderay (1.1.1)
|
coderay (1.1.2)
|
||||||
coercible (1.0.0)
|
coercible (1.0.0)
|
||||||
descendants_tracker (~> 0.0.1)
|
descendants_tracker (~> 0.0.1)
|
||||||
commonmarker (0.17.8)
|
commonmarker (0.17.8)
|
||||||
|
@ -212,12 +211,7 @@ GEM
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
fast_gettext (1.6.0)
|
fast_gettext (1.6.0)
|
||||||
ffaker (2.4.0)
|
ffaker (2.4.0)
|
||||||
ffi (1.9.18)
|
ffi (1.9.25)
|
||||||
flay (2.10.0)
|
|
||||||
erubis (~> 2.7.0)
|
|
||||||
path_expander (~> 1.0)
|
|
||||||
ruby_parser (~> 3.0)
|
|
||||||
sexp_processor (~> 4.0)
|
|
||||||
flipper (0.13.0)
|
flipper (0.13.0)
|
||||||
flipper-active_record (0.13.0)
|
flipper-active_record (0.13.0)
|
||||||
activerecord (>= 3.2, < 6)
|
activerecord (>= 3.2, < 6)
|
||||||
|
@ -242,11 +236,11 @@ GEM
|
||||||
builder
|
builder
|
||||||
excon (~> 0.58)
|
excon (~> 0.58)
|
||||||
formatador (~> 0.2)
|
formatador (~> 0.2)
|
||||||
fog-google (1.3.3)
|
fog-google (1.7.1)
|
||||||
fog-core
|
fog-core
|
||||||
fog-json
|
fog-json
|
||||||
fog-xml
|
fog-xml
|
||||||
google-api-client (~> 0.19.1)
|
google-api-client (~> 0.23.0)
|
||||||
fog-json (1.0.2)
|
fog-json (1.0.2)
|
||||||
fog-core (~> 1.0)
|
fog-core (~> 1.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
|
@ -272,8 +266,6 @@ GEM
|
||||||
fuubar (2.2.0)
|
fuubar (2.2.0)
|
||||||
rspec-core (~> 3.0)
|
rspec-core (~> 3.0)
|
||||||
ruby-progressbar (~> 1.4)
|
ruby-progressbar (~> 1.4)
|
||||||
gemnasium-gitlab-service (0.2.6)
|
|
||||||
rugged (~> 0.21)
|
|
||||||
gemojione (3.3.0)
|
gemojione (3.3.0)
|
||||||
json
|
json
|
||||||
get_process_mem (0.2.0)
|
get_process_mem (0.2.0)
|
||||||
|
@ -287,7 +279,7 @@ GEM
|
||||||
gettext_i18n_rails (>= 0.7.1)
|
gettext_i18n_rails (>= 0.7.1)
|
||||||
po_to_json (>= 1.0.0)
|
po_to_json (>= 1.0.0)
|
||||||
rails (>= 3.2.0)
|
rails (>= 3.2.0)
|
||||||
gitaly-proto (0.113.0)
|
gitaly-proto (0.117.0)
|
||||||
google-protobuf (~> 3.1)
|
google-protobuf (~> 3.1)
|
||||||
grpc (~> 1.10)
|
grpc (~> 1.10)
|
||||||
github-linguist (5.3.3)
|
github-linguist (5.3.3)
|
||||||
|
@ -334,7 +326,7 @@ GEM
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
multi_json
|
multi_json
|
||||||
request_store (>= 1.0)
|
request_store (>= 1.0)
|
||||||
google-api-client (0.19.8)
|
google-api-client (0.23.4)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.5, < 0.7.0)
|
googleauth (>= 0.5, < 0.7.0)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.0)
|
||||||
|
@ -433,6 +425,8 @@ GEM
|
||||||
multipart-post
|
multipart-post
|
||||||
oauth (~> 0.5, >= 0.5.0)
|
oauth (~> 0.5, >= 0.5.0)
|
||||||
jquery-atwho-rails (1.3.2)
|
jquery-atwho-rails (1.3.2)
|
||||||
|
js_regex (2.2.1)
|
||||||
|
regexp_parser (>= 0.4.11, <= 0.5.0)
|
||||||
json (1.8.6)
|
json (1.8.6)
|
||||||
json-jwt (1.9.4)
|
json-jwt (1.9.4)
|
||||||
activesupport
|
activesupport
|
||||||
|
@ -468,13 +462,12 @@ GEM
|
||||||
actionmailer (>= 3.2)
|
actionmailer (>= 3.2)
|
||||||
letter_opener (~> 1.0)
|
letter_opener (~> 1.0)
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
license_finder (3.1.1)
|
license_finder (5.4.0)
|
||||||
bundler
|
bundler
|
||||||
httparty
|
|
||||||
rubyzip
|
rubyzip
|
||||||
thor
|
thor
|
||||||
toml (= 0.1.2)
|
toml (= 0.2.0)
|
||||||
with_env (> 1.0)
|
with_env (= 1.1.0)
|
||||||
xml-simple
|
xml-simple
|
||||||
licensee (8.9.2)
|
licensee (8.9.2)
|
||||||
rugged (~> 0.24)
|
rugged (~> 0.24)
|
||||||
|
@ -497,7 +490,7 @@ GEM
|
||||||
memoist (0.16.0)
|
memoist (0.16.0)
|
||||||
memoizable (0.4.2)
|
memoizable (0.4.2)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
method_source (0.8.2)
|
method_source (0.9.0)
|
||||||
mime-types (3.1)
|
mime-types (3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
|
@ -593,9 +586,7 @@ GEM
|
||||||
parallel (1.12.1)
|
parallel (1.12.1)
|
||||||
parser (2.5.1.0)
|
parser (2.5.1.0)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
parslet (1.5.0)
|
parslet (1.8.2)
|
||||||
blankslate (~> 2.0)
|
|
||||||
path_expander (1.0.2)
|
|
||||||
peek (1.0.1)
|
peek (1.0.1)
|
||||||
concurrent-ruby (>= 0.9.0)
|
concurrent-ruby (>= 0.9.0)
|
||||||
concurrent-ruby-ext (>= 0.9.0)
|
concurrent-ruby-ext (>= 0.9.0)
|
||||||
|
@ -640,12 +631,11 @@ GEM
|
||||||
unparser
|
unparser
|
||||||
procto (0.0.3)
|
procto (0.0.3)
|
||||||
prometheus-client-mmap (0.9.4)
|
prometheus-client-mmap (0.9.4)
|
||||||
pry (0.10.4)
|
pry (0.11.3)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.8.1)
|
method_source (~> 0.9.0)
|
||||||
slop (~> 3.4)
|
pry-byebug (3.4.3)
|
||||||
pry-byebug (3.4.2)
|
byebug (>= 9.0, < 9.1)
|
||||||
byebug (~> 9.0)
|
|
||||||
pry (~> 0.10)
|
pry (~> 0.10)
|
||||||
pry-rails (0.3.5)
|
pry-rails (0.3.5)
|
||||||
pry (>= 0.9.10)
|
pry (>= 0.9.10)
|
||||||
|
@ -701,8 +691,7 @@ GEM
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.2.2)
|
rainbow (3.0.0)
|
||||||
rake
|
|
||||||
raindrops (0.18.0)
|
raindrops (0.18.0)
|
||||||
rake (12.3.1)
|
rake (12.3.1)
|
||||||
rb-fsevent (0.10.2)
|
rb-fsevent (0.10.2)
|
||||||
|
@ -739,6 +728,7 @@ GEM
|
||||||
redis-store (>= 1.2, < 2)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-store (1.4.1)
|
redis-store (1.4.1)
|
||||||
redis (>= 2.2, < 5)
|
redis (>= 2.2, < 5)
|
||||||
|
regexp_parser (0.5.0)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
declarative (< 0.1.0)
|
declarative (< 0.1.0)
|
||||||
declarative-option (< 0.2.0)
|
declarative-option (< 0.2.0)
|
||||||
|
@ -751,10 +741,10 @@ GEM
|
||||||
http-cookie (>= 1.0.2, < 2.0)
|
http-cookie (>= 1.0.2, < 2.0)
|
||||||
mime-types (>= 1.16, < 4.0)
|
mime-types (>= 1.16, < 4.0)
|
||||||
netrc (~> 0.8)
|
netrc (~> 0.8)
|
||||||
retriable (3.1.1)
|
retriable (3.1.2)
|
||||||
rinku (2.0.0)
|
rinku (2.0.0)
|
||||||
rotp (2.1.2)
|
rotp (2.1.2)
|
||||||
rouge (3.2.0)
|
rouge (3.2.1)
|
||||||
rqrcode (0.7.0)
|
rqrcode (0.7.0)
|
||||||
chunky_png
|
chunky_png
|
||||||
rqrcode-rails3 (0.1.7)
|
rqrcode-rails3 (0.1.7)
|
||||||
|
@ -789,6 +779,8 @@ GEM
|
||||||
rspec-core
|
rspec-core
|
||||||
rspec-set (0.1.3)
|
rspec-set (0.1.3)
|
||||||
rspec-support (3.7.1)
|
rspec-support (3.7.1)
|
||||||
|
rspec_junit_formatter (0.4.1)
|
||||||
|
rspec-core (>= 2, < 4, != 2.12.0)
|
||||||
rspec_profiling (0.0.5)
|
rspec_profiling (0.0.5)
|
||||||
activerecord
|
activerecord
|
||||||
pg
|
pg
|
||||||
|
@ -817,10 +809,10 @@ GEM
|
||||||
sexp_processor (~> 4.1)
|
sexp_processor (~> 4.1)
|
||||||
rubyntlm (0.6.2)
|
rubyntlm (0.6.2)
|
||||||
rubypants (0.2.0)
|
rubypants (0.2.0)
|
||||||
rubyzip (1.2.1)
|
rubyzip (1.2.2)
|
||||||
rufus-scheduler (3.4.0)
|
rufus-scheduler (3.4.0)
|
||||||
et-orbi (~> 1.0)
|
et-orbi (~> 1.0)
|
||||||
rugged (0.27.2)
|
rugged (0.27.4)
|
||||||
safe_yaml (1.0.4)
|
safe_yaml (1.0.4)
|
||||||
sanitize (4.6.6)
|
sanitize (4.6.6)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
|
@ -881,7 +873,6 @@ GEM
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.0)
|
simplecov-html (0.10.0)
|
||||||
slack-notifier (1.5.1)
|
slack-notifier (1.5.1)
|
||||||
slop (3.6.0)
|
|
||||||
spring (2.0.1)
|
spring (2.0.1)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
spring-commands-rspec (1.0.4)
|
spring-commands-rspec (1.0.4)
|
||||||
|
@ -919,8 +910,8 @@ GEM
|
||||||
tilt (2.0.8)
|
tilt (2.0.8)
|
||||||
timecop (0.8.1)
|
timecop (0.8.1)
|
||||||
timfel-krb5-auth (0.8.3)
|
timfel-krb5-auth (0.8.3)
|
||||||
toml (0.1.2)
|
toml (0.2.0)
|
||||||
parslet (~> 1.5.0)
|
parslet (~> 1.8.0)
|
||||||
toml-rb (1.0.0)
|
toml-rb (1.0.0)
|
||||||
citrus (~> 3.0, > 3.0)
|
citrus (~> 3.0, > 3.0)
|
||||||
trollop (2.1.3)
|
trollop (2.1.3)
|
||||||
|
@ -1009,12 +1000,12 @@ DEPENDENCIES
|
||||||
bootsnap (~> 1.3)
|
bootsnap (~> 1.3)
|
||||||
bootstrap_form (~> 2.7.0)
|
bootstrap_form (~> 2.7.0)
|
||||||
brakeman (~> 4.2)
|
brakeman (~> 4.2)
|
||||||
browser (~> 2.2)
|
browser (~> 2.5)
|
||||||
bullet (~> 5.5.0)
|
bullet (~> 5.5.0)
|
||||||
bundler-audit (~> 0.5.0)
|
bundler-audit (~> 0.5.0)
|
||||||
capybara (~> 2.15)
|
capybara (~> 2.15)
|
||||||
capybara-screenshot (~> 1.0.0)
|
capybara-screenshot (~> 1.0.0)
|
||||||
carrierwave (~> 1.2)
|
carrierwave (= 1.2.3)
|
||||||
charlock_holmes (~> 0.7.5)
|
charlock_holmes (~> 0.7.5)
|
||||||
chronic (~> 0.10.2)
|
chronic (~> 0.10.2)
|
||||||
chronic_duration (~> 0.10.6)
|
chronic_duration (~> 0.10.6)
|
||||||
|
@ -1039,26 +1030,24 @@ DEPENDENCIES
|
||||||
faraday (~> 0.12)
|
faraday (~> 0.12)
|
||||||
fast_blank
|
fast_blank
|
||||||
ffaker (~> 2.4)
|
ffaker (~> 2.4)
|
||||||
flay (~> 2.10.0)
|
|
||||||
flipper (~> 0.13.0)
|
flipper (~> 0.13.0)
|
||||||
flipper-active_record (~> 0.13.0)
|
flipper-active_record (~> 0.13.0)
|
||||||
flipper-active_support_cache_store (~> 0.13.0)
|
flipper-active_support_cache_store (~> 0.13.0)
|
||||||
fog-aliyun (~> 0.2.0)
|
fog-aliyun (~> 0.2.0)
|
||||||
fog-aws (~> 2.0.1)
|
fog-aws (~> 2.0.1)
|
||||||
fog-core (~> 1.44)
|
fog-core (~> 1.44)
|
||||||
fog-google (~> 1.3.3)
|
fog-google (~> 1.7.1)
|
||||||
fog-local (~> 0.3)
|
fog-local (~> 0.3)
|
||||||
fog-openstack (~> 0.1)
|
fog-openstack (~> 0.1)
|
||||||
fog-rackspace (~> 0.1.1)
|
fog-rackspace (~> 0.1.1)
|
||||||
font-awesome-rails (~> 4.7)
|
font-awesome-rails (~> 4.7)
|
||||||
foreman (~> 0.84.0)
|
foreman (~> 0.84.0)
|
||||||
fuubar (~> 2.2.0)
|
fuubar (~> 2.2.0)
|
||||||
gemnasium-gitlab-service (~> 0.2)
|
|
||||||
gemojione (~> 3.3)
|
gemojione (~> 3.3)
|
||||||
gettext (~> 3.2.2)
|
gettext (~> 3.2.2)
|
||||||
gettext_i18n_rails (~> 1.8.0)
|
gettext_i18n_rails (~> 1.8.0)
|
||||||
gettext_i18n_rails_js (~> 1.3)
|
gettext_i18n_rails_js (~> 1.3)
|
||||||
gitaly-proto (~> 0.113.0)
|
gitaly-proto (~> 0.117.0)
|
||||||
github-linguist (~> 5.3.3)
|
github-linguist (~> 5.3.3)
|
||||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||||
gitlab-gollum-lib (~> 4.2)
|
gitlab-gollum-lib (~> 4.2)
|
||||||
|
@ -1067,7 +1056,7 @@ DEPENDENCIES
|
||||||
gitlab-styles (~> 2.4)
|
gitlab-styles (~> 2.4)
|
||||||
gitlab_omniauth-ldap (~> 2.0.4)
|
gitlab_omniauth-ldap (~> 2.0.4)
|
||||||
gon (~> 6.2)
|
gon (~> 6.2)
|
||||||
google-api-client (~> 0.19.8)
|
google-api-client (~> 0.23)
|
||||||
google-protobuf (= 3.5.1)
|
google-protobuf (= 3.5.1)
|
||||||
gpgme
|
gpgme
|
||||||
grape (~> 1.0)
|
grape (~> 1.0)
|
||||||
|
@ -1090,13 +1079,14 @@ DEPENDENCIES
|
||||||
influxdb (~> 0.2)
|
influxdb (~> 0.2)
|
||||||
jira-ruby (~> 1.4)
|
jira-ruby (~> 1.4)
|
||||||
jquery-atwho-rails (~> 1.3.2)
|
jquery-atwho-rails (~> 1.3.2)
|
||||||
|
js_regex (~> 2.2.1)
|
||||||
json-schema (~> 2.8.0)
|
json-schema (~> 2.8.0)
|
||||||
jwt (~> 1.5.6)
|
jwt (~> 1.5.6)
|
||||||
kaminari (~> 1.0)
|
kaminari (~> 1.0)
|
||||||
knapsack (~> 1.16)
|
knapsack (~> 1.16)
|
||||||
kubeclient (~> 3.1.0)
|
kubeclient (~> 3.1.0)
|
||||||
letter_opener_web (~> 1.3.0)
|
letter_opener_web (~> 1.3.0)
|
||||||
license_finder (~> 3.1)
|
license_finder (~> 5.4)
|
||||||
licensee (~> 8.9)
|
licensee (~> 8.9)
|
||||||
lograge (~> 0.5)
|
lograge (~> 0.5)
|
||||||
loofah (~> 2.2)
|
loofah (~> 2.2)
|
||||||
|
@ -1147,7 +1137,7 @@ DEPENDENCIES
|
||||||
rails-controller-testing
|
rails-controller-testing
|
||||||
rails-deprecated_sanitizer (~> 1.0.3)
|
rails-deprecated_sanitizer (~> 1.0.3)
|
||||||
rails-i18n (~> 5.1)
|
rails-i18n (~> 5.1)
|
||||||
rainbow (~> 2.2)
|
rainbow (~> 3.0)
|
||||||
raindrops (~> 0.18)
|
raindrops (~> 0.18)
|
||||||
rblineprof (~> 0.3.6)
|
rblineprof (~> 0.3.6)
|
||||||
rbtrace (~> 0.4)
|
rbtrace (~> 0.4)
|
||||||
|
@ -1166,6 +1156,7 @@ DEPENDENCIES
|
||||||
rspec-rails (~> 3.7.0)
|
rspec-rails (~> 3.7.0)
|
||||||
rspec-retry (~> 0.4.5)
|
rspec-retry (~> 0.4.5)
|
||||||
rspec-set (~> 0.1.3)
|
rspec-set (~> 0.1.3)
|
||||||
|
rspec_junit_formatter
|
||||||
rspec_profiling (~> 0.0.5)
|
rspec_profiling (~> 0.0.5)
|
||||||
rubocop (~> 0.54.0)
|
rubocop (~> 0.54.0)
|
||||||
rubocop-rspec (~> 1.22.1)
|
rubocop-rspec (~> 1.22.1)
|
||||||
|
@ -1217,4 +1208,4 @@ DEPENDENCIES
|
||||||
wikicloth (= 0.8.1)
|
wikicloth (= 0.8.1)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.16.3
|
1.16.4
|
||||||
|
|
117
PROCESS.md
|
@ -15,8 +15,9 @@
|
||||||
- [Between the 1st and the 7th](#between-the-1st-and-the-7th)
|
- [Between the 1st and the 7th](#between-the-1st-and-the-7th)
|
||||||
- [On the 7th](#on-the-7th)
|
- [On the 7th](#on-the-7th)
|
||||||
- [After the 7th](#after-the-7th)
|
- [After the 7th](#after-the-7th)
|
||||||
- [Regressions](#regressions)
|
- [Bugs](#bugs)
|
||||||
- [How to manage a regression](#how-to-manage-a-regression)
|
- [Regressions](#regressions)
|
||||||
|
- [Managing bugs](#managing-bugs)
|
||||||
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
|
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
|
||||||
- [Retrospective](#retrospective)
|
- [Retrospective](#retrospective)
|
||||||
- [Kickoff](#kickoff)
|
- [Kickoff](#kickoff)
|
||||||
|
@ -115,6 +116,11 @@ target. However, if one does and falls into either of the above categories, it's
|
||||||
the reviewer's responsibility to manage the above communication and assignment
|
the reviewer's responsibility to manage the above communication and assignment
|
||||||
on behalf of the community member.
|
on behalf of the community member.
|
||||||
|
|
||||||
|
Every new feature or change should be shipped with its corresponding documentation
|
||||||
|
in accordance with the
|
||||||
|
[documentation process](https://docs.gitlab.com/ee/development/documentation/workflow.html)
|
||||||
|
and [structure](https://docs.gitlab.com/ee/development/documentation/structure.html).
|
||||||
|
|
||||||
#### What happens if these deadlines are missed?
|
#### What happens if these deadlines are missed?
|
||||||
|
|
||||||
If a small or large feature is _not_ with a maintainer or reviewer by the
|
If a small or large feature is _not_ with a maintainer or reviewer by the
|
||||||
|
@ -140,14 +146,9 @@ and to prevent any last minute surprises.
|
||||||
|
|
||||||
### On the 7th
|
### On the 7th
|
||||||
|
|
||||||
Merge requests should still be complete, following the
|
Merge requests should still be complete, following the [definition of done][done].
|
||||||
[definition of done][done]. The single exception is documentation, and this can
|
|
||||||
only be left until after the freeze if:
|
|
||||||
|
|
||||||
* There is a follow-up issue to add documentation.
|
#### Feature merge requests
|
||||||
* It is assigned to the person writing documentation for this feature, and they
|
|
||||||
are aware of it.
|
|
||||||
* It is in the correct milestone, with the ~Deliverable label.
|
|
||||||
|
|
||||||
If a merge request is not ready, but the developers and Product Manager
|
If a merge request is not ready, but the developers and Product Manager
|
||||||
responsible for the feature think it is essential that it is in the release,
|
responsible for the feature think it is essential that it is in the release,
|
||||||
|
@ -163,15 +164,32 @@ information, see
|
||||||
[Automatic CE->EE merge][automatic_ce_ee_merge] and
|
[Automatic CE->EE merge][automatic_ce_ee_merge] and
|
||||||
[Guidelines for implementing Enterprise Edition features][ee_features].
|
[Guidelines for implementing Enterprise Edition features][ee_features].
|
||||||
|
|
||||||
|
#### Documentation merge requests
|
||||||
|
|
||||||
|
Documentation is part of the product and must be shipped with the feature.
|
||||||
|
|
||||||
|
The single exception for the feature freeze is documentation, and it can
|
||||||
|
be left to be **merged up to the 14th** if:
|
||||||
|
|
||||||
|
* There is a follow-up issue to add documentation.
|
||||||
|
* It is assigned to the developer writing documentation for this feature, and they
|
||||||
|
are aware of it.
|
||||||
|
* It is in the correct milestone, with the labels ~Documentation, ~Deliverable,
|
||||||
|
~missed-deliverable, and "pick into X.Y" applied.
|
||||||
|
* It must be reviewed and approved by a technical writer.
|
||||||
|
|
||||||
|
For more information read the process for
|
||||||
|
[documentation shipped late](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late).
|
||||||
|
|
||||||
### After the 7th
|
### After the 7th
|
||||||
|
|
||||||
Once the stable branch is frozen, the only MRs that can be cherry-picked into
|
Once the stable branch is frozen, the only MRs that can be cherry-picked into
|
||||||
the stable branch are:
|
the stable branch are:
|
||||||
|
|
||||||
* Fixes for [regressions](#regressions)
|
* Fixes for [regressions](#regressions) where the affected version `xx.x` in `regression:xx.x` is the current release. See [Managing bugs](#managing-bugs) section.
|
||||||
* Fixes for security issues
|
* Fixes for security issues
|
||||||
* Fixes or improvements to automated QA scenarios
|
* Fixes or improvements to automated QA scenarios
|
||||||
* Documentation updates for changes in the same release
|
* [Documentation updates](https://docs.gitlab.com/ee/development/documentation/workflow.html#documentation-shipped-late) for changes in the same release
|
||||||
* New or updated translations (as long as they do not touch application code)
|
* New or updated translations (as long as they do not touch application code)
|
||||||
|
|
||||||
During the feature freeze all merge requests that are meant to go into the
|
During the feature freeze all merge requests that are meant to go into the
|
||||||
|
@ -201,48 +219,59 @@ you can ask for an exception to be made.
|
||||||
|
|
||||||
Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md) about how to open an exception request before opening one.
|
Check [this guide](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md) about how to open an exception request before opening one.
|
||||||
|
|
||||||
## Regressions
|
## Bugs
|
||||||
|
|
||||||
A regression for a particular monthly release is a bug that exists in that
|
A ~bug is a defect, error, failure which causes the system to behave incorrectly or prevents it from fulfilling the product requirements.
|
||||||
release, but wasn't present in the release before. This includes bugs in
|
|
||||||
features that were only added in that monthly release. Every regression **must**
|
|
||||||
have the milestone of the release it was introduced in - if a regression doesn't
|
|
||||||
have a milestone, it might be 'just' a bug!
|
|
||||||
|
|
||||||
For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
|
The level of impact of a ~bug can vary from blocking a whole functionality
|
||||||
then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
|
or a feature usability bug. A bug should always be linked to a severity level.
|
||||||
reintroduces the bug, then this bug is still a regression in 10.5.
|
Refer to our [severity levels](../CONTRIBUTING.md#severity-labels)
|
||||||
|
|
||||||
Because GitLab.com runs release candidates of new releases, a regression can be
|
Whether the bug is also a regression or not, the triage process should start as soon as possible.
|
||||||
reported in a release before its 'official' release date on the 22nd of the
|
Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed.
|
||||||
month. When we say 'the most recent monthly release', this can refer to either
|
|
||||||
the version currently running on GitLab.com, or the most recent version
|
|
||||||
available in the package repositories.
|
|
||||||
|
|
||||||
### How to manage a regression
|
### Regressions
|
||||||
|
|
||||||
Regressions are very important, and they should be considered high priority
|
A ~regression implies that a previously **verified working functionality** no longer works.
|
||||||
issues that should be solved as soon as possible, especially if they affect
|
Regressions are a subset of bugs. We use the ~regression label to imply that the defect caused the functionality to regress.
|
||||||
users. Despite that, ~regression label itself does not imply when the issue
|
The label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule.
|
||||||
will be scheduled.
|
|
||||||
|
|
||||||
When a regression is found:
|
The regression label does not apply to ~bugs for new features for which functionality was **never verified as working**.
|
||||||
1. Create an issue describing the problem in the most detailed way possible
|
These, by definition, are not regressions.
|
||||||
1. If possible, provide links to real examples and how to reproduce the problem
|
|
||||||
|
A regression should always have the `regression:xx.x` label on it to designate when it was introduced.
|
||||||
|
|
||||||
|
Regressions should be considered high priority issues that should be solved as soon as possible, especially if they have severe impact on users.
|
||||||
|
|
||||||
|
### Managing bugs
|
||||||
|
|
||||||
|
**Prioritization:** We give higher priority to regressions on features that worked in the last recent monthly release and the current release candidates.
|
||||||
|
The two scenarios below can [bypass the exception request in the release process](https://gitlab.com/gitlab-org/release/docs/blob/master/general/exception-request/process.md#after-the-7th), where the affected regression version matches the current monthly release version.
|
||||||
|
* A regression which worked in the **Last monthly release**
|
||||||
|
* **Example:** In 11.0 we released a new `feature X` that is verified as working. Then in release 11.1 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label.
|
||||||
|
* *Note:* When we say `the last recent monthly release`, this can refer to either the version currently running on GitLab.com, or the most recent version available in the package repositories.
|
||||||
|
* A regression which worked in the **Current release candidates**
|
||||||
|
* **Example:** In 11.1-RC3 we shipped a new feature which has been verified as working. Then in 11.1-RC5 the feature no longer works, this is regression for 11.1. The issue should have the `regression:11.1` label.
|
||||||
|
* *Note:* Because GitLab.com runs release candidates of new releases, a regression can be reported in a release before its 'official' release date on the 22nd of the month.
|
||||||
|
|
||||||
|
When a bug is found:
|
||||||
|
1. Create an issue describing the problem in the most detailed way possible.
|
||||||
|
1. If possible, provide links to real examples and how to reproduce the problem.
|
||||||
1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels),
|
1. Label the issue properly, using the [team label](../CONTRIBUTING.md#team-labels),
|
||||||
the [subject label](../CONTRIBUTING.md#subject-labels)
|
the [subject label](../CONTRIBUTING.md#subject-labels)
|
||||||
and any other label that may apply in the specific case
|
and any other label that may apply in the specific case
|
||||||
1. Add the ~bug and ~regression labels
|
1. Notify the respective Engineering Manager to evaluate and apply the [Severity label](../CONTRIBUTING.md#bug-severity-labels) and [Priority label](../CONTRIBUTING.md#bug-priority-labels).
|
||||||
1. Notify the respective Engineering Manager to evaluate the Severity of the regression and add a [Severity label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-severity-labels). The counterpart Product Manager is included to weigh-in on prioritization as needed to set the [Priority label](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#bug-priority-labels).
|
The counterpart Product Manager is included to weigh-in on prioritization as needed.
|
||||||
1. If the regression is either an ~S1, ~S2 or ~S3 severity, label the regression with the current milestone as it should be fixed in the current milestone.
|
1. If the ~bug is **NOT** a regression:
|
||||||
1. If the regression was introduced in an RC of the current release, label with ~Deliverable
|
1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied.
|
||||||
1. If the regression was introduced in the previous release, label with ~"Next Patch Release"
|
1. If the bug is a ~regression:
|
||||||
1. If the regression is an ~S4 severity, the regression may be scheduled for later milestones at the discretion of Engineering Manager and Product Manager.
|
1. Determine the release that the regression affects and add the corresponding `regression:xx.x` label.
|
||||||
|
1. If the affected release version can't be determined, add the generic ~regression label for the time being.
|
||||||
When a new issue is found, the fix should start as soon as possible. You can
|
1. If the affected version `xx.x` in `regression:xx.x` is the **current release**, it's recommended to schedule the fix for the current milestone.
|
||||||
ping the Engineering Manager or the Product Manager for the relative area to
|
1. This falls under regressions which worked in the last release and the current RCs. More detailed explanations in the **Prioritization** section above.
|
||||||
make them aware of the issue earlier. They will analyze the priority and change
|
1. If the affected version `xx.x` in `regression:xx.x` is older than the **current release**
|
||||||
it if needed.
|
1. If the regression is an ~S1 severity, it's recommended to schedule the fix for the current milestone. We would like to fix the highest severity regression as soon as we can.
|
||||||
|
1. If the regression is an ~S2, ~S3 or ~S4 severity, the regression may be scheduled for later milestones at the discretion of the Engineering Manager and Product Manager.
|
||||||
|
|
||||||
## Release retrospective and kickoff
|
## Release retrospective and kickoff
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ You can access a new installation with the login **`root`** and password **`5ive
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
GitLab is an open source project and we are very happy to accept community contributions. Please refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
|
GitLab is an open source project and we are very happy to accept community contributions. Please refer to [Contributing to GitLab page](https://about.gitlab.com/contributing/) for more details.
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ GitLab Community Edition (CE) is available freely under the MIT Expat license.
|
||||||
|
|
||||||
All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component.
|
All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component.
|
||||||
|
|
||||||
All Documentation content that resides under the doc/ directory of this repository is licensed under Creative Commons: CC BY-SA 4.0.
|
All Documentation content that resides under the `doc/` directory of this repository is licensed under Creative Commons: CC BY-SA 4.0.
|
||||||
|
|
||||||
## Install a development environment
|
## Install a development environment
|
||||||
|
|
||||||
|
|
2
VERSION
|
@ -1 +1 @@
|
||||||
11.2.8
|
11.3.10
|
||||||
|
|
BIN
app/assets/images/auth_buttons/auth0_64.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 199 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
app/assets/images/auth_buttons/jwt_64.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
app/assets/images/auth_buttons/shibboleth_64.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
|
@ -15,6 +15,7 @@ const Api = {
|
||||||
mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
|
mergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
|
||||||
mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
|
mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
|
||||||
groupLabelsPath: '/groups/:namespace_path/-/labels',
|
groupLabelsPath: '/groups/:namespace_path/-/labels',
|
||||||
|
templatesPath: '/api/:version/templates/:key',
|
||||||
licensePath: '/api/:version/templates/licenses/:key',
|
licensePath: '/api/:version/templates/licenses/:key',
|
||||||
gitignorePath: '/api/:version/templates/gitignores/:key',
|
gitignorePath: '/api/:version/templates/gitignores/:key',
|
||||||
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
|
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
|
||||||
|
@ -265,6 +266,12 @@ const Api = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
templates(key, params = {}) {
|
||||||
|
const url = Api.buildUrl(this.templatesPath).replace(':key', key);
|
||||||
|
|
||||||
|
return axios.get(url, { params });
|
||||||
|
},
|
||||||
|
|
||||||
buildUrl(url) {
|
buildUrl(url) {
|
||||||
let urlRoot = '';
|
let urlRoot = '';
|
||||||
if (gon.relative_url_root != null) {
|
if (gon.relative_url_root != null) {
|
||||||
|
|
|
@ -109,8 +109,6 @@ export class AwardsHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
const $menu = $(`.${this.menuClass}`);
|
const $menu = $(`.${this.menuClass}`);
|
||||||
const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
|
|
||||||
const $userAuthored = this.isUserAuthored($addBtn);
|
|
||||||
if ($menu.length) {
|
if ($menu.length) {
|
||||||
if ($menu.is('.is-visible')) {
|
if ($menu.is('.is-visible')) {
|
||||||
$addBtn.removeClass('is-active');
|
$addBtn.removeClass('is-active');
|
||||||
|
@ -134,9 +132,6 @@ export class AwardsHandler {
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$thumbsBtn.toggleClass('disabled', $userAuthored);
|
|
||||||
$thumbsBtn.prop('disabled', $userAuthored);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the emoji menu with the first category of emojis.
|
// Create the emoji menu with the first category of emojis.
|
||||||
|
@ -364,10 +359,6 @@ export class AwardsHandler {
|
||||||
return $emojiButton.hasClass('active');
|
return $emojiButton.hasClass('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
isUserAuthored($button) {
|
|
||||||
return $button.hasClass('js-user-authored');
|
|
||||||
}
|
|
||||||
|
|
||||||
decrementCounter($emojiButton, emoji) {
|
decrementCounter($emojiButton, emoji) {
|
||||||
const counter = $('.js-counter', $emojiButton);
|
const counter = $('.js-counter', $emojiButton);
|
||||||
const counterNumber = parseInt(counter.text(), 10);
|
const counterNumber = parseInt(counter.text(), 10);
|
||||||
|
@ -474,20 +465,16 @@ export class AwardsHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
postEmoji($emojiButton, awardUrl, emoji, callback) {
|
postEmoji($emojiButton, awardUrl, emoji, callback) {
|
||||||
if (this.isUserAuthored($emojiButton)) {
|
axios
|
||||||
this.userAuthored($emojiButton);
|
.post(awardUrl, {
|
||||||
} else {
|
name: emoji,
|
||||||
axios
|
})
|
||||||
.post(awardUrl, {
|
.then(({ data }) => {
|
||||||
name: emoji,
|
if (data.ok) {
|
||||||
})
|
callback();
|
||||||
.then(({ data }) => {
|
}
|
||||||
if (data.ok) {
|
})
|
||||||
callback();
|
.catch(() => flash(__('Something went wrong on our end.')));
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => flash(__('Something went wrong on our end.')));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findEmojiIcon(votesBlock, emoji) {
|
findEmojiIcon(votesBlock, emoji) {
|
||||||
|
|
|
@ -23,6 +23,11 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
wasValidated: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
'badgeInAddForm',
|
'badgeInAddForm',
|
||||||
|
@ -39,16 +44,6 @@ export default {
|
||||||
|
|
||||||
return this.badgeInAddForm;
|
return this.badgeInAddForm;
|
||||||
},
|
},
|
||||||
canSubmit() {
|
|
||||||
return (
|
|
||||||
this.badge !== null &&
|
|
||||||
this.badge.imageUrl &&
|
|
||||||
this.badge.imageUrl.trim() !== '' &&
|
|
||||||
this.badge.linkUrl &&
|
|
||||||
this.badge.linkUrl.trim() !== '' &&
|
|
||||||
!this.isSaving
|
|
||||||
);
|
|
||||||
},
|
|
||||||
helpText() {
|
helpText() {
|
||||||
const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
|
const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
|
||||||
.map(placeholder => `<code>%{${placeholder}}</code>`)
|
.map(placeholder => `<code>%{${placeholder}}</code>`)
|
||||||
|
@ -93,11 +88,18 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
submitButtonLabel() {
|
badgeImageUrlExample() {
|
||||||
if (this.isEditing) {
|
const exampleUrl =
|
||||||
return s__('Badges|Save changes');
|
'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/badge.svg';
|
||||||
}
|
return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
|
||||||
return s__('Badges|Add badge');
|
exampleUrl,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
badgeLinkUrlExample() {
|
||||||
|
const exampleUrl = 'https://example.gitlab.com/%{project_path}';
|
||||||
|
return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
|
||||||
|
exampleUrl,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -109,7 +111,9 @@ export default {
|
||||||
this.stopEditing();
|
this.stopEditing();
|
||||||
},
|
},
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
if (!this.canSubmit) {
|
const form = this.$el;
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
this.wasValidated = true;
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +121,7 @@ export default {
|
||||||
return this.saveBadge()
|
return this.saveBadge()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
createFlash(s__('Badges|The badge was saved.'), 'notice');
|
createFlash(s__('Badges|The badge was saved.'), 'notice');
|
||||||
|
this.wasValidated = false;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
createFlash(
|
createFlash(
|
||||||
|
@ -129,6 +134,7 @@ export default {
|
||||||
return this.addBadge()
|
return this.addBadge()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
createFlash(s__('Badges|A new badge was added.'), 'notice');
|
createFlash(s__('Badges|A new badge was added.'), 'notice');
|
||||||
|
this.wasValidated = false;
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
createFlash(
|
createFlash(
|
||||||
|
@ -138,47 +144,58 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
badgeImageUrlPlaceholder:
|
|
||||||
'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/<badge>.svg',
|
|
||||||
badgeLinkUrlPlaceholder: 'https://example.gitlab.com/%{project_path}',
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form
|
<form
|
||||||
class="prepend-top-default append-bottom-default"
|
:class="{ 'was-validated': wasValidated }"
|
||||||
|
class="prepend-top-default append-bottom-default needs-validation"
|
||||||
|
novalidate
|
||||||
@submit.prevent.stop="onSubmit"
|
@submit.prevent.stop="onSubmit"
|
||||||
>
|
>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="badge-link-url">{{ s__('Badges|Link') }}</label>
|
<label
|
||||||
|
for="badge-link-url"
|
||||||
|
class="label-bold"
|
||||||
|
>{{ s__('Badges|Link') }}</label>
|
||||||
|
<p v-html="helpText"></p>
|
||||||
<input
|
<input
|
||||||
id="badge-link-url"
|
id="badge-link-url"
|
||||||
v-model="linkUrl"
|
v-model="linkUrl"
|
||||||
:placeholder="$options.badgeLinkUrlPlaceholder"
|
type="URL"
|
||||||
type="text"
|
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
required
|
||||||
@input="debouncedPreview"
|
@input="debouncedPreview"
|
||||||
/>
|
/>
|
||||||
<span
|
<div class="invalid-feedback">
|
||||||
class="form-text text-muted"
|
{{ s__('Badges|Please fill in a valid URL') }}
|
||||||
v-html="helpText"
|
</div>
|
||||||
></span>
|
<span class="form-text text-muted">
|
||||||
|
{{ badgeLinkUrlExample }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="badge-image-url">{{ s__('Badges|Badge image URL') }}</label>
|
<label
|
||||||
|
for="badge-image-url"
|
||||||
|
class="label-bold"
|
||||||
|
>{{ s__('Badges|Badge image URL') }}</label>
|
||||||
|
<p v-html="helpText"></p>
|
||||||
<input
|
<input
|
||||||
id="badge-image-url"
|
id="badge-image-url"
|
||||||
v-model="imageUrl"
|
v-model="imageUrl"
|
||||||
:placeholder="$options.badgeImageUrlPlaceholder"
|
type="URL"
|
||||||
type="text"
|
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
required
|
||||||
@input="debouncedPreview"
|
@input="debouncedPreview"
|
||||||
/>
|
/>
|
||||||
<span
|
<div class="invalid-feedback">
|
||||||
class="form-text text-muted"
|
{{ s__('Badges|Please fill in a valid URL') }}
|
||||||
v-html="helpText"
|
</div>
|
||||||
></span>
|
<span class="form-text text-muted">
|
||||||
|
{{ badgeImageUrlExample }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -200,20 +217,32 @@ export default {
|
||||||
>{{ s__('Badges|No image to preview') }}</p>
|
>{{ s__('Badges|No image to preview') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row-content-block">
|
<div
|
||||||
|
v-if="isEditing"
|
||||||
|
class="row-content-block"
|
||||||
|
>
|
||||||
<loading-button
|
<loading-button
|
||||||
:disabled="!canSubmit"
|
|
||||||
:loading="isSaving"
|
:loading="isSaving"
|
||||||
:label="submitButtonLabel"
|
:label="s__('Badges|Save changes')"
|
||||||
type="submit"
|
type="submit"
|
||||||
container-class="btn btn-success"
|
container-class="btn btn-success"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="isEditing"
|
|
||||||
class="btn btn-cancel"
|
class="btn btn-cancel"
|
||||||
type="button"
|
type="button"
|
||||||
@click="onCancel"
|
@click="onCancel"
|
||||||
>{{ __('Cancel') }}</button>
|
>{{ __('Cancel') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="form-group"
|
||||||
|
>
|
||||||
|
<loading-button
|
||||||
|
:loading="isSaving"
|
||||||
|
:label="s__('Badges|Add badge')"
|
||||||
|
type="submit"
|
||||||
|
container-class="btn btn-success"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default {
|
||||||
{{ s__('Badges|Your badges') }}
|
{{ s__('Badges|Your badges') }}
|
||||||
<span
|
<span
|
||||||
v-show="!isLoading"
|
v-show="!isLoading"
|
||||||
class="badge"
|
class="badge badge-pill"
|
||||||
>{{ badges.length }}</span>
|
>{{ badges.length }}</span>
|
||||||
</div>
|
</div>
|
||||||
<loading-icon
|
<loading-icon
|
||||||
|
|
|
@ -43,13 +43,13 @@ export default {
|
||||||
<badge
|
<badge
|
||||||
:image-url="badge.renderedImageUrl"
|
:image-url="badge.renderedImageUrl"
|
||||||
:link-url="badge.renderedLinkUrl"
|
:link-url="badge.renderedLinkUrl"
|
||||||
class="table-section section-30"
|
class="table-section section-40"
|
||||||
/>
|
/>
|
||||||
<span class="table-section section-50 str-truncated">{{ badge.linkUrl }}</span>
|
<span class="table-section section-30 str-truncated">{{ badge.linkUrl }}</span>
|
||||||
<div class="table-section section-10">
|
<div class="table-section section-15">
|
||||||
<span class="badge">{{ badgeKindText }}</span>
|
<span class="badge badge-pill">{{ badgeKindText }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-section section-10 table-button-footer">
|
<div class="table-section section-15 table-button-footer">
|
||||||
<div
|
<div
|
||||||
v-if="canEditBadge"
|
v-if="canEditBadge"
|
||||||
class="table-action-buttons">
|
class="table-action-buttons">
|
||||||
|
|
|
@ -203,7 +203,7 @@ export default {
|
||||||
this.showIssueForm = !this.showIssueForm;
|
this.showIssueForm = !this.showIssueForm;
|
||||||
},
|
},
|
||||||
onScroll() {
|
onScroll() {
|
||||||
if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
|
if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
|
||||||
this.loadNextPage();
|
this.loadNextPage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import Flash from '../../flash';
|
import Flash from '../../flash';
|
||||||
import { __ } from '../../locale';
|
import { sprintf, __ } from '../../locale';
|
||||||
import Sidebar from '../../right_sidebar';
|
import Sidebar from '../../right_sidebar';
|
||||||
import eventHub from '../../sidebar/event_hub';
|
import eventHub from '../../sidebar/event_hub';
|
||||||
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
|
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title.vue';
|
||||||
|
@ -55,8 +55,10 @@ gl.issueBoards.BoardSidebar = Vue.extend({
|
||||||
return this.issue.labels && this.issue.labels.length;
|
return this.issue.labels && this.issue.labels.length;
|
||||||
},
|
},
|
||||||
labelDropdownTitle() {
|
labelDropdownTitle() {
|
||||||
return this.hasLabels ?
|
return this.hasLabels ? sprintf(__('%{firstLabel} +%{labelCount} more'), {
|
||||||
`${this.issue.labels[0].title} ${this.issue.labels.length - 1}+ more` : 'Label';
|
firstLabel: this.issue.labels[0].title,
|
||||||
|
labelCount: this.issue.labels.length - 1
|
||||||
|
}) : __('Label');
|
||||||
},
|
},
|
||||||
selectedLabels() {
|
selectedLabels() {
|
||||||
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
|
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
/* global ListIssue */
|
/* global ListIssue */
|
||||||
import queryData from '~/boards/utils/query_data';
|
import { urlParamsToObject } from '~/lib/utils/common_utils';
|
||||||
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||||
import ModalHeader from './header.vue';
|
import ModalHeader from './header.vue';
|
||||||
import ModalList from './list.vue';
|
import ModalList from './list.vue';
|
||||||
|
@ -109,13 +109,11 @@
|
||||||
loadIssues(clearIssues = false) {
|
loadIssues(clearIssues = false) {
|
||||||
if (!this.showAddIssuesModal) return false;
|
if (!this.showAddIssuesModal) return false;
|
||||||
|
|
||||||
return gl.boardService
|
return gl.boardService.getBacklog({
|
||||||
.getBacklog(
|
...urlParamsToObject(this.filter.path),
|
||||||
queryData(this.filter.path, {
|
page: this.page,
|
||||||
page: this.page,
|
per: this.perPage,
|
||||||
per: this.perPage,
|
})
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then(res => res.data)
|
.then(res => res.data)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (clearIssues) {
|
if (clearIssues) {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len */
|
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len */
|
||||||
/* global ListIssue */
|
/* global ListIssue */
|
||||||
|
|
||||||
|
import { __ } from '~/locale';
|
||||||
import ListLabel from '~/vue_shared/models/label';
|
import ListLabel from '~/vue_shared/models/label';
|
||||||
import ListAssignee from '~/vue_shared/models/assignee';
|
import ListAssignee from '~/vue_shared/models/assignee';
|
||||||
import queryData from '../utils/query_data';
|
import { urlParamsToObject } from '~/lib/utils/common_utils';
|
||||||
|
|
||||||
const PER_PAGE = 20;
|
const PER_PAGE = 20;
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ class List {
|
||||||
this.id = obj.id;
|
this.id = obj.id;
|
||||||
this._uid = this.guid();
|
this._uid = this.guid();
|
||||||
this.position = obj.position;
|
this.position = obj.position;
|
||||||
this.title = obj.title;
|
this.title = obj.list_type === 'backlog' ? __('Open') : obj.title;
|
||||||
this.type = obj.list_type;
|
this.type = obj.list_type;
|
||||||
|
|
||||||
const typeInfo = this.getTypeInfo(this.type);
|
const typeInfo = this.getTypeInfo(this.type);
|
||||||
|
@ -114,7 +115,10 @@ class List {
|
||||||
}
|
}
|
||||||
|
|
||||||
getIssues(emptyIssues = true) {
|
getIssues(emptyIssues = true) {
|
||||||
const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page });
|
const data = {
|
||||||
|
...urlParamsToObject(gl.issueBoards.BoardsStore.filter.path),
|
||||||
|
page: this.page,
|
||||||
|
};
|
||||||
|
|
||||||
if (this.label && data.label_name) {
|
if (this.label && data.label_name) {
|
||||||
data.label_name = data.label_name.filter(label => label !== this.label.title);
|
data.label_name = data.label_name.filter(label => label !== this.label.title);
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
export default (path, extraData) => path.split('&').reduce((dataParam, filterParam) => {
|
|
||||||
if (filterParam === '') return dataParam;
|
|
||||||
|
|
||||||
const data = dataParam;
|
|
||||||
const paramSplit = filterParam.split('=');
|
|
||||||
const paramKeyNormalized = paramSplit[0].replace('[]', '');
|
|
||||||
const isArray = paramSplit[0].indexOf('[]');
|
|
||||||
const value = decodeURIComponent(paramSplit[1].replace(/\+/g, ' '));
|
|
||||||
|
|
||||||
if (isArray !== -1) {
|
|
||||||
if (!data[paramKeyNormalized]) {
|
|
||||||
data[paramKeyNormalized] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
data[paramKeyNormalized].push(value);
|
|
||||||
} else {
|
|
||||||
data[paramKeyNormalized] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}, extraData);
|
|
|
@ -1,16 +1,12 @@
|
||||||
import Visibility from 'visibilityjs';
|
import Visibility from 'visibilityjs';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
import initDismissableCallout from '~/dismissable_callout';
|
||||||
import { s__, sprintf } from '../locale';
|
import { s__, sprintf } from '../locale';
|
||||||
import Flash from '../flash';
|
import Flash from '../flash';
|
||||||
import Poll from '../lib/utils/poll';
|
import Poll from '../lib/utils/poll';
|
||||||
import initSettingsPanels from '../settings_panels';
|
import initSettingsPanels from '../settings_panels';
|
||||||
import eventHub from './event_hub';
|
import eventHub from './event_hub';
|
||||||
import {
|
import { APPLICATION_STATUS, REQUEST_LOADING, REQUEST_SUCCESS, REQUEST_FAILURE } from './constants';
|
||||||
APPLICATION_STATUS,
|
|
||||||
REQUEST_LOADING,
|
|
||||||
REQUEST_SUCCESS,
|
|
||||||
REQUEST_FAILURE,
|
|
||||||
} from './constants';
|
|
||||||
import ClustersService from './services/clusters_service';
|
import ClustersService from './services/clusters_service';
|
||||||
import ClustersStore from './stores/clusters_store';
|
import ClustersStore from './stores/clusters_store';
|
||||||
import applications from './components/applications.vue';
|
import applications from './components/applications.vue';
|
||||||
|
@ -66,6 +62,7 @@ export default class Clusters {
|
||||||
this.showTokenButton = document.querySelector('.js-show-cluster-token');
|
this.showTokenButton = document.querySelector('.js-show-cluster-token');
|
||||||
this.tokenField = document.querySelector('.js-cluster-token');
|
this.tokenField = document.querySelector('.js-cluster-token');
|
||||||
|
|
||||||
|
initDismissableCallout('.js-cluster-security-warning');
|
||||||
initSettingsPanels();
|
initSettingsPanels();
|
||||||
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
|
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
|
||||||
this.initApplications();
|
this.initApplications();
|
||||||
|
@ -129,7 +126,8 @@ export default class Clusters {
|
||||||
if (!Visibility.hidden()) {
|
if (!Visibility.hidden()) {
|
||||||
this.poll.makeRequest();
|
this.poll.makeRequest();
|
||||||
} else {
|
} else {
|
||||||
this.service.fetchData()
|
this.service
|
||||||
|
.fetchData()
|
||||||
.then(data => this.handleSuccess(data))
|
.then(data => this.handleSuccess(data))
|
||||||
.catch(() => Clusters.handleError());
|
.catch(() => Clusters.handleError());
|
||||||
}
|
}
|
||||||
|
@ -177,15 +175,21 @@ export default class Clusters {
|
||||||
|
|
||||||
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
|
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
|
||||||
const appTitles = Object.keys(newApplicationMap)
|
const appTitles = Object.keys(newApplicationMap)
|
||||||
.filter(appId => newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
|
.filter(
|
||||||
prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
|
appId =>
|
||||||
prevApplicationMap[appId].status !== null)
|
newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
|
||||||
|
prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
|
||||||
|
prevApplicationMap[appId].status !== null,
|
||||||
|
)
|
||||||
.map(appId => newApplicationMap[appId].title);
|
.map(appId => newApplicationMap[appId].title);
|
||||||
|
|
||||||
if (appTitles.length > 0) {
|
if (appTitles.length > 0) {
|
||||||
const text = sprintf(s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'), {
|
const text = sprintf(
|
||||||
appList: appTitles.join(', '),
|
s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'),
|
||||||
});
|
{
|
||||||
|
appList: appTitles.join(', '),
|
||||||
|
},
|
||||||
|
);
|
||||||
Flash(text, 'notice', this.successApplicationContainer);
|
Flash(text, 'notice', this.successApplicationContainer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,13 +222,18 @@ export default class Clusters {
|
||||||
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_LOADING);
|
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_LOADING);
|
||||||
this.store.updateAppProperty(appId, 'requestReason', null);
|
this.store.updateAppProperty(appId, 'requestReason', null);
|
||||||
|
|
||||||
this.service.installApplication(appId, data.params)
|
this.service
|
||||||
|
.installApplication(appId, data.params)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUCCESS);
|
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUCCESS);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_FAILURE);
|
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_FAILURE);
|
||||||
this.store.updateAppProperty(appId, 'requestReason', s__('ClusterIntegration|Request to begin installing failed'));
|
this.store.updateAppProperty(
|
||||||
|
appId,
|
||||||
|
'requestReason',
|
||||||
|
s__('ClusterIntegration|Request to begin installing failed'),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import setupToggleButtons from '~/toggle_buttons';
|
import setupToggleButtons from '~/toggle_buttons';
|
||||||
import gcpSignupOffer from '~/clusters/components/gcp_signup_offer';
|
import initDismissableCallout from '~/dismissable_callout';
|
||||||
|
|
||||||
import ClustersService from './services/clusters_service';
|
import ClustersService from './services/clusters_service';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const clusterList = document.querySelector('.js-clusters-list');
|
const clusterList = document.querySelector('.js-clusters-list');
|
||||||
|
|
||||||
gcpSignupOffer();
|
initDismissableCallout('.gcp-signup-offer');
|
||||||
|
|
||||||
// The empty state won't have a clusterList
|
// The empty state won't have a clusterList
|
||||||
if (clusterList) {
|
if (clusterList) {
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import progressBar from '@gitlab-org/gitlab-ui/dist/base/progress_bar';
|
import progressBar from '@gitlab-org/gitlab-ui/dist/components/base/progress_bar';
|
||||||
|
import modal from '@gitlab-org/gitlab-ui/dist/components/base/modal';
|
||||||
|
|
||||||
|
import dModal from '@gitlab-org/gitlab-ui/dist/directives/modal';
|
||||||
|
|
||||||
Vue.component('gl-progress-bar', progressBar);
|
Vue.component('gl-progress-bar', progressBar);
|
||||||
|
Vue.component('gl-ui-modal', modal);
|
||||||
|
|
||||||
|
Vue.directive('gl-modal', dModal);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* eslint-disable object-shorthand, func-names, comma-dangle, no-else-return, quotes */
|
/* eslint-disable object-shorthand, func-names, no-else-return */
|
||||||
/* global CommentsStore */
|
/* global CommentsStore */
|
||||||
/* global ResolveService */
|
/* global ResolveService */
|
||||||
|
|
||||||
|
@ -25,44 +25,44 @@ const ResolveDiscussionBtn = Vue.extend({
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showButton: function () {
|
showButton: function() {
|
||||||
if (this.discussion) {
|
if (this.discussion) {
|
||||||
return this.discussion.isResolvable();
|
return this.discussion.isResolvable();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isDiscussionResolved: function () {
|
isDiscussionResolved: function() {
|
||||||
if (this.discussion) {
|
if (this.discussion) {
|
||||||
return this.discussion.isResolved();
|
return this.discussion.isResolved();
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
buttonText: function () {
|
buttonText: function() {
|
||||||
if (this.isDiscussionResolved) {
|
if (this.isDiscussionResolved) {
|
||||||
return "Unresolve discussion";
|
return 'Unresolve discussion';
|
||||||
} else {
|
} else {
|
||||||
return "Resolve discussion";
|
return 'Resolve discussion';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
loading: function () {
|
loading: function() {
|
||||||
if (this.discussion) {
|
if (this.discussion) {
|
||||||
return this.discussion.loading;
|
return this.discussion.loading;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function() {
|
||||||
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
|
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
|
||||||
|
|
||||||
this.discussion = CommentsStore.state[this.discussionId];
|
this.discussion = CommentsStore.state[this.discussionId];
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
resolve: function () {
|
resolve: function() {
|
||||||
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
|
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,7 @@ window.gl = window.gl || {};
|
||||||
|
|
||||||
class ResolveServiceClass {
|
class ResolveServiceClass {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
this.noteResource = Vue.resource(
|
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve?html=true`);
|
||||||
`${root}/notes{/noteId}/resolve?html=true`,
|
|
||||||
);
|
|
||||||
this.discussionResource = Vue.resource(
|
this.discussionResource = Vue.resource(
|
||||||
`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`,
|
`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`,
|
||||||
);
|
);
|
||||||
|
@ -51,10 +49,7 @@ class ResolveServiceClass {
|
||||||
discussion.updateHeadline(data);
|
discussion.updateHeadline(data);
|
||||||
})
|
})
|
||||||
.catch(
|
.catch(
|
||||||
() =>
|
() => new Flash('An error occurred when trying to resolve a discussion. Please try again.'),
|
||||||
new Flash(
|
|
||||||
'An error occurred when trying to resolve a discussion. Please try again.',
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default {
|
||||||
emailPatchPath: state => state.diffs.emailPatchPath,
|
emailPatchPath: state => state.diffs.emailPatchPath,
|
||||||
}),
|
}),
|
||||||
...mapGetters('diffs', ['isParallelView']),
|
...mapGetters('diffs', ['isParallelView']),
|
||||||
...mapGetters(['isNotesFetched']),
|
...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
|
||||||
targetBranch() {
|
targetBranch() {
|
||||||
return {
|
return {
|
||||||
branchName: this.targetBranchName,
|
branchName: this.targetBranchName,
|
||||||
|
@ -112,18 +112,45 @@ export default {
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.adjustView();
|
this.adjustView();
|
||||||
|
eventHub.$once('fetchedNotesData', this.setDiscussions);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles']),
|
...mapActions('diffs', [
|
||||||
|
'setBaseConfig',
|
||||||
|
'fetchDiffFiles',
|
||||||
|
'startRenderDiffsQueue',
|
||||||
|
'assignDiscussionsToDiff',
|
||||||
|
]),
|
||||||
|
|
||||||
fetchData() {
|
fetchData() {
|
||||||
this.fetchDiffFiles().catch(() => {
|
this.fetchDiffFiles()
|
||||||
createFlash(__('Something went wrong on our end. Please try again!'));
|
.then(() => {
|
||||||
});
|
requestIdleCallback(
|
||||||
|
() => {
|
||||||
|
this.setDiscussions();
|
||||||
|
this.startRenderDiffsQueue();
|
||||||
|
},
|
||||||
|
{ timeout: 1000 },
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
createFlash(__('Something went wrong on our end. Please try again!'));
|
||||||
|
});
|
||||||
|
|
||||||
if (!this.isNotesFetched) {
|
if (!this.isNotesFetched) {
|
||||||
eventHub.$emit('fetchNotesData');
|
eventHub.$emit('fetchNotesData');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
setDiscussions() {
|
||||||
|
if (this.isNotesFetched) {
|
||||||
|
requestIdleCallback(
|
||||||
|
() => {
|
||||||
|
this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
|
||||||
|
},
|
||||||
|
{ timeout: 1000 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
adjustView() {
|
adjustView() {
|
||||||
if (this.shouldShow && this.isParallelView) {
|
if (this.shouldShow && this.isParallelView) {
|
||||||
window.mrTabs.expandViewContainer();
|
window.mrTabs.expandViewContainer();
|
||||||
|
|
|
@ -63,7 +63,7 @@ export default {
|
||||||
v-else
|
v-else
|
||||||
role="button"
|
role="button"
|
||||||
class="fa fa-times dropdown-input-search"
|
class="fa fa-times dropdown-input-search"
|
||||||
@click="clearSearch"
|
@click.stop.prevent="clearSearch"
|
||||||
></i>
|
></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-content">
|
<div class="dropdown-content">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
import noteableDiscussion from '../../notes/components/noteable_discussion.vue';
|
import noteableDiscussion from '../../notes/components/noteable_discussion.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -11,6 +12,14 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions('diffs', ['removeDiscussionsFromDiff']),
|
||||||
|
deleteNoteHandler(discussion) {
|
||||||
|
if (discussion.notes.length <= 1) {
|
||||||
|
this.removeDiscussionsFromDiff(discussion);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -31,6 +40,7 @@ export default {
|
||||||
:render-diff-file="false"
|
:render-diff-file="false"
|
||||||
:always-expanded="true"
|
:always-expanded="true"
|
||||||
:discussions-by-diff-order="true"
|
:discussions-by-diff-order="true"
|
||||||
|
@noteDeleted="deleteNoteHandler"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
import _ from 'underscore';
|
import _ from 'underscore';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
import createFlash from '~/flash';
|
import createFlash from '~/flash';
|
||||||
|
@ -30,6 +30,7 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
|
||||||
isCollapsed() {
|
isCollapsed() {
|
||||||
return this.file.collapsed || false;
|
return this.file.collapsed || false;
|
||||||
},
|
},
|
||||||
|
@ -44,18 +45,27 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
showExpandMessage() {
|
showExpandMessage() {
|
||||||
return this.isCollapsed && !this.isLoadingCollapsedDiff && !this.file.tooLarge;
|
return (
|
||||||
|
this.isCollapsed ||
|
||||||
|
!this.file.highlightedDiffLines &&
|
||||||
|
!this.isLoadingCollapsedDiff &&
|
||||||
|
!this.file.tooLarge &&
|
||||||
|
this.file.text
|
||||||
|
);
|
||||||
|
},
|
||||||
|
showLoadingIcon() {
|
||||||
|
return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('diffs', ['loadCollapsedDiff']),
|
...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff']),
|
||||||
handleToggle() {
|
handleToggle() {
|
||||||
const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file;
|
const { highlightedDiffLines, parallelDiffLines } = this.file;
|
||||||
|
if (!highlightedDiffLines && parallelDiffLines !== undefined && !parallelDiffLines.length) {
|
||||||
if (collapsed && !highlightedDiffLines && !parallelDiffLines.length) {
|
|
||||||
this.handleLoadCollapsedDiff();
|
this.handleLoadCollapsedDiff();
|
||||||
} else {
|
} else {
|
||||||
this.file.collapsed = !this.file.collapsed;
|
this.file.collapsed = !this.file.collapsed;
|
||||||
|
this.file.renderIt = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleLoadCollapsedDiff() {
|
handleLoadCollapsedDiff() {
|
||||||
|
@ -65,6 +75,15 @@ export default {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isLoadingCollapsedDiff = false;
|
this.isLoadingCollapsedDiff = false;
|
||||||
this.file.collapsed = false;
|
this.file.collapsed = false;
|
||||||
|
this.file.renderIt = true;
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
requestIdleCallback(
|
||||||
|
() => {
|
||||||
|
this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
|
||||||
|
},
|
||||||
|
{ timeout: 1000 },
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.isLoadingCollapsedDiff = false;
|
this.isLoadingCollapsedDiff = false;
|
||||||
|
@ -121,16 +140,16 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<diff-content
|
<diff-content
|
||||||
v-if="!isCollapsed"
|
v-if="!isCollapsed && file.renderIt"
|
||||||
:class="{ hidden: isCollapsed || file.tooLarge }"
|
:class="{ hidden: isCollapsed || file.tooLarge }"
|
||||||
:diff-file="file"
|
:diff-file="file"
|
||||||
/>
|
/>
|
||||||
<loading-icon
|
<loading-icon
|
||||||
v-if="isLoadingCollapsedDiff"
|
v-if="showLoadingIcon"
|
||||||
class="diff-content loading"
|
class="diff-content loading"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-if="showExpandMessage"
|
v-else-if="showExpandMessage"
|
||||||
class="nothing-here-block diff-collapsed"
|
class="nothing-here-block diff-collapsed"
|
||||||
>
|
>
|
||||||
{{ __('This diff is collapsed.') }}
|
{{ __('This diff is collapsed.') }}
|
||||||
|
|
|
@ -13,6 +13,10 @@ export default {
|
||||||
Icon,
|
Icon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
line: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
fileHash: {
|
fileHash: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -21,31 +25,16 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
lineType: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
lineNumber: {
|
lineNumber: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: false,
|
required: false,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
lineCode: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
linePosition: {
|
linePosition: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
metaData: {
|
|
||||||
type: Object,
|
|
||||||
required: false,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
showCommentButton: {
|
showCommentButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -76,11 +65,6 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
discussions: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
|
@ -89,7 +73,7 @@ export default {
|
||||||
}),
|
}),
|
||||||
...mapGetters(['isLoggedIn']),
|
...mapGetters(['isLoggedIn']),
|
||||||
lineHref() {
|
lineHref() {
|
||||||
return this.lineCode ? `#${this.lineCode}` : '#';
|
return `#${this.line.lineCode || ''}`;
|
||||||
},
|
},
|
||||||
shouldShowCommentButton() {
|
shouldShowCommentButton() {
|
||||||
return (
|
return (
|
||||||
|
@ -103,20 +87,19 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
hasDiscussions() {
|
hasDiscussions() {
|
||||||
return this.discussions.length > 0;
|
return this.line.discussions && this.line.discussions.length > 0;
|
||||||
},
|
},
|
||||||
shouldShowAvatarsOnGutter() {
|
shouldShowAvatarsOnGutter() {
|
||||||
if (!this.lineType && this.linePosition === LINE_POSITION_RIGHT) {
|
if (!this.line.type && this.linePosition === LINE_POSITION_RIGHT) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.showCommentButton && this.hasDiscussions;
|
return this.showCommentButton && this.hasDiscussions;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('diffs', ['loadMoreLines', 'showCommentForm']),
|
...mapActions('diffs', ['loadMoreLines', 'showCommentForm']),
|
||||||
handleCommentButton() {
|
handleCommentButton() {
|
||||||
this.showCommentForm({ lineCode: this.lineCode });
|
this.showCommentForm({ lineCode: this.line.lineCode });
|
||||||
},
|
},
|
||||||
handleLoadMoreLines() {
|
handleLoadMoreLines() {
|
||||||
if (this.isRequesting) {
|
if (this.isRequesting) {
|
||||||
|
@ -125,8 +108,8 @@ export default {
|
||||||
|
|
||||||
this.isRequesting = true;
|
this.isRequesting = true;
|
||||||
const endpoint = this.contextLinesPath;
|
const endpoint = this.contextLinesPath;
|
||||||
const oldLineNumber = this.metaData.oldPos || 0;
|
const oldLineNumber = this.line.metaData.oldPos || 0;
|
||||||
const newLineNumber = this.metaData.newPos || 0;
|
const newLineNumber = this.line.metaData.newPos || 0;
|
||||||
const offset = newLineNumber - oldLineNumber;
|
const offset = newLineNumber - oldLineNumber;
|
||||||
const bottom = this.isBottom;
|
const bottom = this.isBottom;
|
||||||
const { fileHash } = this;
|
const { fileHash } = this;
|
||||||
|
@ -201,7 +184,7 @@ export default {
|
||||||
</a>
|
</a>
|
||||||
<diff-gutter-avatars
|
<diff-gutter-avatars
|
||||||
v-if="shouldShowAvatarsOnGutter"
|
v-if="shouldShowAvatarsOnGutter"
|
||||||
:discussions="discussions"
|
:discussions="line.discussions"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import noteForm from '../../notes/components/note_form.vue';
|
||||||
import { getNoteFormData } from '../store/utils';
|
import { getNoteFormData } from '../store/utils';
|
||||||
import autosave from '../../notes/mixins/autosave';
|
import autosave from '../../notes/mixins/autosave';
|
||||||
import { DIFF_NOTE_TYPE } from '../constants';
|
import { DIFF_NOTE_TYPE } from '../constants';
|
||||||
|
import { reduceDiscussionsToLineCodes } from '../../notes/stores/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -52,7 +53,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions('diffs', ['cancelCommentForm']),
|
...mapActions('diffs', ['cancelCommentForm', 'assignDiscussionsToDiff']),
|
||||||
...mapActions(['saveNote', 'refetchDiscussionById']),
|
...mapActions(['saveNote', 'refetchDiscussionById']),
|
||||||
handleCancelCommentForm(shouldConfirm, isDirty) {
|
handleCancelCommentForm(shouldConfirm, isDirty) {
|
||||||
if (shouldConfirm && isDirty) {
|
if (shouldConfirm && isDirty) {
|
||||||
|
@ -88,7 +89,10 @@ export default {
|
||||||
const endpoint = this.getNotesDataByProp('discussionsPath');
|
const endpoint = this.getNotesDataByProp('discussionsPath');
|
||||||
|
|
||||||
this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id })
|
this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id })
|
||||||
.then(() => {
|
.then(selectedDiscussion => {
|
||||||
|
const lineCodeDiscussions = reduceDiscussionsToLineCodes([selectedDiscussion]);
|
||||||
|
this.assignDiscussionsToDiff(lineCodeDiscussions);
|
||||||
|
|
||||||
this.handleCancelCommentForm();
|
this.handleCancelCommentForm();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
|
|
@ -11,8 +11,6 @@ import {
|
||||||
LINE_HOVER_CLASS_NAME,
|
LINE_HOVER_CLASS_NAME,
|
||||||
LINE_UNFOLD_CLASS_NAME,
|
LINE_UNFOLD_CLASS_NAME,
|
||||||
INLINE_DIFF_VIEW_TYPE,
|
INLINE_DIFF_VIEW_TYPE,
|
||||||
LINE_POSITION_LEFT,
|
|
||||||
LINE_POSITION_RIGHT,
|
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -67,42 +65,24 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
discussions: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['isLoggedIn']),
|
...mapGetters(['isLoggedIn']),
|
||||||
normalizedLine() {
|
|
||||||
let normalizedLine;
|
|
||||||
|
|
||||||
if (this.diffViewType === INLINE_DIFF_VIEW_TYPE) {
|
|
||||||
normalizedLine = this.line;
|
|
||||||
} else if (this.linePosition === LINE_POSITION_LEFT) {
|
|
||||||
normalizedLine = this.line.left;
|
|
||||||
} else if (this.linePosition === LINE_POSITION_RIGHT) {
|
|
||||||
normalizedLine = this.line.right;
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalizedLine;
|
|
||||||
},
|
|
||||||
isMatchLine() {
|
isMatchLine() {
|
||||||
return this.normalizedLine.type === MATCH_LINE_TYPE;
|
return this.line.type === MATCH_LINE_TYPE;
|
||||||
},
|
},
|
||||||
isContextLine() {
|
isContextLine() {
|
||||||
return this.normalizedLine.type === CONTEXT_LINE_TYPE;
|
return this.line.type === CONTEXT_LINE_TYPE;
|
||||||
},
|
},
|
||||||
isMetaLine() {
|
isMetaLine() {
|
||||||
const { type } = this.normalizedLine;
|
const { type } = this.line;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE
|
type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
classNameMap() {
|
classNameMap() {
|
||||||
const { type } = this.normalizedLine;
|
const { type } = this.line;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
[type]: type,
|
[type]: type,
|
||||||
|
@ -116,9 +96,9 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
lineNumber() {
|
lineNumber() {
|
||||||
const { lineType, normalizedLine } = this;
|
const { lineType } = this;
|
||||||
|
|
||||||
return lineType === OLD_LINE_TYPE ? normalizedLine.oldLine : normalizedLine.newLine;
|
return lineType === OLD_LINE_TYPE ? this.line.oldLine : this.line.newLine;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -129,20 +109,17 @@ export default {
|
||||||
:class="classNameMap"
|
:class="classNameMap"
|
||||||
>
|
>
|
||||||
<diff-line-gutter-content
|
<diff-line-gutter-content
|
||||||
|
:line="line"
|
||||||
:file-hash="fileHash"
|
:file-hash="fileHash"
|
||||||
:context-lines-path="contextLinesPath"
|
:context-lines-path="contextLinesPath"
|
||||||
:line-type="normalizedLine.type"
|
|
||||||
:line-code="normalizedLine.lineCode"
|
|
||||||
:line-position="linePosition"
|
:line-position="linePosition"
|
||||||
:line-number="lineNumber"
|
:line-number="lineNumber"
|
||||||
:meta-data="normalizedLine.metaData"
|
|
||||||
:show-comment-button="showCommentButton"
|
:show-comment-button="showCommentButton"
|
||||||
:is-hover="isHover"
|
:is-hover="isHover"
|
||||||
:is-bottom="isBottom"
|
:is-bottom="isBottom"
|
||||||
:is-match-line="isMatchLine"
|
:is-match-line="isMatchLine"
|
||||||
:is-context-line="isContentLine"
|
:is-context-line="isContentLine"
|
||||||
:is-meta-line="isMetaLine"
|
:is-meta-line="isMetaLine"
|
||||||
:discussions="discussions"
|
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -21,18 +21,13 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
discussions: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
||||||
}),
|
}),
|
||||||
className() {
|
className() {
|
||||||
return this.discussions.length ? '' : 'js-temp-notes-holder';
|
return this.line.discussions.length ? '' : 'js-temp-notes-holder';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -44,14 +39,13 @@ export default {
|
||||||
class="notes_holder"
|
class="notes_holder"
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
class="notes_line"
|
class="notes_content"
|
||||||
colspan="2"
|
colspan="3"
|
||||||
></td>
|
>
|
||||||
<td class="notes_content">
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<diff-discussions
|
<diff-discussions
|
||||||
v-if="discussions.length"
|
v-if="line.discussions.length"
|
||||||
:discussions="discussions"
|
:discussions="line.discussions"
|
||||||
/>
|
/>
|
||||||
<diff-line-note-form
|
<diff-line-note-form
|
||||||
v-if="diffLineCommentForms[line.lineCode]"
|
v-if="diffLineCommentForms[line.lineCode]"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import DiffTableCell from './diff_table_cell.vue';
|
import DiffTableCell from './diff_table_cell.vue';
|
||||||
import {
|
import {
|
||||||
NEW_LINE_TYPE,
|
NEW_LINE_TYPE,
|
||||||
|
@ -33,11 +33,6 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
discussions: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -68,7 +63,11 @@ export default {
|
||||||
this.linePositionLeft = LINE_POSITION_LEFT;
|
this.linePositionLeft = LINE_POSITION_LEFT;
|
||||||
this.linePositionRight = LINE_POSITION_RIGHT;
|
this.linePositionRight = LINE_POSITION_RIGHT;
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.scrollToLineIfNeededInline(this.line);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions('diffs', ['scrollToLineIfNeededInline']),
|
||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
// To show the comment icon on the gutter we need to know if we hover the line.
|
// To show the comment icon on the gutter we need to know if we hover the line.
|
||||||
// Current table structure doesn't allow us to do this with CSS in both of the diff view types
|
// Current table structure doesn't allow us to do this with CSS in both of the diff view types
|
||||||
|
@ -94,7 +93,6 @@ export default {
|
||||||
:is-bottom="isBottom"
|
:is-bottom="isBottom"
|
||||||
:is-hover="isHover"
|
:is-hover="isHover"
|
||||||
:show-comment-button="true"
|
:show-comment-button="true"
|
||||||
:discussions="discussions"
|
|
||||||
class="diff-line-num old_line"
|
class="diff-line-num old_line"
|
||||||
/>
|
/>
|
||||||
<diff-table-cell
|
<diff-table-cell
|
||||||
|
@ -104,7 +102,6 @@ export default {
|
||||||
:line-type="newLineType"
|
:line-type="newLineType"
|
||||||
:is-bottom="isBottom"
|
:is-bottom="isBottom"
|
||||||
:is-hover="isHover"
|
:is-hover="isHover"
|
||||||
:discussions="discussions"
|
|
||||||
class="diff-line-num new_line"
|
class="diff-line-num new_line"
|
||||||
/>
|
/>
|
||||||
<td
|
<td
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import { mapGetters, mapState } from 'vuex';
|
import { mapGetters, mapState } from 'vuex';
|
||||||
import inlineDiffTableRow from './inline_diff_table_row.vue';
|
import inlineDiffTableRow from './inline_diff_table_row.vue';
|
||||||
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
|
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
|
||||||
import { trimFirstCharOfLineContent } from '../store/utils';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -20,29 +19,17 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('diffs', [
|
...mapGetters('diffs', ['commitId', 'shouldRenderInlineCommentRow']),
|
||||||
'commitId',
|
|
||||||
'shouldRenderInlineCommentRow',
|
|
||||||
'singleDiscussionByLineCode',
|
|
||||||
]),
|
|
||||||
...mapState({
|
...mapState({
|
||||||
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
||||||
}),
|
}),
|
||||||
normalizedDiffLines() {
|
|
||||||
return this.diffLines.map(line => (line.richText ? trimFirstCharOfLineContent(line) : line));
|
|
||||||
},
|
|
||||||
diffLinesLength() {
|
diffLinesLength() {
|
||||||
return this.normalizedDiffLines.length;
|
return this.diffLines.length;
|
||||||
},
|
},
|
||||||
userColorScheme() {
|
userColorScheme() {
|
||||||
return window.gon.user_color_scheme;
|
return window.gon.user_color_scheme;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
discussionsList(line) {
|
|
||||||
return line.lineCode !== undefined ? this.singleDiscussionByLineCode(line.lineCode) : [];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -53,7 +40,7 @@ export default {
|
||||||
class="code diff-wrap-lines js-syntax-highlight text-file js-diff-inline-view">
|
class="code diff-wrap-lines js-syntax-highlight text-file js-diff-inline-view">
|
||||||
<tbody>
|
<tbody>
|
||||||
<template
|
<template
|
||||||
v-for="(line, index) in normalizedDiffLines"
|
v-for="(line, index) in diffLines"
|
||||||
>
|
>
|
||||||
<inline-diff-table-row
|
<inline-diff-table-row
|
||||||
:file-hash="diffFile.fileHash"
|
:file-hash="diffFile.fileHash"
|
||||||
|
@ -61,7 +48,6 @@ export default {
|
||||||
:line="line"
|
:line="line"
|
||||||
:is-bottom="index + 1 === diffLinesLength"
|
:is-bottom="index + 1 === diffLinesLength"
|
||||||
:key="line.lineCode"
|
:key="line.lineCode"
|
||||||
:discussions="discussionsList(line)"
|
|
||||||
/>
|
/>
|
||||||
<inline-diff-comment-row
|
<inline-diff-comment-row
|
||||||
v-if="shouldRenderInlineCommentRow(line)"
|
v-if="shouldRenderInlineCommentRow(line)"
|
||||||
|
@ -69,7 +55,6 @@ export default {
|
||||||
:line="line"
|
:line="line"
|
||||||
:line-index="index"
|
:line-index="index"
|
||||||
:key="index"
|
:key="index"
|
||||||
:discussions="discussionsList(line)"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -21,51 +21,49 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
leftDiscussions: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
rightDiscussions: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState({
|
...mapState({
|
||||||
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
||||||
}),
|
}),
|
||||||
leftLineCode() {
|
leftLineCode() {
|
||||||
return this.line.left.lineCode;
|
return this.line.left && this.line.left.lineCode;
|
||||||
},
|
},
|
||||||
rightLineCode() {
|
rightLineCode() {
|
||||||
return this.line.right.lineCode;
|
return this.line.right && this.line.right.lineCode;
|
||||||
},
|
},
|
||||||
hasExpandedDiscussionOnLeft() {
|
hasExpandedDiscussionOnLeft() {
|
||||||
const discussions = this.leftDiscussions;
|
return this.line.left && this.line.left.discussions
|
||||||
|
? this.line.left.discussions.every(discussion => discussion.expanded)
|
||||||
return discussions ? discussions.every(discussion => discussion.expanded) : false;
|
: false;
|
||||||
},
|
},
|
||||||
hasExpandedDiscussionOnRight() {
|
hasExpandedDiscussionOnRight() {
|
||||||
const discussions = this.rightDiscussions;
|
return this.line.right && this.line.right.discussions
|
||||||
|
? this.line.right.discussions.every(discussion => discussion.expanded)
|
||||||
return discussions ? discussions.every(discussion => discussion.expanded) : false;
|
: false;
|
||||||
},
|
},
|
||||||
hasAnyExpandedDiscussion() {
|
hasAnyExpandedDiscussion() {
|
||||||
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
|
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
|
||||||
},
|
},
|
||||||
shouldRenderDiscussionsOnLeft() {
|
shouldRenderDiscussionsOnLeft() {
|
||||||
return this.leftDiscussions && this.hasExpandedDiscussionOnLeft;
|
return this.line.left && this.line.left.discussions && this.hasExpandedDiscussionOnLeft;
|
||||||
},
|
},
|
||||||
shouldRenderDiscussionsOnRight() {
|
shouldRenderDiscussionsOnRight() {
|
||||||
return this.rightDiscussions && this.hasExpandedDiscussionOnRight && this.line.right.type;
|
return (
|
||||||
|
this.line.right &&
|
||||||
|
this.line.right.discussions &&
|
||||||
|
this.hasExpandedDiscussionOnRight &&
|
||||||
|
this.line.right.type
|
||||||
|
);
|
||||||
},
|
},
|
||||||
showRightSideCommentForm() {
|
showRightSideCommentForm() {
|
||||||
return this.line.right.type && this.diffLineCommentForms[this.rightLineCode];
|
return (
|
||||||
|
this.line.right && this.line.right.type && this.diffLineCommentForms[this.rightLineCode]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
className() {
|
className() {
|
||||||
return this.leftDiscussions.length > 0 || this.rightDiscussions.length > 0
|
return (this.left && this.line.left.discussions.length > 0) ||
|
||||||
|
(this.right && this.line.right.discussions.length > 0)
|
||||||
? ''
|
? ''
|
||||||
: 'js-temp-notes-holder';
|
: 'js-temp-notes-holder';
|
||||||
},
|
},
|
||||||
|
@ -85,8 +83,8 @@ export default {
|
||||||
class="content"
|
class="content"
|
||||||
>
|
>
|
||||||
<diff-discussions
|
<diff-discussions
|
||||||
v-if="leftDiscussions.length"
|
v-if="line.left.discussions.length"
|
||||||
:discussions="leftDiscussions"
|
:discussions="line.left.discussions"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<diff-line-note-form
|
<diff-line-note-form
|
||||||
|
@ -104,8 +102,8 @@ export default {
|
||||||
class="content"
|
class="content"
|
||||||
>
|
>
|
||||||
<diff-discussions
|
<diff-discussions
|
||||||
v-if="rightDiscussions.length"
|
v-if="line.right.discussions.length"
|
||||||
:discussions="rightDiscussions"
|
:discussions="line.right.discussions"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<diff-line-note-form
|
<diff-line-note-form
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { mapGetters } from 'vuex';
|
|
||||||
import DiffTableCell from './diff_table_cell.vue';
|
import DiffTableCell from './diff_table_cell.vue';
|
||||||
import {
|
import {
|
||||||
NEW_LINE_TYPE,
|
NEW_LINE_TYPE,
|
||||||
|
@ -10,8 +10,7 @@ import {
|
||||||
OLD_NO_NEW_LINE_TYPE,
|
OLD_NO_NEW_LINE_TYPE,
|
||||||
PARALLEL_DIFF_VIEW_TYPE,
|
PARALLEL_DIFF_VIEW_TYPE,
|
||||||
NEW_NO_NEW_LINE_TYPE,
|
NEW_NO_NEW_LINE_TYPE,
|
||||||
LINE_POSITION_LEFT,
|
EMPTY_CELL_TYPE,
|
||||||
LINE_POSITION_RIGHT,
|
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -36,16 +35,6 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
leftDiscussions: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
rightDiscussions: {
|
|
||||||
type: Array,
|
|
||||||
required: false,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -54,32 +43,33 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('diffs', ['isParallelView']),
|
|
||||||
isContextLine() {
|
isContextLine() {
|
||||||
return this.line.left.type === CONTEXT_LINE_TYPE;
|
return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE;
|
||||||
},
|
},
|
||||||
classNameMap() {
|
classNameMap() {
|
||||||
return {
|
return {
|
||||||
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
|
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
|
||||||
[PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView,
|
[PARALLEL_DIFF_VIEW_TYPE]: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
parallelViewLeftLineType() {
|
parallelViewLeftLineType() {
|
||||||
if (this.line.right.type === NEW_NO_NEW_LINE_TYPE) {
|
if (this.line.right && this.line.right.type === NEW_NO_NEW_LINE_TYPE) {
|
||||||
return OLD_NO_NEW_LINE_TYPE;
|
return OLD_NO_NEW_LINE_TYPE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.line.left.type;
|
return this.line.left ? this.line.left.type : EMPTY_CELL_TYPE;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.newLineType = NEW_LINE_TYPE;
|
this.newLineType = NEW_LINE_TYPE;
|
||||||
this.oldLineType = OLD_LINE_TYPE;
|
this.oldLineType = OLD_LINE_TYPE;
|
||||||
this.linePositionLeft = LINE_POSITION_LEFT;
|
|
||||||
this.linePositionRight = LINE_POSITION_RIGHT;
|
|
||||||
this.parallelDiffViewType = PARALLEL_DIFF_VIEW_TYPE;
|
this.parallelDiffViewType = PARALLEL_DIFF_VIEW_TYPE;
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.scrollToLineIfNeededParallel(this.line);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions('diffs', ['scrollToLineIfNeededParallel']),
|
||||||
handleMouseMove(e) {
|
handleMouseMove(e) {
|
||||||
const isHover = e.type === 'mouseover';
|
const isHover = e.type === 'mouseover';
|
||||||
const hoveringCell = e.target.closest('td');
|
const hoveringCell = e.target.closest('td');
|
||||||
|
@ -116,47 +106,57 @@ export default {
|
||||||
@mouseover="handleMouseMove"
|
@mouseover="handleMouseMove"
|
||||||
@mouseout="handleMouseMove"
|
@mouseout="handleMouseMove"
|
||||||
>
|
>
|
||||||
<diff-table-cell
|
<template v-if="line.left">
|
||||||
:file-hash="fileHash"
|
<diff-table-cell
|
||||||
:context-lines-path="contextLinesPath"
|
:file-hash="fileHash"
|
||||||
:line="line"
|
:context-lines-path="contextLinesPath"
|
||||||
:line-type="oldLineType"
|
:line="line.left"
|
||||||
:line-position="linePositionLeft"
|
:line-type="oldLineType"
|
||||||
:is-bottom="isBottom"
|
:is-bottom="isBottom"
|
||||||
:is-hover="isLeftHover"
|
:is-hover="isLeftHover"
|
||||||
:show-comment-button="true"
|
:show-comment-button="true"
|
||||||
:diff-view-type="parallelDiffViewType"
|
:diff-view-type="parallelDiffViewType"
|
||||||
:discussions="leftDiscussions"
|
line-position="left"
|
||||||
class="diff-line-num old_line"
|
class="diff-line-num old_line"
|
||||||
/>
|
/>
|
||||||
<td
|
<td
|
||||||
:id="line.left.lineCode"
|
:id="line.left.lineCode"
|
||||||
:class="parallelViewLeftLineType"
|
:class="parallelViewLeftLineType"
|
||||||
class="line_content parallel left-side"
|
class="line_content parallel left-side"
|
||||||
@mousedown.native="handleParallelLineMouseDown"
|
@mousedown.native="handleParallelLineMouseDown"
|
||||||
v-html="line.left.richText"
|
v-html="line.left.richText"
|
||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
<diff-table-cell
|
</template>
|
||||||
:file-hash="fileHash"
|
<template v-else>
|
||||||
:context-lines-path="contextLinesPath"
|
<td class="diff-line-num old_line empty-cell"></td>
|
||||||
:line="line"
|
<td class="line_content parallel left-side empty-cell"></td>
|
||||||
:line-type="newLineType"
|
</template>
|
||||||
:line-position="linePositionRight"
|
<template v-if="line.right">
|
||||||
:is-bottom="isBottom"
|
<diff-table-cell
|
||||||
:is-hover="isRightHover"
|
:file-hash="fileHash"
|
||||||
:show-comment-button="true"
|
:context-lines-path="contextLinesPath"
|
||||||
:diff-view-type="parallelDiffViewType"
|
:line="line.right"
|
||||||
:discussions="rightDiscussions"
|
:line-type="newLineType"
|
||||||
class="diff-line-num new_line"
|
:is-bottom="isBottom"
|
||||||
/>
|
:is-hover="isRightHover"
|
||||||
<td
|
:show-comment-button="true"
|
||||||
:id="line.right.lineCode"
|
:diff-view-type="parallelDiffViewType"
|
||||||
:class="line.right.type"
|
line-position="right"
|
||||||
class="line_content parallel right-side"
|
class="diff-line-num new_line"
|
||||||
@mousedown.native="handleParallelLineMouseDown"
|
/>
|
||||||
v-html="line.right.richText"
|
<td
|
||||||
>
|
:id="line.right.lineCode"
|
||||||
</td>
|
:class="line.right.type"
|
||||||
|
class="line_content parallel right-side"
|
||||||
|
@mousedown.native="handleParallelLineMouseDown"
|
||||||
|
v-html="line.right.richText"
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<td class="diff-line-num old_line empty-cell"></td>
|
||||||
|
<td class="line_content parallel right-side empty-cell"></td>
|
||||||
|
</template>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
import { mapState, mapGetters } from 'vuex';
|
import { mapState, mapGetters } from 'vuex';
|
||||||
import parallelDiffTableRow from './parallel_diff_table_row.vue';
|
import parallelDiffTableRow from './parallel_diff_table_row.vue';
|
||||||
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
|
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
|
||||||
import { EMPTY_CELL_TYPE } from '../constants';
|
|
||||||
import { trimFirstCharOfLineContent } from '../store/utils';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -21,46 +19,17 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters('diffs', [
|
...mapGetters('diffs', ['commitId', 'shouldRenderParallelCommentRow']),
|
||||||
'commitId',
|
|
||||||
'singleDiscussionByLineCode',
|
|
||||||
'shouldRenderParallelCommentRow',
|
|
||||||
]),
|
|
||||||
...mapState({
|
...mapState({
|
||||||
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
||||||
}),
|
}),
|
||||||
parallelDiffLines() {
|
|
||||||
return this.diffLines.map(line => {
|
|
||||||
const parallelLine = Object.assign({}, line);
|
|
||||||
|
|
||||||
if (line.left) {
|
|
||||||
parallelLine.left = trimFirstCharOfLineContent(line.left);
|
|
||||||
} else {
|
|
||||||
parallelLine.left = { type: EMPTY_CELL_TYPE };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.right) {
|
|
||||||
parallelLine.right = trimFirstCharOfLineContent(line.right);
|
|
||||||
} else {
|
|
||||||
parallelLine.right = { type: EMPTY_CELL_TYPE };
|
|
||||||
}
|
|
||||||
|
|
||||||
return parallelLine;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
diffLinesLength() {
|
diffLinesLength() {
|
||||||
return this.parallelDiffLines.length;
|
return this.diffLines.length;
|
||||||
},
|
},
|
||||||
userColorScheme() {
|
userColorScheme() {
|
||||||
return window.gon.user_color_scheme;
|
return window.gon.user_color_scheme;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
discussionsByLine(line, leftOrRight) {
|
|
||||||
return line[leftOrRight] && line[leftOrRight].lineCode !== undefined ?
|
|
||||||
this.singleDiscussionByLineCode(line[leftOrRight].lineCode) : [];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -73,7 +42,7 @@ export default {
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<template
|
<template
|
||||||
v-for="(line, index) in parallelDiffLines"
|
v-for="(line, index) in diffLines"
|
||||||
>
|
>
|
||||||
<parallel-diff-table-row
|
<parallel-diff-table-row
|
||||||
:file-hash="diffFile.fileHash"
|
:file-hash="diffFile.fileHash"
|
||||||
|
@ -81,8 +50,6 @@ export default {
|
||||||
:line="line"
|
:line="line"
|
||||||
:is-bottom="index + 1 === diffLinesLength"
|
:is-bottom="index + 1 === diffLinesLength"
|
||||||
:key="index"
|
:key="index"
|
||||||
:left-discussions="discussionsByLine(line, 'left')"
|
|
||||||
:right-discussions="discussionsByLine(line, 'right')"
|
|
||||||
/>
|
/>
|
||||||
<parallel-diff-comment-row
|
<parallel-diff-comment-row
|
||||||
v-if="shouldRenderParallelCommentRow(line)"
|
v-if="shouldRenderParallelCommentRow(line)"
|
||||||
|
@ -90,8 +57,6 @@ export default {
|
||||||
:line="line"
|
:line="line"
|
||||||
:diff-file-hash="diffFile.fileHash"
|
:diff-file-hash="diffFile.fileHash"
|
||||||
:line-index="index"
|
:line-index="index"
|
||||||
:left-discussions="discussionsByLine(line, 'left')"
|
|
||||||
:right-discussions="discussionsByLine(line, 'right')"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
|
@ -25,3 +25,6 @@ export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
|
||||||
export const UNFOLD_COUNT = 20;
|
export const UNFOLD_COUNT = 20;
|
||||||
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
|
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
|
||||||
export const LENGTH_OF_AVATAR_TOOLTIP = 17;
|
export const LENGTH_OF_AVATAR_TOOLTIP = 17;
|
||||||
|
|
||||||
|
export const LINES_TO_BE_RENDERED_DIRECTLY = 100;
|
||||||
|
export const MAX_LINES_TO_BE_RENDERED = 2000;
|
||||||
|
|
|
@ -2,7 +2,8 @@ import Vue from 'vue';
|
||||||
import axios from '~/lib/utils/axios_utils';
|
import axios from '~/lib/utils/axios_utils';
|
||||||
import Cookies from 'js-cookie';
|
import Cookies from 'js-cookie';
|
||||||
import { handleLocationHash, historyPushState } from '~/lib/utils/common_utils';
|
import { handleLocationHash, historyPushState } from '~/lib/utils/common_utils';
|
||||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility';
|
||||||
|
import { getDiffPositionByLineCode } from './utils';
|
||||||
import * as types from './mutation_types';
|
import * as types from './mutation_types';
|
||||||
import {
|
import {
|
||||||
PARALLEL_DIFF_VIEW_TYPE,
|
PARALLEL_DIFF_VIEW_TYPE,
|
||||||
|
@ -29,6 +30,55 @@ export const fetchDiffFiles = ({ state, commit }) => {
|
||||||
.then(handleLocationHash);
|
.then(handleLocationHash);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This is adding line discussions to the actual lines in the diff tree
|
||||||
|
// once for parallel and once for inline mode
|
||||||
|
export const assignDiscussionsToDiff = ({ state, commit }, allLineDiscussions) => {
|
||||||
|
const diffPositionByLineCode = getDiffPositionByLineCode(state.diffFiles);
|
||||||
|
|
||||||
|
Object.values(allLineDiscussions).forEach(discussions => {
|
||||||
|
if (discussions.length > 0) {
|
||||||
|
const { fileHash } = discussions[0];
|
||||||
|
commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, {
|
||||||
|
fileHash,
|
||||||
|
discussions,
|
||||||
|
diffPositionByLineCode,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => {
|
||||||
|
const { fileHash, line_code } = removeDiscussion;
|
||||||
|
commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash, lineCode: line_code });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const startRenderDiffsQueue = ({ state, commit }) => {
|
||||||
|
const checkItem = () =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
const nextFile = state.diffFiles.find(
|
||||||
|
file => !file.renderIt && (!file.collapsed || !file.text),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nextFile) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
commit(types.RENDER_FILE, nextFile);
|
||||||
|
});
|
||||||
|
requestIdleCallback(
|
||||||
|
() => {
|
||||||
|
checkItem()
|
||||||
|
.then(resolve)
|
||||||
|
.catch(() => {});
|
||||||
|
},
|
||||||
|
{ timeout: 1000 },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return checkItem();
|
||||||
|
};
|
||||||
|
|
||||||
export const setInlineDiffViewType = ({ commit }) => {
|
export const setInlineDiffViewType = ({ commit }) => {
|
||||||
commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE);
|
commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE);
|
||||||
|
|
||||||
|
@ -70,6 +120,25 @@ export const loadMoreLines = ({ commit }, options) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const scrollToLineIfNeededInline = (_, line) => {
|
||||||
|
const hash = getLocationHash();
|
||||||
|
|
||||||
|
if (hash && line.lineCode === hash) {
|
||||||
|
handleLocationHash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const scrollToLineIfNeededParallel = (_, line) => {
|
||||||
|
const hash = getLocationHash();
|
||||||
|
|
||||||
|
if (
|
||||||
|
hash &&
|
||||||
|
((line.left && line.left.lineCode === hash) || (line.right && line.right.lineCode === hash))
|
||||||
|
) {
|
||||||
|
handleLocationHash();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const loadCollapsedDiff = ({ commit }, file) =>
|
export const loadCollapsedDiff = ({ commit }, file) =>
|
||||||
axios.get(file.loadCollapsedDiffUrl).then(res => {
|
axios.get(file.loadCollapsedDiffUrl).then(res => {
|
||||||
commit(types.ADD_COLLAPSED_DIFFS, {
|
commit(types.ADD_COLLAPSED_DIFFS, {
|
||||||
|
|
|
@ -17,7 +17,10 @@ export const commitId = state => (state.commit && state.commit.id ? state.commit
|
||||||
export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
|
export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
|
||||||
const discussions = getters.getDiffFileDiscussions(diff);
|
const discussions = getters.getDiffFileDiscussions(diff);
|
||||||
|
|
||||||
return (discussions.length && discussions.every(discussion => discussion.expanded)) || false;
|
return (
|
||||||
|
(discussions && discussions.length && discussions.every(discussion => discussion.expanded)) ||
|
||||||
|
false
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,7 +31,10 @@ export const diffHasAllExpandedDiscussions = (state, getters) => diff => {
|
||||||
export const diffHasAllCollpasedDiscussions = (state, getters) => diff => {
|
export const diffHasAllCollpasedDiscussions = (state, getters) => diff => {
|
||||||
const discussions = getters.getDiffFileDiscussions(diff);
|
const discussions = getters.getDiffFileDiscussions(diff);
|
||||||
|
|
||||||
return (discussions.length && discussions.every(discussion => !discussion.expanded)) || false;
|
return (
|
||||||
|
(discussions && discussions.length && discussions.every(discussion => !discussion.expanded)) ||
|
||||||
|
false
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +46,9 @@ export const diffHasExpandedDiscussions = (state, getters) => diff => {
|
||||||
const discussions = getters.getDiffFileDiscussions(diff);
|
const discussions = getters.getDiffFileDiscussions(diff);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(discussions.length && discussions.find(discussion => discussion.expanded) !== undefined) ||
|
(discussions &&
|
||||||
|
discussions.length &&
|
||||||
|
discussions.find(discussion => discussion.expanded) !== undefined) ||
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -64,45 +72,38 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
|
||||||
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
|
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
|
||||||
) || [];
|
) || [];
|
||||||
|
|
||||||
export const singleDiscussionByLineCode = (state, getters, rootState, rootGetters) => lineCode => {
|
export const shouldRenderParallelCommentRow = state => line => {
|
||||||
if (!lineCode || lineCode === undefined) return [];
|
const hasDiscussion =
|
||||||
const discussions = rootGetters.discussionsByLineCode;
|
(line.left && line.left.discussions && line.left.discussions.length) ||
|
||||||
return discussions[lineCode] || [];
|
(line.right && line.right.discussions && line.right.discussions.length);
|
||||||
};
|
|
||||||
|
|
||||||
export const shouldRenderParallelCommentRow = (state, getters) => line => {
|
const hasExpandedDiscussionOnLeft =
|
||||||
const leftLineCode = line.left.lineCode;
|
line.left && line.left.discussions && line.left.discussions.length
|
||||||
const rightLineCode = line.right.lineCode;
|
? line.left.discussions.every(discussion => discussion.expanded)
|
||||||
const leftDiscussions = getters.singleDiscussionByLineCode(leftLineCode);
|
: false;
|
||||||
const rightDiscussions = getters.singleDiscussionByLineCode(rightLineCode);
|
const hasExpandedDiscussionOnRight =
|
||||||
const hasDiscussion = leftDiscussions.length || rightDiscussions.length;
|
line.right && line.right.discussions && line.right.discussions.length
|
||||||
|
? line.right.discussions.every(discussion => discussion.expanded)
|
||||||
const hasExpandedDiscussionOnLeft = leftDiscussions.length
|
: false;
|
||||||
? leftDiscussions.every(discussion => discussion.expanded)
|
|
||||||
: false;
|
|
||||||
const hasExpandedDiscussionOnRight = rightDiscussions.length
|
|
||||||
? rightDiscussions.every(discussion => discussion.expanded)
|
|
||||||
: false;
|
|
||||||
|
|
||||||
if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
|
if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasCommentFormOnLeft = state.diffLineCommentForms[leftLineCode];
|
const hasCommentFormOnLeft = line.left && state.diffLineCommentForms[line.left.lineCode];
|
||||||
const hasCommentFormOnRight = state.diffLineCommentForms[rightLineCode];
|
const hasCommentFormOnRight = line.right && state.diffLineCommentForms[line.right.lineCode];
|
||||||
|
|
||||||
return hasCommentFormOnLeft || hasCommentFormOnRight;
|
return hasCommentFormOnLeft || hasCommentFormOnRight;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const shouldRenderInlineCommentRow = (state, getters) => line => {
|
export const shouldRenderInlineCommentRow = state => line => {
|
||||||
if (state.diffLineCommentForms[line.lineCode]) return true;
|
if (state.diffLineCommentForms[line.lineCode]) return true;
|
||||||
|
|
||||||
const lineDiscussions = getters.singleDiscussionByLineCode(line.lineCode);
|
if (!line.discussions || line.discussions.length === 0) {
|
||||||
if (lineDiscussions.length === 0) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return lineDiscussions.every(discussion => discussion.expanded);
|
return line.discussions.every(discussion => discussion.expanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
|
// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests
|
||||||
|
|
|
@ -8,3 +8,6 @@ export const REMOVE_COMMENT_FORM_LINE = 'REMOVE_COMMENT_FORM_LINE';
|
||||||
export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
|
export const ADD_CONTEXT_LINES = 'ADD_CONTEXT_LINES';
|
||||||
export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS';
|
export const ADD_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS';
|
||||||
export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES';
|
export const EXPAND_ALL_FILES = 'EXPAND_ALL_FILES';
|
||||||
|
export const RENDER_FILE = 'RENDER_FILE';
|
||||||
|
export const SET_LINE_DISCUSSIONS_FOR_FILE = 'SET_LINE_DISCUSSIONS_FOR_FILE';
|
||||||
|
export const REMOVE_LINE_DISCUSSIONS_FOR_FILE = 'REMOVE_LINE_DISCUSSIONS_FOR_FILE';
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import _ from 'underscore';
|
|
||||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||||
import { findDiffFile, addLineReferences, removeMatchLine, addContextLines } from './utils';
|
import {
|
||||||
|
findDiffFile,
|
||||||
|
addLineReferences,
|
||||||
|
removeMatchLine,
|
||||||
|
addContextLines,
|
||||||
|
prepareDiffData,
|
||||||
|
isDiscussionApplicableToLine,
|
||||||
|
} from './utils';
|
||||||
import * as types from './mutation_types';
|
import * as types from './mutation_types';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -15,8 +21,17 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.SET_DIFF_DATA](state, data) {
|
[types.SET_DIFF_DATA](state, data) {
|
||||||
|
const diffData = convertObjectPropsToCamelCase(data, { deep: true });
|
||||||
|
prepareDiffData(diffData);
|
||||||
|
|
||||||
Object.assign(state, {
|
Object.assign(state, {
|
||||||
...convertObjectPropsToCamelCase(data, { deep: true }),
|
...diffData,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.RENDER_FILE](state, file) {
|
||||||
|
Object.assign(file, {
|
||||||
|
renderIt: true,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -57,19 +72,93 @@ export default {
|
||||||
|
|
||||||
[types.ADD_COLLAPSED_DIFFS](state, { file, data }) {
|
[types.ADD_COLLAPSED_DIFFS](state, { file, data }) {
|
||||||
const normalizedData = convertObjectPropsToCamelCase(data, { deep: true });
|
const normalizedData = convertObjectPropsToCamelCase(data, { deep: true });
|
||||||
|
prepareDiffData(normalizedData);
|
||||||
const [newFileData] = normalizedData.diffFiles.filter(f => f.fileHash === file.fileHash);
|
const [newFileData] = normalizedData.diffFiles.filter(f => f.fileHash === file.fileHash);
|
||||||
|
const selectedFile = state.diffFiles.find(f => f.fileHash === file.fileHash);
|
||||||
if (newFileData) {
|
Object.assign(selectedFile, { ...newFileData });
|
||||||
const index = _.findIndex(state.diffFiles, f => f.fileHash === file.fileHash);
|
|
||||||
state.diffFiles.splice(index, 1, newFileData);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[types.EXPAND_ALL_FILES](state) {
|
[types.EXPAND_ALL_FILES](state) {
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
state.diffFiles = state.diffFiles.map(file => ({
|
state.diffFiles = state.diffFiles.map(file => ({
|
||||||
...file,
|
...file,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, discussions, diffPositionByLineCode }) {
|
||||||
|
const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash);
|
||||||
|
const firstDiscussion = discussions[0];
|
||||||
|
const isDiffDiscussion = firstDiscussion.diff_discussion;
|
||||||
|
const hasLineCode = firstDiscussion.line_code;
|
||||||
|
const isResolvable = firstDiscussion.resolvable;
|
||||||
|
const diffPosition = diffPositionByLineCode[firstDiscussion.line_code];
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedFile &&
|
||||||
|
isDiffDiscussion &&
|
||||||
|
hasLineCode &&
|
||||||
|
isResolvable &&
|
||||||
|
diffPosition &&
|
||||||
|
isDiscussionApplicableToLine(firstDiscussion, diffPosition)
|
||||||
|
) {
|
||||||
|
const targetLine = selectedFile.parallelDiffLines.find(
|
||||||
|
line =>
|
||||||
|
(line.left && line.left.lineCode === firstDiscussion.line_code) ||
|
||||||
|
(line.right && line.right.lineCode === firstDiscussion.line_code),
|
||||||
|
);
|
||||||
|
if (targetLine) {
|
||||||
|
if (targetLine.left && targetLine.left.lineCode === firstDiscussion.line_code) {
|
||||||
|
Object.assign(targetLine.left, {
|
||||||
|
discussions,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Object.assign(targetLine.right, {
|
||||||
|
discussions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFile.highlightedDiffLines) {
|
||||||
|
const targetInlineLine = selectedFile.highlightedDiffLines.find(
|
||||||
|
line => line.lineCode === firstDiscussion.line_code,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targetInlineLine) {
|
||||||
|
Object.assign(targetInlineLine, {
|
||||||
|
discussions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) {
|
||||||
|
const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash);
|
||||||
|
if (selectedFile) {
|
||||||
|
const targetLine = selectedFile.parallelDiffLines.find(
|
||||||
|
line =>
|
||||||
|
(line.left && line.left.lineCode === lineCode) ||
|
||||||
|
(line.right && line.right.lineCode === lineCode),
|
||||||
|
);
|
||||||
|
if (targetLine) {
|
||||||
|
const side = targetLine.left && targetLine.left.lineCode === lineCode ? 'left' : 'right';
|
||||||
|
|
||||||
|
Object.assign(targetLine[side], {
|
||||||
|
discussions: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFile.highlightedDiffLines) {
|
||||||
|
const targetInlineLine = selectedFile.highlightedDiffLines.find(
|
||||||
|
line => line.lineCode === lineCode,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targetInlineLine) {
|
||||||
|
Object.assign(targetInlineLine, {
|
||||||
|
discussions: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,8 @@ import {
|
||||||
NEW_LINE_TYPE,
|
NEW_LINE_TYPE,
|
||||||
OLD_LINE_TYPE,
|
OLD_LINE_TYPE,
|
||||||
MATCH_LINE_TYPE,
|
MATCH_LINE_TYPE,
|
||||||
|
LINES_TO_BE_RENDERED_DIRECTLY,
|
||||||
|
MAX_LINES_TO_BE_RENDERED,
|
||||||
} from '../constants';
|
} from '../constants';
|
||||||
|
|
||||||
export function findDiffFile(files, hash) {
|
export function findDiffFile(files, hash) {
|
||||||
|
@ -161,6 +163,11 @@ export function addContextLines(options) {
|
||||||
* @returns {Object}
|
* @returns {Object}
|
||||||
*/
|
*/
|
||||||
export function trimFirstCharOfLineContent(line = {}) {
|
export function trimFirstCharOfLineContent(line = {}) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
delete line.text;
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
line.discussions = [];
|
||||||
|
|
||||||
const parsedLine = Object.assign({}, line);
|
const parsedLine = Object.assign({}, line);
|
||||||
|
|
||||||
if (line.richText) {
|
if (line.richText) {
|
||||||
|
@ -174,7 +181,44 @@ export function trimFirstCharOfLineContent(line = {}) {
|
||||||
return parsedLine;
|
return parsedLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDiffRefsByLineCode(diffFiles) {
|
// This prepares and optimizes the incoming diff data from the server
|
||||||
|
// by setting up incremental rendering and removing unneeded data
|
||||||
|
export function prepareDiffData(diffData) {
|
||||||
|
const filesLength = diffData.diffFiles.length;
|
||||||
|
let showingLines = 0;
|
||||||
|
for (let i = 0; i < filesLength; i += 1) {
|
||||||
|
const file = diffData.diffFiles[i];
|
||||||
|
|
||||||
|
if (file.parallelDiffLines) {
|
||||||
|
const linesLength = file.parallelDiffLines.length;
|
||||||
|
for (let u = 0; u < linesLength; u += 1) {
|
||||||
|
const line = file.parallelDiffLines[u];
|
||||||
|
if (line.left) {
|
||||||
|
line.left = trimFirstCharOfLineContent(line.left);
|
||||||
|
}
|
||||||
|
if (line.right) {
|
||||||
|
line.right = trimFirstCharOfLineContent(line.right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.highlightedDiffLines) {
|
||||||
|
const linesLength = file.highlightedDiffLines.length;
|
||||||
|
for (let u = 0; u < linesLength; u += 1) {
|
||||||
|
const line = file.highlightedDiffLines[u];
|
||||||
|
Object.assign(line, { ...trimFirstCharOfLineContent(line) });
|
||||||
|
}
|
||||||
|
showingLines += file.parallelDiffLines.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(file, {
|
||||||
|
renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
|
||||||
|
collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDiffPositionByLineCode(diffFiles) {
|
||||||
return diffFiles.reduce((acc, diffFile) => {
|
return diffFiles.reduce((acc, diffFile) => {
|
||||||
const { baseSha, headSha, startSha } = diffFile.diffRefs;
|
const { baseSha, headSha, startSha } = diffFile.diffRefs;
|
||||||
const { newPath, oldPath } = diffFile;
|
const { newPath, oldPath } = diffFile;
|
||||||
|
@ -186,7 +230,7 @@ export function getDiffRefsByLineCode(diffFiles) {
|
||||||
const { lineCode, oldLine, newLine } = line;
|
const { lineCode, oldLine, newLine } = line;
|
||||||
|
|
||||||
if (lineCode) {
|
if (lineCode) {
|
||||||
acc[lineCode] = { baseSha, headSha, startSha, newPath, oldPath, oldLine, newLine };
|
acc[lineCode] = { baseSha, headSha, startSha, newPath, oldPath, oldLine, newLine, positionType: 'text' };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -194,3 +238,12 @@ export function getDiffRefsByLineCode(diffFiles) {
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This method will check whether the discussion is still applicable
|
||||||
|
// to the diff line in question regarding different versions of the MR
|
||||||
|
export function isDiscussionApplicableToLine(discussion, diffPosition) {
|
||||||
|
const originalRefs = convertObjectPropsToCamelCase(discussion.original_position);
|
||||||
|
const refs = convertObjectPropsToCamelCase(discussion.position);
|
||||||
|
|
||||||
|
return _.isEqual(refs, diffPosition) || _.isEqual(originalRefs, diffPosition);
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ import axios from '~/lib/utils/axios_utils';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import Flash from '~/flash';
|
import Flash from '~/flash';
|
||||||
|
|
||||||
export default function gcpSignupOffer() {
|
export default function initDismissableCallout(alertSelector) {
|
||||||
const alertEl = document.querySelector('.gcp-signup-offer');
|
const alertEl = document.querySelector(alertSelector);
|
||||||
if (!alertEl) {
|
if (!alertEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
|
@ -7,6 +7,19 @@ import axios from './lib/utils/axios_utils';
|
||||||
|
|
||||||
Dropzone.autoDiscover = false;
|
Dropzone.autoDiscover = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the error message string from the given response.
|
||||||
|
*
|
||||||
|
* @param {String|Object} res
|
||||||
|
*/
|
||||||
|
function getErrorMessage(res) {
|
||||||
|
if (!res || _.isString(res)) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.message;
|
||||||
|
}
|
||||||
|
|
||||||
export default function dropzoneInput(form) {
|
export default function dropzoneInput(form) {
|
||||||
const divHover = '<div class="div-dropzone-hover"></div>';
|
const divHover = '<div class="div-dropzone-hover"></div>';
|
||||||
const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
|
const iconPaperclip = '<i class="fa fa-paperclip div-dropzone-icon"></i>';
|
||||||
|
@ -18,7 +31,7 @@ export default function dropzoneInput(form) {
|
||||||
const $uploadingErrorContainer = form.find('.uploading-error-container');
|
const $uploadingErrorContainer = form.find('.uploading-error-container');
|
||||||
const $uploadingErrorMessage = form.find('.uploading-error-message');
|
const $uploadingErrorMessage = form.find('.uploading-error-message');
|
||||||
const $uploadingProgressContainer = form.find('.uploading-progress-container');
|
const $uploadingProgressContainer = form.find('.uploading-progress-container');
|
||||||
const uploadsPath = window.uploads_path || null;
|
const uploadsPath = form.data('uploads-path') || window.uploads_path || null;
|
||||||
const maxFileSize = gon.max_file_size || 10;
|
const maxFileSize = gon.max_file_size || 10;
|
||||||
const formTextarea = form.find('.js-gfm-input');
|
const formTextarea = form.find('.js-gfm-input');
|
||||||
let handlePaste;
|
let handlePaste;
|
||||||
|
@ -42,7 +55,7 @@ export default function dropzoneInput(form) {
|
||||||
|
|
||||||
if (!uploadsPath) {
|
if (!uploadsPath) {
|
||||||
$formDropzone.addClass('js-invalid-dropzone');
|
$formDropzone.addClass('js-invalid-dropzone');
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dropzone = $formDropzone.dropzone({
|
const dropzone = $formDropzone.dropzone({
|
||||||
|
@ -84,9 +97,7 @@ export default function dropzoneInput(form) {
|
||||||
// xhr object (xhr.responseText is error message).
|
// xhr object (xhr.responseText is error message).
|
||||||
// On error we hide the 'Attach' and 'Cancel' buttons
|
// On error we hide the 'Attach' and 'Cancel' buttons
|
||||||
// and show an error.
|
// and show an error.
|
||||||
|
const message = getErrorMessage(errorMessage || xhr.responseText);
|
||||||
// If there's xhr error message, let's show it instead of dropzone's one.
|
|
||||||
const message = xhr ? xhr.responseText : errorMessage;
|
|
||||||
|
|
||||||
$uploadingErrorContainer.removeClass('hide');
|
$uploadingErrorContainer.removeClass('hide');
|
||||||
$uploadingErrorMessage.html(message);
|
$uploadingErrorMessage.html(message);
|
||||||
|
@ -274,4 +285,6 @@ export default function dropzoneInput(form) {
|
||||||
$(this).closest('.gfm-form').find('.div-dropzone').click();
|
$(this).closest('.gfm-form').find('.div-dropzone').click();
|
||||||
formTextarea.focus();
|
formTextarea.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Dropzone.forElement($formDropzone.get(0));
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,7 +86,7 @@ function generateUnicodeSupportMap(testMap) {
|
||||||
canvas.height = numTestEntries * fontSize;
|
canvas.height = numTestEntries * fontSize;
|
||||||
ctx.fillStyle = '#000000';
|
ctx.fillStyle = '#000000';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"`;
|
ctx.font = `${fontSize}px "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"`;
|
||||||
// Write each emoji to the canvas vertically
|
// Write each emoji to the canvas vertically
|
||||||
let writeIndex = 0;
|
let writeIndex = 0;
|
||||||
testMapKeys.forEach(testKey => {
|
testMapKeys.forEach(testKey => {
|
||||||
|
|
|
@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => {
|
||||||
|
|
||||||
flashEl.addEventListener('transitionend', () => {
|
flashEl.addEventListener('transitionend', () => {
|
||||||
flashEl.remove();
|
flashEl.remove();
|
||||||
|
window.dispatchEvent(new Event('resize'));
|
||||||
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
|
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
|
||||||
}, {
|
}, {
|
||||||
once: true,
|
once: true,
|
||||||
|
|
|
@ -65,8 +65,8 @@ export const hideMenu = (el) => {
|
||||||
|
|
||||||
const parentEl = el.parentNode;
|
const parentEl = el.parentNode;
|
||||||
|
|
||||||
el.style.display = ''; // eslint-disable-line no-param-reassign
|
el.style.display = '';
|
||||||
el.style.transform = ''; // eslint-disable-line no-param-reassign
|
el.style.transform = '';
|
||||||
el.classList.remove(IS_ABOVE_CLASS);
|
el.classList.remove(IS_ABOVE_CLASS);
|
||||||
parentEl.classList.remove(IS_OVER_CLASS);
|
parentEl.classList.remove(IS_OVER_CLASS);
|
||||||
parentEl.classList.remove(IS_SHOWING_FLY_OUT_CLASS);
|
parentEl.classList.remove(IS_SHOWING_FLY_OUT_CLASS);
|
||||||
|
|
|
@ -149,10 +149,16 @@ class GfmAutoComplete {
|
||||||
// Team Members
|
// Team Members
|
||||||
$input.atwho({
|
$input.atwho({
|
||||||
at: '@',
|
at: '@',
|
||||||
|
alias: 'users',
|
||||||
displayTpl(value) {
|
displayTpl(value) {
|
||||||
let tmpl = GfmAutoComplete.Loading.template;
|
let tmpl = GfmAutoComplete.Loading.template;
|
||||||
if (value.username != null) {
|
const { avatarTag, username, title } = value;
|
||||||
tmpl = GfmAutoComplete.Members.template;
|
if (username != null) {
|
||||||
|
tmpl = GfmAutoComplete.Members.templateFunction({
|
||||||
|
avatarTag,
|
||||||
|
username,
|
||||||
|
title,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return tmpl;
|
return tmpl;
|
||||||
},
|
},
|
||||||
|
@ -512,8 +518,9 @@ GfmAutoComplete.Emoji = {
|
||||||
};
|
};
|
||||||
// Team Members
|
// Team Members
|
||||||
GfmAutoComplete.Members = {
|
GfmAutoComplete.Members = {
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
templateFunction({ avatarTag, username, title }) {
|
||||||
template: '<li>${avatarTag} ${username} <small>${title}</small></li>',
|
return `<li>${avatarTag} ${username} <small>${_.escape(title)}</small></li>`;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
GfmAutoComplete.Labels = {
|
GfmAutoComplete.Labels = {
|
||||||
// eslint-disable-next-line no-template-curly-in-string
|
// eslint-disable-next-line no-template-curly-in-string
|
||||||
|
|
|
@ -2,14 +2,15 @@
|
||||||
/* global Flash */
|
/* global Flash */
|
||||||
|
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import { s__ } from '~/locale';
|
import { s__, sprintf } from '~/locale';
|
||||||
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||||
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
|
import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
|
||||||
|
import { HIDDEN_CLASS } from '~/lib/utils/constants';
|
||||||
import { getParameterByName } from '~/lib/utils/common_utils';
|
import { getParameterByName } from '~/lib/utils/common_utils';
|
||||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||||
|
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
import { COMMON_STR } from '../constants';
|
import { COMMON_STR, CONTENT_LIST_CLASS } from '../constants';
|
||||||
import groupsComponent from './groups.vue';
|
import groupsComponent from './groups.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -19,6 +20,16 @@ export default {
|
||||||
groupsComponent,
|
groupsComponent,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
containerId: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
store: {
|
store: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -56,31 +67,28 @@ export default {
|
||||||
? COMMON_STR.GROUP_SEARCH_EMPTY
|
? COMMON_STR.GROUP_SEARCH_EMPTY
|
||||||
: COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
|
: COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
|
||||||
|
|
||||||
eventHub.$on('fetchPage', this.fetchPage);
|
eventHub.$on(`${this.action}fetchPage`, this.fetchPage);
|
||||||
eventHub.$on('toggleChildren', this.toggleChildren);
|
eventHub.$on(`${this.action}toggleChildren`, this.toggleChildren);
|
||||||
eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
|
eventHub.$on(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
|
||||||
eventHub.$on('updatePagination', this.updatePagination);
|
eventHub.$on(`${this.action}updatePagination`, this.updatePagination);
|
||||||
eventHub.$on('updateGroups', this.updateGroups);
|
eventHub.$on(`${this.action}updateGroups`, this.updateGroups);
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchAllGroups();
|
this.fetchAllGroups();
|
||||||
|
|
||||||
|
if (this.containerId) {
|
||||||
|
this.containerEl = document.getElementById(this.containerId);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
eventHub.$off('fetchPage', this.fetchPage);
|
eventHub.$off(`${this.action}fetchPage`, this.fetchPage);
|
||||||
eventHub.$off('toggleChildren', this.toggleChildren);
|
eventHub.$off(`${this.action}toggleChildren`, this.toggleChildren);
|
||||||
eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
|
eventHub.$off(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
|
||||||
eventHub.$off('updatePagination', this.updatePagination);
|
eventHub.$off(`${this.action}updatePagination`, this.updatePagination);
|
||||||
eventHub.$off('updateGroups', this.updateGroups);
|
eventHub.$off(`${this.action}updateGroups`, this.updateGroups);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchGroups({
|
fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
|
||||||
parentId,
|
|
||||||
page,
|
|
||||||
filterGroupsBy,
|
|
||||||
sortBy,
|
|
||||||
archived,
|
|
||||||
updatePagination,
|
|
||||||
}) {
|
|
||||||
return this.service
|
return this.service
|
||||||
.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
|
.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
@ -165,13 +173,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showLeaveGroupModal(group, parentGroup) {
|
showLeaveGroupModal(group, parentGroup) {
|
||||||
|
const { fullName } = group;
|
||||||
this.targetGroup = group;
|
this.targetGroup = group;
|
||||||
this.targetParentGroup = parentGroup;
|
this.targetParentGroup = parentGroup;
|
||||||
this.showModal = true;
|
this.showModal = true;
|
||||||
this.groupLeaveConfirmationMessage = s__(
|
this.groupLeaveConfirmationMessage = sprintf(
|
||||||
`GroupsTree|Are you sure you want to leave the "${
|
s__('GroupsTree|Are you sure you want to leave the "%{fullName}" group?'),
|
||||||
group.fullName
|
{ fullName },
|
||||||
}" group?`,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
hideLeaveGroupModal() {
|
hideLeaveGroupModal() {
|
||||||
|
@ -197,16 +205,35 @@ export default {
|
||||||
this.targetGroup.isBeingRemoved = false;
|
this.targetGroup.isBeingRemoved = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
showEmptyState() {
|
||||||
|
const { containerEl } = this;
|
||||||
|
const contentListEl = containerEl.querySelector(CONTENT_LIST_CLASS);
|
||||||
|
const emptyStateEl = containerEl.querySelector('.empty-state');
|
||||||
|
|
||||||
|
if (contentListEl) {
|
||||||
|
contentListEl.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emptyStateEl) {
|
||||||
|
emptyStateEl.classList.remove(HIDDEN_CLASS);
|
||||||
|
}
|
||||||
|
},
|
||||||
updatePagination(headers) {
|
updatePagination(headers) {
|
||||||
this.store.setPaginationInfo(headers);
|
this.store.setPaginationInfo(headers);
|
||||||
},
|
},
|
||||||
updateGroups(groups, fromSearch) {
|
updateGroups(groups, fromSearch) {
|
||||||
this.isSearchEmpty = groups ? groups.length === 0 : false;
|
const hasGroups = groups && groups.length > 0;
|
||||||
|
this.isSearchEmpty = !hasGroups;
|
||||||
|
|
||||||
if (fromSearch) {
|
if (fromSearch) {
|
||||||
this.store.setSearchedGroups(groups);
|
this.store.setSearchedGroups(groups);
|
||||||
} else {
|
} else {
|
||||||
this.store.setGroups(groups);
|
this.store.setGroups(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.action && !hasGroups && !fromSearch) {
|
||||||
|
this.showEmptyState();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -226,6 +253,7 @@ export default {
|
||||||
:search-empty="isSearchEmpty"
|
:search-empty="isSearchEmpty"
|
||||||
:search-empty-message="searchEmptyMessage"
|
:search-empty-message="searchEmptyMessage"
|
||||||
:page-info="pageInfo"
|
:page-info="pageInfo"
|
||||||
|
:action="action"
|
||||||
/>
|
/>
|
||||||
<deprecated-modal
|
<deprecated-modal
|
||||||
v-show="showModal"
|
v-show="showModal"
|
||||||
|
|
|
@ -11,8 +11,12 @@ export default {
|
||||||
},
|
},
|
||||||
groups: {
|
groups: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: () => ([]),
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -37,6 +41,7 @@ export default {
|
||||||
:key="index"
|
:key="index"
|
||||||
:group="group"
|
:group="group"
|
||||||
:parent-group="parentGroup"
|
:parent-group="parentGroup"
|
||||||
|
:action="action"
|
||||||
/>
|
/>
|
||||||
<li
|
<li
|
||||||
v-if="hasMoreChildren"
|
v-if="hasMoreChildren"
|
||||||
|
|
|
@ -30,6 +30,11 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
groupDomId() {
|
groupDomId() {
|
||||||
|
@ -56,10 +61,12 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
onClickRowGroup(e) {
|
onClickRowGroup(e) {
|
||||||
const NO_EXPAND_CLS = 'no-expand';
|
const NO_EXPAND_CLS = 'no-expand';
|
||||||
if (!(e.target.classList.contains(NO_EXPAND_CLS) ||
|
const targetClasses = e.target.classList;
|
||||||
e.target.parentElement.classList.contains(NO_EXPAND_CLS))) {
|
const parentElClasses = e.target.parentElement.classList;
|
||||||
|
|
||||||
|
if (!(targetClasses.contains(NO_EXPAND_CLS) || parentElClasses.contains(NO_EXPAND_CLS))) {
|
||||||
if (this.hasChildren) {
|
if (this.hasChildren) {
|
||||||
eventHub.$emit('toggleChildren', this.group);
|
eventHub.$emit(`${this.action}toggleChildren`, this.group);
|
||||||
} else {
|
} else {
|
||||||
visitUrl(this.group.relativePath);
|
visitUrl(this.group.relativePath);
|
||||||
}
|
}
|
||||||
|
@ -93,7 +100,7 @@ export default {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
:class="{ 'content-loading': group.isChildrenLoading }"
|
:class="{ 'content-loading': group.isChildrenLoading }"
|
||||||
class="avatar-container s24 d-none d-sm-block"
|
class="avatar-container s24 d-none d-sm-flex"
|
||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
:href="group.relativePath"
|
:href="group.relativePath"
|
||||||
|
@ -158,6 +165,7 @@ export default {
|
||||||
v-if="group.isOpen && hasChildren"
|
v-if="group.isOpen && hasChildren"
|
||||||
:parent-group="group"
|
:parent-group="group"
|
||||||
:groups="group.children"
|
:groups="group.children"
|
||||||
|
:action="action"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,43 +1,48 @@
|
||||||
<script>
|
<script>
|
||||||
import tablePagination from '~/vue_shared/components/table_pagination.vue';
|
import tablePagination from '~/vue_shared/components/table_pagination.vue';
|
||||||
import eventHub from '../event_hub';
|
import eventHub from '../event_hub';
|
||||||
import { getParameterByName } from '../../lib/utils/common_utils';
|
import { getParameterByName } from '../../lib/utils/common_utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
tablePagination,
|
tablePagination,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
groups: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
props: {
|
pageInfo: {
|
||||||
groups: {
|
type: Object,
|
||||||
type: Array,
|
required: true,
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
pageInfo: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
searchEmpty: {
|
|
||||||
type: Boolean,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
searchEmptyMessage: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
searchEmpty: {
|
||||||
change(page) {
|
type: Boolean,
|
||||||
const filterGroupsParam = getParameterByName('filter_groups');
|
required: true,
|
||||||
const sortParam = getParameterByName('sort');
|
|
||||||
const archivedParam = getParameterByName('archived');
|
|
||||||
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
searchEmptyMessage: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
change(page) {
|
||||||
|
const filterGroupsParam = getParameterByName('filter_groups');
|
||||||
|
const sortParam = getParameterByName('sort');
|
||||||
|
const archivedParam = getParameterByName('archived');
|
||||||
|
eventHub.$emit(`${this.action}fetchPage`, page, filterGroupsParam, sortParam, archivedParam);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="groups-list-tree-container">
|
<div class="groups-list-tree-container qa-groups-list-tree-container">
|
||||||
<div
|
<div
|
||||||
v-if="searchEmpty"
|
v-if="searchEmpty"
|
||||||
class="has-no-search-results"
|
class="has-no-search-results"
|
||||||
|
@ -47,6 +52,7 @@
|
||||||
<group-folder
|
<group-folder
|
||||||
v-if="!searchEmpty"
|
v-if="!searchEmpty"
|
||||||
:groups="groups"
|
:groups="groups"
|
||||||
|
:action="action"
|
||||||
/>
|
/>
|
||||||
<table-pagination
|
<table-pagination
|
||||||
v-if="!searchEmpty"
|
v-if="!searchEmpty"
|
||||||
|
|
|
@ -21,6 +21,11 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
action: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
leaveBtnTitle() {
|
leaveBtnTitle() {
|
||||||
|
@ -32,7 +37,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onLeaveGroup() {
|
onLeaveGroup() {
|
||||||
eventHub.$emit('showLeaveGroupModal', this.group, this.parentGroup);
|
eventHub.$emit(`${this.action}showLeaveGroupModal`, this.group, this.parentGroup);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,13 +2,23 @@ import { __, s__ } from '../locale';
|
||||||
|
|
||||||
export const MAX_CHILDREN_COUNT = 20;
|
export const MAX_CHILDREN_COUNT = 20;
|
||||||
|
|
||||||
|
export const ACTIVE_TAB_SUBGROUPS_AND_PROJECTS = 'subgroups_and_projects';
|
||||||
|
export const ACTIVE_TAB_SHARED = 'shared';
|
||||||
|
export const ACTIVE_TAB_ARCHIVED = 'archived';
|
||||||
|
|
||||||
|
export const GROUPS_LIST_HOLDER_CLASS = '.js-groups-list-holder';
|
||||||
|
export const GROUPS_FILTER_FORM_CLASS = '.js-group-filter-form';
|
||||||
|
export const CONTENT_LIST_CLASS = '.content-list';
|
||||||
|
|
||||||
export const COMMON_STR = {
|
export const COMMON_STR = {
|
||||||
FAILURE: __('An error occurred. Please try again.'),
|
FAILURE: __('An error occurred. Please try again.'),
|
||||||
LEAVE_FORBIDDEN: s__('GroupsTree|Failed to leave the group. Please make sure you are not the only owner.'),
|
LEAVE_FORBIDDEN: s__(
|
||||||
|
'GroupsTree|Failed to leave the group. Please make sure you are not the only owner.',
|
||||||
|
),
|
||||||
LEAVE_BTN_TITLE: s__('GroupsTree|Leave this group'),
|
LEAVE_BTN_TITLE: s__('GroupsTree|Leave this group'),
|
||||||
EDIT_BTN_TITLE: s__('GroupsTree|Edit group'),
|
EDIT_BTN_TITLE: s__('GroupsTree|Edit group'),
|
||||||
GROUP_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups matched your search'),
|
GROUP_SEARCH_EMPTY: s__('GroupsTree|No groups matched your search'),
|
||||||
GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups or projects matched your search'),
|
GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|No groups or projects matched your search'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ITEM_TYPE = {
|
export const ITEM_TYPE = {
|
||||||
|
@ -17,8 +27,12 @@ export const ITEM_TYPE = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GROUP_VISIBILITY_TYPE = {
|
export const GROUP_VISIBILITY_TYPE = {
|
||||||
public: __('Public - The group and any public projects can be viewed without any authentication.'),
|
public: __(
|
||||||
internal: __('Internal - The group and any internal projects can be viewed by any logged in user.'),
|
'Public - The group and any public projects can be viewed without any authentication.',
|
||||||
|
),
|
||||||
|
internal: __(
|
||||||
|
'Internal - The group and any internal projects can be viewed by any logged in user.',
|
||||||
|
),
|
||||||
private: __('Private - The group and its projects can only be viewed by members.'),
|
private: __('Private - The group and its projects can only be viewed by members.'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,13 +4,23 @@ import eventHub from './event_hub';
|
||||||
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
|
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
|
||||||
|
|
||||||
export default class GroupFilterableList extends FilterableList {
|
export default class GroupFilterableList extends FilterableList {
|
||||||
constructor({ form, filter, holder, filterEndpoint, pagePath, dropdownSel, filterInputField }) {
|
constructor({
|
||||||
|
form,
|
||||||
|
filter,
|
||||||
|
holder,
|
||||||
|
filterEndpoint,
|
||||||
|
pagePath,
|
||||||
|
dropdownSel,
|
||||||
|
filterInputField,
|
||||||
|
action,
|
||||||
|
}) {
|
||||||
super(form, filter, holder, filterInputField);
|
super(form, filter, holder, filterInputField);
|
||||||
this.form = form;
|
this.form = form;
|
||||||
this.filterEndpoint = filterEndpoint;
|
this.filterEndpoint = filterEndpoint;
|
||||||
this.pagePath = pagePath;
|
this.pagePath = pagePath;
|
||||||
this.filterInputField = filterInputField;
|
this.filterInputField = filterInputField;
|
||||||
this.$dropdown = $(dropdownSel);
|
this.$dropdown = $(dropdownSel);
|
||||||
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterEndpoint() {
|
getFilterEndpoint() {
|
||||||
|
@ -20,15 +30,16 @@ export default class GroupFilterableList extends FilterableList {
|
||||||
getPagePath(queryData) {
|
getPagePath(queryData) {
|
||||||
const params = queryData ? $.param(queryData) : '';
|
const params = queryData ? $.param(queryData) : '';
|
||||||
const queryString = params ? `?${params}` : '';
|
const queryString = params ? `?${params}` : '';
|
||||||
return `${this.pagePath}${queryString}`;
|
const path = this.pagePath || window.location.pathname;
|
||||||
|
return `${path}${queryString}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
super.bindEvents();
|
super.bindEvents();
|
||||||
|
|
||||||
this.onFilterOptionClikWrapper = this.onOptionClick.bind(this);
|
this.onFilterOptionClickWrapper = this.onOptionClick.bind(this);
|
||||||
|
|
||||||
this.$dropdown.on('click', 'a', this.onFilterOptionClikWrapper);
|
this.$dropdown.on('click', 'a', this.onFilterOptionClickWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterInput() {
|
onFilterInput() {
|
||||||
|
@ -53,7 +64,12 @@ export default class GroupFilterableList extends FilterableList {
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultFilterOption() {
|
setDefaultFilterOption() {
|
||||||
const defaultOption = $.trim(this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').first().text());
|
const defaultOption = $.trim(
|
||||||
|
this.$dropdown
|
||||||
|
.find('.dropdown-menu li.js-filter-sort-order a')
|
||||||
|
.first()
|
||||||
|
.text(),
|
||||||
|
);
|
||||||
this.$dropdown.find('.dropdown-label').text(defaultOption);
|
this.$dropdown.find('.dropdown-label').text(defaultOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,11 +81,19 @@ export default class GroupFilterableList extends FilterableList {
|
||||||
// Get type of option selected from dropdown
|
// Get type of option selected from dropdown
|
||||||
const currentTargetClassList = e.currentTarget.parentElement.classList;
|
const currentTargetClassList = e.currentTarget.parentElement.classList;
|
||||||
const isOptionFilterBySort = currentTargetClassList.contains('js-filter-sort-order');
|
const isOptionFilterBySort = currentTargetClassList.contains('js-filter-sort-order');
|
||||||
const isOptionFilterByArchivedProjects = currentTargetClassList.contains('js-filter-archived-projects');
|
const isOptionFilterByArchivedProjects = currentTargetClassList.contains(
|
||||||
|
'js-filter-archived-projects',
|
||||||
|
);
|
||||||
|
|
||||||
// Get option query param, also preserve currently applied query param
|
// Get option query param, also preserve currently applied query param
|
||||||
const sortParam = getParameterByName('sort', isOptionFilterBySort ? e.currentTarget.href : window.location.href);
|
const sortParam = getParameterByName(
|
||||||
const archivedParam = getParameterByName('archived', isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href);
|
'sort',
|
||||||
|
isOptionFilterBySort ? e.currentTarget.href : window.location.href,
|
||||||
|
);
|
||||||
|
const archivedParam = getParameterByName(
|
||||||
|
'archived',
|
||||||
|
isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href,
|
||||||
|
);
|
||||||
|
|
||||||
if (sortParam) {
|
if (sortParam) {
|
||||||
queryData.sort = sortParam;
|
queryData.sort = sortParam;
|
||||||
|
@ -86,7 +110,9 @@ export default class GroupFilterableList extends FilterableList {
|
||||||
this.$dropdown.find('.dropdown-label').text($.trim(e.currentTarget.text));
|
this.$dropdown.find('.dropdown-label').text($.trim(e.currentTarget.text));
|
||||||
this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').removeClass('is-active');
|
this.$dropdown.find('.dropdown-menu li.js-filter-sort-order a').removeClass('is-active');
|
||||||
} else if (isOptionFilterByArchivedProjects) {
|
} else if (isOptionFilterByArchivedProjects) {
|
||||||
this.$dropdown.find('.dropdown-menu li.js-filter-archived-projects a').removeClass('is-active');
|
this.$dropdown
|
||||||
|
.find('.dropdown-menu li.js-filter-archived-projects a')
|
||||||
|
.removeClass('is-active');
|
||||||
}
|
}
|
||||||
|
|
||||||
$(e.target).addClass('is-active');
|
$(e.target).addClass('is-active');
|
||||||
|
@ -98,11 +124,19 @@ export default class GroupFilterableList extends FilterableList {
|
||||||
onFilterSuccess(res, queryData) {
|
onFilterSuccess(res, queryData) {
|
||||||
const currentPath = this.getPagePath(queryData);
|
const currentPath = this.getPagePath(queryData);
|
||||||
|
|
||||||
window.history.replaceState({
|
window.history.replaceState(
|
||||||
page: currentPath,
|
{
|
||||||
}, document.title, currentPath);
|
page: currentPath,
|
||||||
|
},
|
||||||
|
document.title,
|
||||||
|
currentPath,
|
||||||
|
);
|
||||||
|
|
||||||
eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
|
eventHub.$emit(
|
||||||
eventHub.$emit('updatePagination', normalizeHeaders(res.headers));
|
`${this.action}updateGroups`,
|
||||||
|
res.data,
|
||||||
|
Object.prototype.hasOwnProperty.call(queryData, this.filterInputField),
|
||||||
|
);
|
||||||
|
eventHub.$emit(`${this.action}updatePagination`, normalizeHeaders(res.headers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,18 +7,26 @@ import GroupsService from './service/groups_service';
|
||||||
import groupsApp from './components/app.vue';
|
import groupsApp from './components/app.vue';
|
||||||
import groupFolderComponent from './components/group_folder.vue';
|
import groupFolderComponent from './components/group_folder.vue';
|
||||||
import groupItemComponent from './components/group_item.vue';
|
import groupItemComponent from './components/group_item.vue';
|
||||||
|
import { GROUPS_LIST_HOLDER_CLASS, CONTENT_LIST_CLASS } from './constants';
|
||||||
|
|
||||||
Vue.use(Translate);
|
Vue.use(Translate);
|
||||||
|
|
||||||
export default () => {
|
export default (containerId = 'js-groups-tree', endpoint, action = '') => {
|
||||||
const el = document.getElementById('js-groups-tree');
|
const containerEl = document.getElementById(containerId);
|
||||||
|
let dataEl;
|
||||||
|
|
||||||
// Don't do anything if element doesn't exist (No groups)
|
// Don't do anything if element doesn't exist (No groups)
|
||||||
// This is for when the user enters directly to the page via URL
|
// This is for when the user enters directly to the page via URL
|
||||||
if (!el) {
|
if (!containerEl) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const el = action ? containerEl.querySelector(GROUPS_LIST_HOLDER_CLASS) : containerEl;
|
||||||
|
|
||||||
|
if (action) {
|
||||||
|
dataEl = containerEl.querySelector(CONTENT_LIST_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
Vue.component('group-folder', groupFolderComponent);
|
Vue.component('group-folder', groupFolderComponent);
|
||||||
Vue.component('group-item', groupItemComponent);
|
Vue.component('group-item', groupItemComponent);
|
||||||
|
|
||||||
|
@ -29,20 +37,26 @@ export default () => {
|
||||||
groupsApp,
|
groupsApp,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
const { dataset } = this.$options.el;
|
const { dataset } = dataEl || this.$options.el;
|
||||||
const hideProjects = dataset.hideProjects === 'true';
|
const hideProjects = dataset.hideProjects === 'true';
|
||||||
|
const service = new GroupsService(endpoint || dataset.endpoint);
|
||||||
const store = new GroupsStore(hideProjects);
|
const store = new GroupsStore(hideProjects);
|
||||||
const service = new GroupsService(dataset.endpoint);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
action,
|
||||||
store,
|
store,
|
||||||
service,
|
service,
|
||||||
hideProjects,
|
hideProjects,
|
||||||
loading: true,
|
loading: true,
|
||||||
|
containerId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
beforeMount() {
|
beforeMount() {
|
||||||
const { dataset } = this.$options.el;
|
if (this.action) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { dataset } = dataEl || this.$options.el;
|
||||||
let groupFilterList = null;
|
let groupFilterList = null;
|
||||||
const form = document.querySelector(dataset.formSel);
|
const form = document.querySelector(dataset.formSel);
|
||||||
const filter = document.querySelector(dataset.filterSel);
|
const filter = document.querySelector(dataset.filterSel);
|
||||||
|
@ -52,10 +66,11 @@ export default () => {
|
||||||
form,
|
form,
|
||||||
filter,
|
filter,
|
||||||
holder,
|
holder,
|
||||||
filterEndpoint: dataset.endpoint,
|
filterEndpoint: endpoint || dataset.endpoint,
|
||||||
pagePath: dataset.path,
|
pagePath: dataset.path,
|
||||||
dropdownSel: dataset.dropdownSel,
|
dropdownSel: dataset.dropdownSel,
|
||||||
filterInputField: 'filter',
|
filterInputField: 'filter',
|
||||||
|
action: this.action,
|
||||||
};
|
};
|
||||||
|
|
||||||
groupFilterList = new GroupFilterableList(opts);
|
groupFilterList = new GroupFilterableList(opts);
|
||||||
|
@ -64,9 +79,11 @@ export default () => {
|
||||||
render(createElement) {
|
render(createElement) {
|
||||||
return createElement('groups-app', {
|
return createElement('groups-app', {
|
||||||
props: {
|
props: {
|
||||||
|
action: this.action,
|
||||||
store: this.store,
|
store: this.store,
|
||||||
service: this.service,
|
service: this.service,
|
||||||
hideProjects: this.hideProjects,
|
hideProjects: this.hideProjects,
|
||||||
|
containerId: this.containerId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<script>
|
||||||
|
import $ from 'jquery';
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import { __ } from '~/locale';
|
||||||
|
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||||
|
import ChangedFileIcon from '../changed_file_icon.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
FileIcon,
|
||||||
|
ChangedFileIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
activeFile: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
activeButtonText() {
|
||||||
|
return this.activeFile.staged ? __('Unstage') : __('Stage');
|
||||||
|
},
|
||||||
|
isStaged() {
|
||||||
|
return !this.activeFile.changed && this.activeFile.staged;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(['stageChange', 'unstageChange']),
|
||||||
|
actionButtonClicked() {
|
||||||
|
if (this.activeFile.staged) {
|
||||||
|
this.unstageChange(this.activeFile.path);
|
||||||
|
} else {
|
||||||
|
this.stageChange(this.activeFile.path);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showDiscardModal() {
|
||||||
|
$(document.getElementById(`discard-file-${this.activeFile.path}`)).modal('show');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-flex ide-commit-editor-header align-items-center">
|
||||||
|
<file-icon
|
||||||
|
:file-name="activeFile.name"
|
||||||
|
:size="16"
|
||||||
|
class="mr-2"
|
||||||
|
/>
|
||||||
|
<strong class="mr-2">
|
||||||
|
{{ activeFile.path }}
|
||||||
|
</strong>
|
||||||
|
<changed-file-icon
|
||||||
|
:file="activeFile"
|
||||||
|
/>
|
||||||
|
<div class="ml-auto">
|
||||||
|
<button
|
||||||
|
v-if="!isStaged"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-remove btn-inverted append-right-8"
|
||||||
|
@click="showDiscardModal"
|
||||||
|
>
|
||||||
|
{{ __('Discard') }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{
|
||||||
|
'btn-success': !isStaged,
|
||||||
|
'btn-warning': isStaged
|
||||||
|
}"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-inverted"
|
||||||
|
@click="actionButtonClicked"
|
||||||
|
>
|
||||||
|
{{ activeButtonText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -1,7 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
|
import $ from 'jquery';
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
import { __, sprintf } from '~/locale';
|
import { __, sprintf } from '~/locale';
|
||||||
import Icon from '~/vue_shared/components/icon.vue';
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
|
import GlModal from '~/vue_shared/components/gl_modal.vue';
|
||||||
import tooltip from '~/vue_shared/directives/tooltip';
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
import ListItem from './list_item.vue';
|
import ListItem from './list_item.vue';
|
||||||
|
|
||||||
|
@ -9,6 +11,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Icon,
|
Icon,
|
||||||
ListItem,
|
ListItem,
|
||||||
|
GlModal,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
tooltip,
|
tooltip,
|
||||||
|
@ -56,6 +59,11 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
emptyStateText: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: __('No changes'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
titleText() {
|
titleText() {
|
||||||
|
@ -68,11 +76,19 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['stageAllChanges', 'unstageAllChanges']),
|
...mapActions(['stageAllChanges', 'unstageAllChanges', 'discardAllChanges']),
|
||||||
actionBtnClicked() {
|
actionBtnClicked() {
|
||||||
this[this.action]();
|
this[this.action]();
|
||||||
|
|
||||||
|
$(this.$refs.actionBtn).tooltip('hide');
|
||||||
|
},
|
||||||
|
openDiscardModal() {
|
||||||
|
$('#discard-all-changes').modal('show');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
discardModalText: __(
|
||||||
|
"You will loose all the unstaged changes you've made in this project. This action cannot be undone.",
|
||||||
|
),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -81,27 +97,32 @@ export default {
|
||||||
class="ide-commit-list-container"
|
class="ide-commit-list-container"
|
||||||
>
|
>
|
||||||
<header
|
<header
|
||||||
class="multi-file-commit-panel-header"
|
class="multi-file-commit-panel-header d-flex mb-0"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="multi-file-commit-panel-header-title"
|
class="d-flex align-items-center flex-fill"
|
||||||
>
|
>
|
||||||
<icon
|
<icon
|
||||||
v-once
|
v-once
|
||||||
:name="iconName"
|
:name="iconName"
|
||||||
:size="18"
|
:size="18"
|
||||||
|
class="append-right-8"
|
||||||
/>
|
/>
|
||||||
{{ titleText }}
|
<strong>
|
||||||
|
{{ titleText }}
|
||||||
|
</strong>
|
||||||
<div class="d-flex ml-auto">
|
<div class="d-flex ml-auto">
|
||||||
<button
|
<button
|
||||||
v-tooltip
|
v-tooltip
|
||||||
v-show="filesLength"
|
ref="actionBtn"
|
||||||
:class="{
|
|
||||||
'd-flex': filesLength
|
|
||||||
}"
|
|
||||||
:title="actionBtnText"
|
:title="actionBtnText"
|
||||||
|
:aria-label="actionBtnText"
|
||||||
|
:disabled="!filesLength"
|
||||||
|
:class="{
|
||||||
|
'disabled-content': !filesLength
|
||||||
|
}"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-default ide-staged-action-btn p-0 order-1 align-items-center"
|
class="d-flex ide-staged-action-btn p-0 border-0 align-items-center"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
data-boundary="viewport"
|
data-boundary="viewport"
|
||||||
|
@ -109,18 +130,32 @@ export default {
|
||||||
>
|
>
|
||||||
<icon
|
<icon
|
||||||
:name="actionBtnIcon"
|
:name="actionBtnIcon"
|
||||||
:size="12"
|
:size="16"
|
||||||
class="ml-auto mr-auto"
|
class="ml-auto mr-auto"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<span
|
<button
|
||||||
|
v-tooltip
|
||||||
|
v-if="!stagedList"
|
||||||
|
:title="__('Discard all changes')"
|
||||||
|
:aria-label="__('Discard all changes')"
|
||||||
|
:disabled="!filesLength"
|
||||||
:class="{
|
:class="{
|
||||||
'rounded-right': !filesLength
|
'disabled-content': !filesLength
|
||||||
}"
|
}"
|
||||||
class="ide-commit-file-count order-0 rounded-left text-center"
|
type="button"
|
||||||
|
class="d-flex ide-staged-action-btn p-0 border-0 align-items-center"
|
||||||
|
data-placement="bottom"
|
||||||
|
data-container="body"
|
||||||
|
data-boundary="viewport"
|
||||||
|
@click="openDiscardModal"
|
||||||
>
|
>
|
||||||
{{ filesLength }}
|
<icon
|
||||||
</span>
|
:size="16"
|
||||||
|
name="remove-all"
|
||||||
|
class="ml-auto mr-auto"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -143,9 +178,19 @@ export default {
|
||||||
</ul>
|
</ul>
|
||||||
<p
|
<p
|
||||||
v-else
|
v-else
|
||||||
class="multi-file-commit-list form-text text-muted"
|
class="multi-file-commit-list form-text text-muted text-center"
|
||||||
>
|
>
|
||||||
{{ __('No changes') }}
|
{{ emptyStateText }}
|
||||||
</p>
|
</p>
|
||||||
|
<gl-modal
|
||||||
|
v-if="!stagedList"
|
||||||
|
id="discard-all-changes"
|
||||||
|
:footer-primary-button-text="__('Discard all changes')"
|
||||||
|
:header-title-text="__('Discard all unstaged changes?')"
|
||||||
|
footer-primary-button-variant="danger"
|
||||||
|
@submit="discardAllChanges"
|
||||||
|
>
|
||||||
|
{{ $options.discardModalText }}
|
||||||
|
</gl-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
import tooltip from '~/vue_shared/directives/tooltip';
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
import Icon from '~/vue_shared/components/icon.vue';
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
|
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||||
import StageButton from './stage_button.vue';
|
import StageButton from './stage_button.vue';
|
||||||
import UnstageButton from './unstage_button.vue';
|
import UnstageButton from './unstage_button.vue';
|
||||||
import { viewerTypes } from '../../constants';
|
import { viewerTypes } from '../../constants';
|
||||||
|
@ -12,6 +13,7 @@ export default {
|
||||||
Icon,
|
Icon,
|
||||||
StageButton,
|
StageButton,
|
||||||
UnstageButton,
|
UnstageButton,
|
||||||
|
FileIcon,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
tooltip,
|
tooltip,
|
||||||
|
@ -48,7 +50,7 @@ export default {
|
||||||
return `${getCommitIconMap(this.file).icon}${suffix}`;
|
return `${getCommitIconMap(this.file).icon}${suffix}`;
|
||||||
},
|
},
|
||||||
iconClass() {
|
iconClass() {
|
||||||
return `${getCommitIconMap(this.file).class} append-right-8`;
|
return `${getCommitIconMap(this.file).class} ml-auto mr-auto`;
|
||||||
},
|
},
|
||||||
fullKey() {
|
fullKey() {
|
||||||
return `${this.keyPrefix}-${this.file.key}`;
|
return `${this.keyPrefix}-${this.file.key}`;
|
||||||
|
@ -105,17 +107,24 @@ export default {
|
||||||
@click="openFileInEditor"
|
@click="openFileInEditor"
|
||||||
>
|
>
|
||||||
<span class="multi-file-commit-list-file-path d-flex align-items-center">
|
<span class="multi-file-commit-list-file-path d-flex align-items-center">
|
||||||
<icon
|
<file-icon
|
||||||
:name="iconName"
|
:file-name="file.name"
|
||||||
:size="16"
|
class="append-right-8"
|
||||||
:css-classes="iconClass"
|
|
||||||
/>{{ file.name }}
|
/>{{ file.name }}
|
||||||
</span>
|
</span>
|
||||||
|
<div class="ml-auto d-flex align-items-center">
|
||||||
|
<div class="d-flex align-items-center ide-commit-list-changed-icon">
|
||||||
|
<icon
|
||||||
|
:name="iconName"
|
||||||
|
:size="16"
|
||||||
|
:css-classes="iconClass"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<component
|
||||||
|
:is="actionComponent"
|
||||||
|
:path="file.path"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<component
|
|
||||||
:is="actionComponent"
|
|
||||||
:path="file.path"
|
|
||||||
class="d-flex position-absolute"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<script>
|
<script>
|
||||||
|
import $ from 'jquery';
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
|
import { sprintf, __ } from '~/locale';
|
||||||
import Icon from '~/vue_shared/components/icon.vue';
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
import tooltip from '~/vue_shared/directives/tooltip';
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
|
import GlModal from '~/vue_shared/components/gl_modal.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Icon,
|
Icon,
|
||||||
|
GlModal,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
tooltip,
|
tooltip,
|
||||||
|
@ -16,8 +20,22 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
modalId() {
|
||||||
|
return `discard-file-${this.path}`;
|
||||||
|
},
|
||||||
|
modalTitle() {
|
||||||
|
return sprintf(
|
||||||
|
__('Discard changes to %{path}?'),
|
||||||
|
{ path: this.path },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['stageChange', 'discardFileChanges']),
|
...mapActions(['stageChange', 'discardFileChanges']),
|
||||||
|
showDiscardModal() {
|
||||||
|
$(document.getElementById(this.modalId)).modal('show');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -25,51 +43,50 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-once
|
v-once
|
||||||
class="multi-file-discard-btn dropdown"
|
class="multi-file-discard-btn d-flex"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-tooltip
|
v-tooltip
|
||||||
:aria-label="__('Stage changes')"
|
:aria-label="__('Stage changes')"
|
||||||
:title="__('Stage changes')"
|
:title="__('Stage changes')"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-blank append-right-5 d-flex align-items-center"
|
class="btn btn-blank align-items-center"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
data-boundary="viewport"
|
data-boundary="viewport"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
@click.stop="stageChange(path)"
|
@click.stop.prevent="stageChange(path)"
|
||||||
>
|
>
|
||||||
<icon
|
<icon
|
||||||
:size="12"
|
:size="16"
|
||||||
name="mobile-issue-close"
|
name="mobile-issue-close"
|
||||||
|
class="ml-auto mr-auto"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-tooltip
|
v-tooltip
|
||||||
:title="__('More actions')"
|
:aria-label="__('Discard changes')"
|
||||||
|
:title="__('Discard changes')"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-blank d-flex align-items-center"
|
class="btn btn-blank align-items-center"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
data-boundary="viewport"
|
data-boundary="viewport"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
data-toggle="dropdown"
|
@click.stop.prevent="showDiscardModal"
|
||||||
data-display="static"
|
|
||||||
>
|
>
|
||||||
<icon
|
<icon
|
||||||
:size="12"
|
:size="16"
|
||||||
name="ellipsis_h"
|
name="remove"
|
||||||
|
class="ml-auto mr-auto"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-right">
|
<gl-modal
|
||||||
<ul>
|
:id="modalId"
|
||||||
<li>
|
:header-title-text="modalTitle"
|
||||||
<button
|
:footer-primary-button-text="__('Discard changes')"
|
||||||
type="button"
|
footer-primary-button-variant="danger"
|
||||||
@click.stop="discardFileChanges(path)"
|
@submit="discardFileChanges(path)"
|
||||||
>
|
>
|
||||||
{{ __('Discard changes') }}
|
{{ __("You will loose all changes you've made to this file. This action cannot be undone.") }}
|
||||||
</button>
|
</gl-modal>
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -25,22 +25,23 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-once
|
v-once
|
||||||
class="multi-file-discard-btn"
|
class="multi-file-discard-btn d-flex"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-tooltip
|
v-tooltip
|
||||||
:aria-label="__('Unstage changes')"
|
:aria-label="__('Unstage changes')"
|
||||||
:title="__('Unstage changes')"
|
:title="__('Unstage changes')"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-blank d-flex align-items-center"
|
class="btn btn-blank align-items-center"
|
||||||
data-container="body"
|
data-container="body"
|
||||||
data-boundary="viewport"
|
data-boundary="viewport"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
@click="unstageChange(path)"
|
@click.stop.prevent="unstageChange(path)"
|
||||||
>
|
>
|
||||||
<icon
|
<icon
|
||||||
:size="12"
|
:size="16"
|
||||||
name="history"
|
name="redo"
|
||||||
|
class="ml-auto mr-auto"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
80
app/assets/javascripts/ide/components/file_templates/bar.vue
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
<script>
|
||||||
|
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||||
|
import Dropdown from './dropdown.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Dropdown,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['activeFile']),
|
||||||
|
...mapGetters('fileTemplates', ['templateTypes']),
|
||||||
|
...mapState('fileTemplates', ['selectedTemplateType', 'updateSuccess']),
|
||||||
|
showTemplatesDropdown() {
|
||||||
|
return Object.keys(this.selectedTemplateType).length > 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
activeFile: 'setInitialType',
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.setInitialType();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions('fileTemplates', [
|
||||||
|
'setSelectedTemplateType',
|
||||||
|
'fetchTemplate',
|
||||||
|
'undoFileTemplate',
|
||||||
|
]),
|
||||||
|
setInitialType() {
|
||||||
|
const initialTemplateType = this.templateTypes.find(t => t.name === this.activeFile.name);
|
||||||
|
|
||||||
|
if (initialTemplateType) {
|
||||||
|
this.setSelectedTemplateType(initialTemplateType);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectTemplateType(templateType) {
|
||||||
|
this.setSelectedTemplateType(templateType);
|
||||||
|
},
|
||||||
|
selectTemplate(template) {
|
||||||
|
this.fetchTemplate(template);
|
||||||
|
},
|
||||||
|
undo() {
|
||||||
|
this.undoFileTemplate();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-flex align-items-center ide-file-templates">
|
||||||
|
<strong class="append-right-default">
|
||||||
|
{{ __('File templates') }}
|
||||||
|
</strong>
|
||||||
|
<dropdown
|
||||||
|
:data="templateTypes"
|
||||||
|
:label="selectedTemplateType.name || __('Choose a type...')"
|
||||||
|
class="mr-2"
|
||||||
|
@click="selectTemplateType"
|
||||||
|
/>
|
||||||
|
<dropdown
|
||||||
|
v-if="showTemplatesDropdown"
|
||||||
|
:label="__('Choose a template...')"
|
||||||
|
:is-async-data="true"
|
||||||
|
:searchable="true"
|
||||||
|
:title="__('File templates')"
|
||||||
|
class="mr-2"
|
||||||
|
@click="selectTemplate"
|
||||||
|
/>
|
||||||
|
<transition name="fade">
|
||||||
|
<button
|
||||||
|
v-show="updateSuccess"
|
||||||
|
type="button"
|
||||||
|
class="btn btn-default"
|
||||||
|
@click="undo"
|
||||||
|
>
|
||||||
|
{{ __('Undo') }}
|
||||||
|
</button>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,125 @@
|
||||||
|
<script>
|
||||||
|
import $ from 'jquery';
|
||||||
|
import { mapActions, mapState } from 'vuex';
|
||||||
|
import LoadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||||
|
import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
DropdownButton,
|
||||||
|
LoadingIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
isAsyncData: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
searchable: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
search: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState('fileTemplates', ['templates', 'isLoading']),
|
||||||
|
outputData() {
|
||||||
|
return (this.isAsyncData ? this.templates : this.data).filter(t => {
|
||||||
|
if (!this.searchable) return true;
|
||||||
|
|
||||||
|
return t.name.toLowerCase().indexOf(this.search.toLowerCase()) >= 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showLoading() {
|
||||||
|
return this.isAsyncData ? this.isLoading : false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
$(this.$el).on('show.bs.dropdown', this.fetchTemplatesIfAsync);
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
$(this.$el).off('show.bs.dropdown', this.fetchTemplatesIfAsync);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions('fileTemplates', ['fetchTemplateTypes']),
|
||||||
|
fetchTemplatesIfAsync() {
|
||||||
|
if (this.isAsyncData) {
|
||||||
|
this.fetchTemplateTypes();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickItem(item) {
|
||||||
|
this.$emit('click', item);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="dropdown">
|
||||||
|
<dropdown-button
|
||||||
|
:toggle-text="label"
|
||||||
|
data-display="static"
|
||||||
|
/>
|
||||||
|
<div class="dropdown-menu pb-0">
|
||||||
|
<div
|
||||||
|
v-if="title"
|
||||||
|
class="dropdown-title ml-0 mr-0"
|
||||||
|
>
|
||||||
|
{{ title }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="!showLoading && searchable"
|
||||||
|
class="dropdown-input"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="search"
|
||||||
|
:placeholder="__('Filter...')"
|
||||||
|
type="search"
|
||||||
|
class="dropdown-input-field"
|
||||||
|
/>
|
||||||
|
<i
|
||||||
|
aria-hidden="true"
|
||||||
|
class="fa fa-search dropdown-input-search"
|
||||||
|
></i>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown-content">
|
||||||
|
<loading-icon
|
||||||
|
v-if="showLoading"
|
||||||
|
size="2"
|
||||||
|
/>
|
||||||
|
<ul v-else>
|
||||||
|
<li
|
||||||
|
v-for="(item, index) in outputData"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
@click="clickItem(item)"
|
||||||
|
>
|
||||||
|
{{ item.name }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -10,6 +10,7 @@ import RepoEditor from './repo_editor.vue';
|
||||||
import FindFile from './file_finder/index.vue';
|
import FindFile from './file_finder/index.vue';
|
||||||
import RightPane from './panes/right.vue';
|
import RightPane from './panes/right.vue';
|
||||||
import ErrorMessage from './error_message.vue';
|
import ErrorMessage from './error_message.vue';
|
||||||
|
import CommitEditorHeader from './commit_sidebar/editor_header.vue';
|
||||||
|
|
||||||
const originalStopCallback = Mousetrap.stopCallback;
|
const originalStopCallback = Mousetrap.stopCallback;
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@ export default {
|
||||||
FindFile,
|
FindFile,
|
||||||
RightPane,
|
RightPane,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
|
CommitEditorHeader,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
|
@ -34,7 +36,7 @@ export default {
|
||||||
'currentProjectId',
|
'currentProjectId',
|
||||||
'errorMessage',
|
'errorMessage',
|
||||||
]),
|
]),
|
||||||
...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges']),
|
...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges', 'isCommitModeActive']),
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
window.onbeforeunload = e => this.onBeforeUnload(e);
|
window.onbeforeunload = e => this.onBeforeUnload(e);
|
||||||
|
@ -78,13 +80,13 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<article class="ide">
|
<article class="ide position-relative d-flex flex-column align-items-stretch">
|
||||||
<error-message
|
<error-message
|
||||||
v-if="errorMessage"
|
v-if="errorMessage"
|
||||||
:message="errorMessage"
|
:message="errorMessage"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="ide-view"
|
class="ide-view flex-grow d-flex"
|
||||||
>
|
>
|
||||||
<find-file
|
<find-file
|
||||||
v-show="fileFindVisible"
|
v-show="fileFindVisible"
|
||||||
|
@ -96,7 +98,12 @@ export default {
|
||||||
<template
|
<template
|
||||||
v-if="activeFile"
|
v-if="activeFile"
|
||||||
>
|
>
|
||||||
|
<commit-editor-header
|
||||||
|
v-if="isCommitModeActive"
|
||||||
|
:active-file="activeFile"
|
||||||
|
/>
|
||||||
<repo-tabs
|
<repo-tabs
|
||||||
|
v-else
|
||||||
:active-file="activeFile"
|
:active-file="activeFile"
|
||||||
:files="openFiles"
|
:files="openFiles"
|
||||||
:viewer="viewer"
|
:viewer="viewer"
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import Icon from '~/vue_shared/components/icon.vue';
|
import Icon from '~/vue_shared/components/icon.vue';
|
||||||
|
import tooltip from '~/vue_shared/directives/tooltip';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
directives: {
|
||||||
|
tooltip,
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
Icon,
|
Icon,
|
||||||
},
|
},
|
||||||
|
@ -26,6 +30,11 @@ export default {
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
tooltipTitle() {
|
||||||
|
return this.showLabel ? '' : this.label;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
clicked() {
|
clicked() {
|
||||||
this.$emit('click');
|
this.$emit('click');
|
||||||
|
@ -36,7 +45,9 @@ export default {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
|
v-tooltip
|
||||||
:aria-label="label"
|
:aria-label="label"
|
||||||
|
:title="tooltipTitle"
|
||||||
type="button"
|
type="button"
|
||||||
class="btn-blank"
|
class="btn-blank"
|
||||||
@click.stop.prevent="clicked"
|
@click.stop.prevent="clicked"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
|
import $ from 'jquery';
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
import { mapActions, mapState } from 'vuex';
|
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||||
import GlModal from '~/vue_shared/components/gl_modal.vue';
|
import GlModal from '~/vue_shared/components/gl_modal.vue';
|
||||||
import { modalTypes } from '../../constants';
|
import { modalTypes } from '../../constants';
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['entryModal']),
|
...mapState(['entryModal']),
|
||||||
|
...mapGetters('fileTemplates', ['templateTypes']),
|
||||||
entryName: {
|
entryName: {
|
||||||
get() {
|
get() {
|
||||||
if (this.entryModal.type === modalTypes.rename) {
|
if (this.entryModal.type === modalTypes.rename) {
|
||||||
|
@ -31,7 +33,9 @@ export default {
|
||||||
if (this.entryModal.type === modalTypes.tree) {
|
if (this.entryModal.type === modalTypes.tree) {
|
||||||
return __('Create new directory');
|
return __('Create new directory');
|
||||||
} else if (this.entryModal.type === modalTypes.rename) {
|
} else if (this.entryModal.type === modalTypes.rename) {
|
||||||
return this.entryModal.entry.type === modalTypes.tree ? __('Rename folder') : __('Rename file');
|
return this.entryModal.entry.type === modalTypes.tree
|
||||||
|
? __('Rename folder')
|
||||||
|
: __('Rename file');
|
||||||
}
|
}
|
||||||
|
|
||||||
return __('Create new file');
|
return __('Create new file');
|
||||||
|
@ -40,11 +44,16 @@ export default {
|
||||||
if (this.entryModal.type === modalTypes.tree) {
|
if (this.entryModal.type === modalTypes.tree) {
|
||||||
return __('Create directory');
|
return __('Create directory');
|
||||||
} else if (this.entryModal.type === modalTypes.rename) {
|
} else if (this.entryModal.type === modalTypes.rename) {
|
||||||
return this.entryModal.entry.type === modalTypes.tree ? __('Rename folder') : __('Rename file');
|
return this.entryModal.entry.type === modalTypes.tree
|
||||||
|
? __('Rename folder')
|
||||||
|
: __('Rename file');
|
||||||
}
|
}
|
||||||
|
|
||||||
return __('Create file');
|
return __('Create file');
|
||||||
},
|
},
|
||||||
|
isCreatingNew() {
|
||||||
|
return this.entryModal.type !== modalTypes.rename;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['createTempEntry', 'renameEntry']),
|
...mapActions(['createTempEntry', 'renameEntry']),
|
||||||
|
@ -61,6 +70,14 @@ export default {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
createFromTemplate(template) {
|
||||||
|
this.createTempEntry({
|
||||||
|
name: template.name,
|
||||||
|
type: this.entryModal.type,
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#ide-new-entry').modal('toggle');
|
||||||
|
},
|
||||||
focusInput() {
|
focusInput() {
|
||||||
this.$refs.fieldName.focus();
|
this.$refs.fieldName.focus();
|
||||||
},
|
},
|
||||||
|
@ -77,6 +94,7 @@ export default {
|
||||||
:header-title-text="modalTitle"
|
:header-title-text="modalTitle"
|
||||||
:footer-primary-button-text="buttonLabel"
|
:footer-primary-button-text="buttonLabel"
|
||||||
footer-primary-button-variant="success"
|
footer-primary-button-variant="success"
|
||||||
|
modal-size="lg"
|
||||||
@submit="submitForm"
|
@submit="submitForm"
|
||||||
@open="focusInput"
|
@open="focusInput"
|
||||||
@closed="closedModal"
|
@closed="closedModal"
|
||||||
|
@ -84,16 +102,35 @@ export default {
|
||||||
<div
|
<div
|
||||||
class="form-group row"
|
class="form-group row"
|
||||||
>
|
>
|
||||||
<label class="label-bold col-form-label col-sm-3">
|
<label class="label-bold col-form-label col-sm-2">
|
||||||
{{ __('Name') }}
|
{{ __('Name') }}
|
||||||
</label>
|
</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-10">
|
||||||
<input
|
<input
|
||||||
ref="fieldName"
|
ref="fieldName"
|
||||||
v-model="entryName"
|
v-model="entryName"
|
||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
placeholder="/dir/file_name"
|
||||||
/>
|
/>
|
||||||
|
<ul
|
||||||
|
v-if="isCreatingNew"
|
||||||
|
class="prepend-top-default list-inline"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="(template, index) in templateTypes"
|
||||||
|
:key="index"
|
||||||
|
class="list-inline-item"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-missing p-1 pr-2 pl-2"
|
||||||
|
@click="createFromTemplate(template)"
|
||||||
|
>
|
||||||
|
{{ template.name }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</gl-modal>
|
</gl-modal>
|
||||||
|
|
|
@ -24,12 +24,6 @@ export default {
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.$refs.fileUpload.addEventListener('change', this.openFile);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
this.$refs.fileUpload.removeEventListener('change', this.openFile);
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
createFile(target, file, isText) {
|
createFile(target, file, isText) {
|
||||||
const { name } = file;
|
const { name } = file;
|
||||||
|
@ -85,6 +79,8 @@ export default {
|
||||||
ref="fileUpload"
|
ref="fileUpload"
|
||||||
type="file"
|
type="file"
|
||||||
class="hidden"
|
class="hidden"
|
||||||
|
multiple
|
||||||
|
@change="openFile"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -95,8 +95,9 @@ export default {
|
||||||
:file-list="changedFiles"
|
:file-list="changedFiles"
|
||||||
:action-btn-text="__('Stage all changes')"
|
:action-btn-text="__('Stage all changes')"
|
||||||
:active-file-key="activeFileKey"
|
:active-file-key="activeFileKey"
|
||||||
|
:empty-state-text="__('There are no unstaged changes')"
|
||||||
action="stageAllChanges"
|
action="stageAllChanges"
|
||||||
action-btn-icon="mobile-issue-close"
|
action-btn-icon="stage-all"
|
||||||
item-action-component="stage-button"
|
item-action-component="stage-button"
|
||||||
class="is-first"
|
class="is-first"
|
||||||
icon-name="unstaged"
|
icon-name="unstaged"
|
||||||
|
@ -108,8 +109,9 @@ export default {
|
||||||
:action-btn-text="__('Unstage all changes')"
|
:action-btn-text="__('Unstage all changes')"
|
||||||
:staged-list="true"
|
:staged-list="true"
|
||||||
:active-file-key="activeFileKey"
|
:active-file-key="activeFileKey"
|
||||||
|
:empty-state-text="__('There are no staged changes')"
|
||||||
action="unstageAllChanges"
|
action="unstageAllChanges"
|
||||||
action-btn-icon="history"
|
action-btn-icon="unstage-all"
|
||||||
item-action-component="unstage-button"
|
item-action-component="unstage-button"
|
||||||
icon-name="staged"
|
icon-name="staged"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -6,12 +6,14 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
||||||
import { activityBarViews, viewerTypes } from '../constants';
|
import { activityBarViews, viewerTypes } from '../constants';
|
||||||
import Editor from '../lib/editor';
|
import Editor from '../lib/editor';
|
||||||
import ExternalLink from './external_link.vue';
|
import ExternalLink from './external_link.vue';
|
||||||
|
import FileTemplatesBar from './file_templates/bar.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
ContentViewer,
|
ContentViewer,
|
||||||
DiffViewer,
|
DiffViewer,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
|
FileTemplatesBar,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
file: {
|
file: {
|
||||||
|
@ -34,6 +36,7 @@ export default {
|
||||||
'isCommitModeActive',
|
'isCommitModeActive',
|
||||||
'isReviewModeActive',
|
'isReviewModeActive',
|
||||||
]),
|
]),
|
||||||
|
...mapGetters('fileTemplates', ['showFileTemplatesBar']),
|
||||||
shouldHideEditor() {
|
shouldHideEditor() {
|
||||||
return this.file && this.file.binary && !this.file.content;
|
return this.file && this.file.binary && !this.file.content;
|
||||||
},
|
},
|
||||||
|
@ -216,7 +219,7 @@ export default {
|
||||||
id="ide"
|
id="ide"
|
||||||
class="blob-viewer-container blob-editor-container"
|
class="blob-viewer-container blob-editor-container"
|
||||||
>
|
>
|
||||||
<div class="ide-mode-tabs clearfix" >
|
<div class="ide-mode-tabs clearfix">
|
||||||
<ul
|
<ul
|
||||||
v-if="!shouldHideEditor && isEditModeActive"
|
v-if="!shouldHideEditor && isEditModeActive"
|
||||||
class="nav-links float-left"
|
class="nav-links float-left"
|
||||||
|
@ -249,6 +252,9 @@ export default {
|
||||||
:file="file"
|
:file="file"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<file-templates-bar
|
||||||
|
v-if="showFileTemplatesBar(file.name)"
|
||||||
|
/>
|
||||||
<div
|
<div
|
||||||
v-show="!shouldHideEditor && file.viewMode ==='editor'"
|
v-show="!shouldHideEditor && file.viewMode ==='editor'"
|
||||||
ref="editor"
|
ref="editor"
|
||||||
|
|
|
@ -95,16 +95,18 @@ export default {
|
||||||
return this.file.changed || this.file.tempFile || this.file.staged;
|
return this.file.changed || this.file.tempFile || this.file.staged;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
'file.active': function fileActiveWatch(active) {
|
||||||
|
if (this.file.type === 'blob' && active) {
|
||||||
|
this.scrollIntoView();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.hasPathAtCurrentRoute()) {
|
if (this.hasPathAtCurrentRoute()) {
|
||||||
this.scrollIntoView(true);
|
this.scrollIntoView(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated() {
|
|
||||||
if (this.file.type === 'blob' && this.file.active) {
|
|
||||||
this.scrollIntoView();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['toggleTreeOpen']),
|
...mapActions(['toggleTreeOpen']),
|
||||||
clickFile() {
|
clickFile() {
|
||||||
|
|
|
@ -3,7 +3,6 @@ import VueRouter from 'vue-router';
|
||||||
import { join as joinPath } from 'path';
|
import { join as joinPath } from 'path';
|
||||||
import flash from '~/flash';
|
import flash from '~/flash';
|
||||||
import store from './stores';
|
import store from './stores';
|
||||||
import { activityBarViews } from './constants';
|
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
@ -74,98 +73,23 @@ router.beforeEach((to, from, next) => {
|
||||||
projectId: to.params.project,
|
projectId: to.params.project,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const fullProjectId = `${to.params.namespace}/${to.params.project}`;
|
const basePath = to.params[0] || '';
|
||||||
|
const projectId = `${to.params.namespace}/${to.params.project}`;
|
||||||
const branchId = to.params.branchid;
|
const branchId = to.params.branchid;
|
||||||
|
const mergeRequestId = to.params.mrid;
|
||||||
|
|
||||||
if (branchId) {
|
if (branchId) {
|
||||||
const basePath = to.params[0] || '';
|
store.dispatch('openBranch', {
|
||||||
|
projectId,
|
||||||
store.dispatch('setCurrentBranchId', branchId);
|
|
||||||
|
|
||||||
store.dispatch('getBranchData', {
|
|
||||||
projectId: fullProjectId,
|
|
||||||
branchId,
|
branchId,
|
||||||
|
basePath,
|
||||||
|
});
|
||||||
|
} else if (mergeRequestId) {
|
||||||
|
store.dispatch('openMergeRequest', {
|
||||||
|
projectId,
|
||||||
|
mergeRequestId,
|
||||||
|
targetProjectId: to.query.target_project,
|
||||||
});
|
});
|
||||||
|
|
||||||
store
|
|
||||||
.dispatch('getFiles', {
|
|
||||||
projectId: fullProjectId,
|
|
||||||
branchId,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
if (basePath) {
|
|
||||||
const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
|
|
||||||
const treeEntryKey = Object.keys(store.state.entries).find(
|
|
||||||
key => key === path && !store.state.entries[key].pending,
|
|
||||||
);
|
|
||||||
const treeEntry = store.state.entries[treeEntryKey];
|
|
||||||
|
|
||||||
if (treeEntry) {
|
|
||||||
store.dispatch('handleTreeEntryAction', treeEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
} else if (to.params.mrid) {
|
|
||||||
store
|
|
||||||
.dispatch('getMergeRequestData', {
|
|
||||||
projectId: fullProjectId,
|
|
||||||
targetProjectId: to.query.target_project,
|
|
||||||
mergeRequestId: to.params.mrid,
|
|
||||||
})
|
|
||||||
.then(mr => {
|
|
||||||
store.dispatch('updateActivityBarView', activityBarViews.review);
|
|
||||||
|
|
||||||
store.dispatch('getBranchData', {
|
|
||||||
projectId: fullProjectId,
|
|
||||||
branchId: mr.source_branch,
|
|
||||||
});
|
|
||||||
|
|
||||||
return store.dispatch('getFiles', {
|
|
||||||
projectId: fullProjectId,
|
|
||||||
branchId: mr.source_branch,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(() =>
|
|
||||||
store.dispatch('getMergeRequestVersions', {
|
|
||||||
projectId: fullProjectId,
|
|
||||||
targetProjectId: to.query.target_project,
|
|
||||||
mergeRequestId: to.params.mrid,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then(() =>
|
|
||||||
store.dispatch('getMergeRequestChanges', {
|
|
||||||
projectId: fullProjectId,
|
|
||||||
targetProjectId: to.query.target_project,
|
|
||||||
mergeRequestId: to.params.mrid,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then(mrChanges => {
|
|
||||||
mrChanges.changes.forEach((change, ind) => {
|
|
||||||
const changeTreeEntry = store.state.entries[change.new_path];
|
|
||||||
|
|
||||||
if (changeTreeEntry) {
|
|
||||||
store.dispatch('setFileMrChange', {
|
|
||||||
file: changeTreeEntry,
|
|
||||||
mrChange: change,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ind < 10) {
|
|
||||||
store.dispatch('getFileData', {
|
|
||||||
path: change.new_path,
|
|
||||||
makeFileActive: ind === 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
flash('Error while loading the merge request. Please try again.');
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { visitUrl } from '~/lib/utils/url_utility';
|
||||||
import flash from '~/flash';
|
import flash from '~/flash';
|
||||||
import * as types from './mutation_types';
|
import * as types from './mutation_types';
|
||||||
import FilesDecoratorWorker from './workers/files_decorator_worker';
|
import FilesDecoratorWorker from './workers/files_decorator_worker';
|
||||||
|
import { stageKeys } from '../constants';
|
||||||
|
|
||||||
export const redirectToUrl = (_, url) => visitUrl(url);
|
export const redirectToUrl = (_, url) => visitUrl(url);
|
||||||
|
|
||||||
|
@ -122,14 +123,28 @@ export const scrollToTab = () => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stageAllChanges = ({ state, commit }) => {
|
export const stageAllChanges = ({ state, commit, dispatch }) => {
|
||||||
|
const openFile = state.openFiles[0];
|
||||||
|
|
||||||
commit(types.SET_LAST_COMMIT_MSG, '');
|
commit(types.SET_LAST_COMMIT_MSG, '');
|
||||||
|
|
||||||
state.changedFiles.forEach(file => commit(types.STAGE_CHANGE, file.path));
|
state.changedFiles.forEach(file => commit(types.STAGE_CHANGE, file.path));
|
||||||
|
|
||||||
|
dispatch('openPendingTab', {
|
||||||
|
file: state.stagedFiles.find(f => f.path === openFile.path),
|
||||||
|
keyPrefix: stageKeys.staged,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const unstageAllChanges = ({ state, commit }) => {
|
export const unstageAllChanges = ({ state, commit, dispatch }) => {
|
||||||
|
const openFile = state.openFiles[0];
|
||||||
|
|
||||||
state.stagedFiles.forEach(file => commit(types.UNSTAGE_CHANGE, file.path));
|
state.stagedFiles.forEach(file => commit(types.UNSTAGE_CHANGE, file.path));
|
||||||
|
|
||||||
|
dispatch('openPendingTab', {
|
||||||
|
file: state.changedFiles.find(f => f.path === openFile.path),
|
||||||
|
keyPrefix: stageKeys.unstaged,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateViewer = ({ commit }, viewer) => {
|
export const updateViewer = ({ commit }, viewer) => {
|
||||||
|
@ -206,6 +221,7 @@ export const resetOpenFiles = ({ commit }) => commit(types.RESET_OPEN_FILES);
|
||||||
|
|
||||||
export const renameEntry = ({ dispatch, commit, state }, { path, name, entryPath = null }) => {
|
export const renameEntry = ({ dispatch, commit, state }, { path, name, entryPath = null }) => {
|
||||||
const entry = state.entries[entryPath || path];
|
const entry = state.entries[entryPath || path];
|
||||||
|
|
||||||
commit(types.RENAME_ENTRY, { path, name, entryPath });
|
commit(types.RENAME_ENTRY, { path, name, entryPath });
|
||||||
|
|
||||||
if (entry.type === 'tree') {
|
if (entry.type === 'tree') {
|
||||||
|
@ -214,7 +230,7 @@ export const renameEntry = ({ dispatch, commit, state }, { path, name, entryPath
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!entryPath) {
|
if (!entryPath && !entry.tempFile) {
|
||||||
dispatch('deleteEntry', path);
|
dispatch('deleteEntry', path);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@ import service from '../../services';
|
||||||
import * as types from '../mutation_types';
|
import * as types from '../mutation_types';
|
||||||
import router from '../../ide_router';
|
import router from '../../ide_router';
|
||||||
import { setPageTitle } from '../utils';
|
import { setPageTitle } from '../utils';
|
||||||
import { viewerTypes } from '../../constants';
|
import { viewerTypes, stageKeys } from '../../constants';
|
||||||
|
|
||||||
export const closeFile = ({ commit, state, dispatch }, file) => {
|
export const closeFile = ({ commit, state, dispatch }, file) => {
|
||||||
const { path } = file;
|
const { path } = file;
|
||||||
|
@ -54,9 +54,6 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
|
||||||
|
|
||||||
commit(types.SET_FILE_ACTIVE, { path, active: true });
|
commit(types.SET_FILE_ACTIVE, { path, active: true });
|
||||||
dispatch('scrollToTab');
|
dispatch('scrollToTab');
|
||||||
|
|
||||||
commit(types.SET_CURRENT_PROJECT, file.projectId);
|
|
||||||
commit(types.SET_CURRENT_BRANCH, file.branchId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
|
export const getFileData = ({ state, commit, dispatch }, { path, makeFileActive = true }) => {
|
||||||
|
@ -211,8 +208,9 @@ export const discardFileChanges = ({ dispatch, state, commit, getters }, path) =
|
||||||
eventHub.$emit(`editor.update.model.dispose.unstaged-${file.key}`, file.content);
|
eventHub.$emit(`editor.update.model.dispose.unstaged-${file.key}`, file.content);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stageChange = ({ commit, state }, path) => {
|
export const stageChange = ({ commit, state, dispatch }, path) => {
|
||||||
const stagedFile = state.stagedFiles.find(f => f.path === path);
|
const stagedFile = state.stagedFiles.find(f => f.path === path);
|
||||||
|
const openFile = state.openFiles.find(f => f.path === path);
|
||||||
|
|
||||||
commit(types.STAGE_CHANGE, path);
|
commit(types.STAGE_CHANGE, path);
|
||||||
commit(types.SET_LAST_COMMIT_MSG, '');
|
commit(types.SET_LAST_COMMIT_MSG, '');
|
||||||
|
@ -220,21 +218,39 @@ export const stageChange = ({ commit, state }, path) => {
|
||||||
if (stagedFile) {
|
if (stagedFile) {
|
||||||
eventHub.$emit(`editor.update.model.new.content.staged-${stagedFile.key}`, stagedFile.content);
|
eventHub.$emit(`editor.update.model.new.content.staged-${stagedFile.key}`, stagedFile.content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (openFile && openFile.active) {
|
||||||
|
const file = state.stagedFiles.find(f => f.path === path);
|
||||||
|
|
||||||
|
dispatch('openPendingTab', {
|
||||||
|
file,
|
||||||
|
keyPrefix: stageKeys.staged,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const unstageChange = ({ commit }, path) => {
|
export const unstageChange = ({ commit, dispatch, state }, path) => {
|
||||||
|
const openFile = state.openFiles.find(f => f.path === path);
|
||||||
|
|
||||||
commit(types.UNSTAGE_CHANGE, path);
|
commit(types.UNSTAGE_CHANGE, path);
|
||||||
|
|
||||||
|
if (openFile && openFile.active) {
|
||||||
|
const file = state.changedFiles.find(f => f.path === path);
|
||||||
|
|
||||||
|
dispatch('openPendingTab', {
|
||||||
|
file,
|
||||||
|
keyPrefix: stageKeys.unstaged,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openPendingTab = ({ commit, getters, dispatch, state }, { file, keyPrefix }) => {
|
export const openPendingTab = ({ commit, getters, state }, { file, keyPrefix }) => {
|
||||||
if (getters.activeFile && getters.activeFile.key === `${keyPrefix}-${file.key}`) return false;
|
if (getters.activeFile && getters.activeFile.key === `${keyPrefix}-${file.key}`) return false;
|
||||||
|
|
||||||
state.openFiles.forEach(f => eventHub.$emit(`editor.update.model.dispose.${f.key}`));
|
state.openFiles.forEach(f => eventHub.$emit(`editor.update.model.dispose.${f.key}`));
|
||||||
|
|
||||||
commit(types.ADD_PENDING_TAB, { file, keyPrefix });
|
commit(types.ADD_PENDING_TAB, { file, keyPrefix });
|
||||||
|
|
||||||
dispatch('scrollToTab');
|
|
||||||
|
|
||||||
router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
|
router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { __ } from '../../../locale';
|
import flash from '~/flash';
|
||||||
|
import { __ } from '~/locale';
|
||||||
import service from '../../services';
|
import service from '../../services';
|
||||||
import * as types from '../mutation_types';
|
import * as types from '../mutation_types';
|
||||||
|
import { activityBarViews } from '../../constants';
|
||||||
|
|
||||||
export const getMergeRequestData = (
|
export const getMergeRequestData = (
|
||||||
{ commit, dispatch, state },
|
{ commit, dispatch, state },
|
||||||
|
@ -104,3 +106,67 @@ export const getMergeRequestVersions = (
|
||||||
resolve(state.projects[projectId].mergeRequests[mergeRequestId].versions);
|
resolve(state.projects[projectId].mergeRequests[mergeRequestId].versions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const openMergeRequest = (
|
||||||
|
{ dispatch, state },
|
||||||
|
{ projectId, targetProjectId, mergeRequestId } = {},
|
||||||
|
) =>
|
||||||
|
dispatch('getMergeRequestData', {
|
||||||
|
projectId,
|
||||||
|
targetProjectId,
|
||||||
|
mergeRequestId,
|
||||||
|
})
|
||||||
|
.then(mr => {
|
||||||
|
dispatch('setCurrentBranchId', mr.source_branch);
|
||||||
|
|
||||||
|
dispatch('getBranchData', {
|
||||||
|
projectId,
|
||||||
|
branchId: mr.source_branch,
|
||||||
|
});
|
||||||
|
|
||||||
|
return dispatch('getFiles', {
|
||||||
|
projectId,
|
||||||
|
branchId: mr.source_branch,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() =>
|
||||||
|
dispatch('getMergeRequestVersions', {
|
||||||
|
projectId,
|
||||||
|
targetProjectId,
|
||||||
|
mergeRequestId,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then(() =>
|
||||||
|
dispatch('getMergeRequestChanges', {
|
||||||
|
projectId,
|
||||||
|
targetProjectId,
|
||||||
|
mergeRequestId,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.then(mrChanges => {
|
||||||
|
if (mrChanges.changes.length) {
|
||||||
|
dispatch('updateActivityBarView', activityBarViews.review);
|
||||||
|
}
|
||||||
|
|
||||||
|
mrChanges.changes.forEach((change, ind) => {
|
||||||
|
const changeTreeEntry = state.entries[change.new_path];
|
||||||
|
|
||||||
|
if (changeTreeEntry) {
|
||||||
|
dispatch('setFileMrChange', {
|
||||||
|
file: changeTreeEntry,
|
||||||
|
mrChange: change,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ind < 10) {
|
||||||
|
dispatch('getFileData', {
|
||||||
|
path: change.new_path,
|
||||||
|
makeFileActive: ind === 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
flash(__('Error while loading the merge request. Please try again.'));
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
|
|
@ -124,3 +124,35 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
|
||||||
actionPayload: branchId,
|
actionPayload: branchId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const openBranch = (
|
||||||
|
{ dispatch, state },
|
||||||
|
{ projectId, branchId, basePath },
|
||||||
|
) => {
|
||||||
|
dispatch('setCurrentBranchId', branchId);
|
||||||
|
|
||||||
|
dispatch('getBranchData', {
|
||||||
|
projectId,
|
||||||
|
branchId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
dispatch('getFiles', {
|
||||||
|
projectId,
|
||||||
|
branchId,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
if (basePath) {
|
||||||
|
const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath;
|
||||||
|
const treeEntryKey = Object.keys(state.entries).find(
|
||||||
|
key => key === path && !state.entries[key].pending,
|
||||||
|
);
|
||||||
|
const treeEntry = state.entries[treeEntryKey];
|
||||||
|
|
||||||
|
if (treeEntry) {
|
||||||
|
dispatch('handleTreeEntryAction', treeEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|