New upstream version 11.3.10+dfsg
|
@ -33,6 +33,15 @@ rules:
|
|||
- error
|
||||
- max: 1
|
||||
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:
|
||||
- error
|
||||
- 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/*
|
||||
/.gitlab_pages_secret
|
||||
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
|
||||
retry: 1
|
||||
|
@ -131,7 +131,7 @@ stages:
|
|||
.single-script-job: &single-script-job
|
||||
image: ruby:2.4-alpine
|
||||
before_script: []
|
||||
stage: build
|
||||
stage: test
|
||||
cache: {}
|
||||
dependencies: []
|
||||
variables: &single-script-job-variables
|
||||
|
@ -171,7 +171,7 @@ stages:
|
|||
- '[[ -f $FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_REPORT_PATH}'
|
||||
- '[[ -f $NEW_FLAKY_RSPEC_REPORT_PATH ]] || echo "{}" > ${NEW_FLAKY_RSPEC_REPORT_PATH}'
|
||||
- scripts/gitaly-test-spawn
|
||||
- knapsack rspec "--color --format documentation"
|
||||
- knapsack rspec "--color --format documentation --format RspecJunitFormatter --out junit_rspec.xml"
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
when: always
|
||||
|
@ -180,6 +180,8 @@ stages:
|
|||
- knapsack/
|
||||
- rspec_flaky/
|
||||
- tmp/capybara/
|
||||
reports:
|
||||
junit: junit_rspec.xml
|
||||
|
||||
.rspec-metadata-pg: &rspec-metadata-pg
|
||||
<<: *rspec-metadata
|
||||
|
@ -311,10 +313,13 @@ review-docs-cleanup:
|
|||
environment:
|
||||
name: review-docs/$CI_COMMIT_REF_SLUG
|
||||
action: stop
|
||||
when: manual
|
||||
script:
|
||||
- gem install gitlab --no-ri --no-rdoc
|
||||
- ./$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
|
||||
|
@ -322,7 +327,7 @@ review-docs-cleanup:
|
|||
cloud-native-image:
|
||||
image: ruby:2.4-alpine
|
||||
before_script: []
|
||||
stage: build
|
||||
stage: test
|
||||
allow_failure: true
|
||||
variables:
|
||||
GIT_DEPTH: "1"
|
||||
|
@ -703,7 +708,6 @@ gitlab:assets:compile:
|
|||
SETUP_DB: "false"
|
||||
SKIP_STORAGE_VALIDATION: "true"
|
||||
WEBPACK_REPORT: "true"
|
||||
NO_COMPRESSION: "true"
|
||||
# we override the max_old_space_size to prevent OOM errors
|
||||
NODE_OPTIONS: --max_old_space_size=3584
|
||||
script:
|
||||
|
@ -717,6 +721,7 @@ gitlab:assets:compile:
|
|||
expire_in: 31d
|
||||
paths:
|
||||
- webpack-report/
|
||||
- public/assets/
|
||||
|
||||
karma:
|
||||
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
|
||||
|
@ -739,7 +744,7 @@ karma:
|
|||
- chrome_debug.log
|
||||
- coverage-javascript/
|
||||
|
||||
codequality:
|
||||
code_quality:
|
||||
<<: *dedicated-no-docs-no-db-pull-cache-job
|
||||
image: docker:stable
|
||||
allow_failure: true
|
||||
|
@ -757,9 +762,13 @@ codequality:
|
|||
script:
|
||||
# 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/')
|
||||
- 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:
|
||||
paths: [codeclimate.json]
|
||||
paths: [gl-code-quality-report.json]
|
||||
expire_in: 1 week
|
||||
|
||||
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
|
||||
- [ ] 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 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
|
||||
- [ ] 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
|
||||
|
||||
|
|
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?
|
||||
|
||||
|
@ -10,20 +14,19 @@
|
|||
|
||||
Closes
|
||||
|
||||
## Moving docs to a new location?
|
||||
## Author's checklist
|
||||
|
||||
Read the guidelines:
|
||||
https://docs.gitlab.com/ee/development/documentation/#changing-document-location
|
||||
- [ ] [Apply the correct labels and milestone](https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process)
|
||||
- [ ] 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
|
||||
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 the old docs in the 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/ee/development/documentation/index.html#redirections-for-pages-with-disqus-comments)
|
||||
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.
|
||||
## Review checklist
|
||||
|
||||
- [ ] Your team's review (required)
|
||||
- [ ] PM's review (recommended, but not a blocker)
|
||||
- [ ] Technical writer's review (required)
|
||||
- [ ] Merge the EE-MR first, CE-MR afterwards
|
||||
|
||||
/label ~Documentation
|
||||
|
|
|
@ -70,14 +70,15 @@ linters:
|
|||
enabled: false
|
||||
|
||||
RuboCop:
|
||||
enabled: false
|
||||
enabled: true
|
||||
# These cops are incredibly noisy when it comes to HAML templates, so we
|
||||
# ignore them.
|
||||
ignored_cops:
|
||||
- Lint/BlockAlignment
|
||||
- Lint/EndAlignment
|
||||
- Layout/BlockAlignment
|
||||
- Layout/EndAlignment
|
||||
- Lint/Void
|
||||
- Metrics/LineLength
|
||||
- Naming/FileName
|
||||
- Style/AlignParameters
|
||||
- Style/BlockNesting
|
||||
- Style/ElseAlignment
|
||||
|
@ -91,6 +92,51 @@ linters:
|
|||
- Style/TrailingWhitespace
|
||||
- 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:
|
||||
enabled: true
|
||||
|
||||
|
|
|
@ -31,6 +31,10 @@ Style/MutableConstant:
|
|||
- 'ee/db/post_migrate/**/*'
|
||||
- 'ee/db/geo/migrate/**/*'
|
||||
|
||||
# TODO: Move this to gitlab-styles
|
||||
Style/SafeNavigation:
|
||||
Enabled: false
|
||||
|
||||
Naming/FileName:
|
||||
ExpectMatchingDefinition: true
|
||||
Exclude:
|
||||
|
@ -44,6 +48,8 @@ Naming/FileName:
|
|||
- 'qa/bin/*'
|
||||
- 'config/**/*'
|
||||
- 'lib/generators/**/*'
|
||||
- 'locale/unfound_translations.rb'
|
||||
- 'ee/locale/unfound_translations.rb'
|
||||
- 'ee/lib/generators/**/*'
|
||||
IgnoreExecutableScripts: true
|
||||
AllowedAcronyms:
|
||||
|
|
|
@ -706,12 +706,6 @@ Style/RescueModifier:
|
|||
Style/RescueStandardError:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 92
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: ConvertCodeThatCanStartToReturnNil.
|
||||
Style/SafeNavigation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 8
|
||||
# Cop supports --auto-correct.
|
||||
Style/SelfAssignment:
|
||||
|
|
339
CHANGELOG.md
|
@ -2,38 +2,76 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
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)
|
||||
|
||||
- Monkey kubeclient to not follow any redirects.
|
||||
|
||||
|
||||
## 11.2.7 (2018-10-27)
|
||||
## 11.3.8 (2018-10-27)
|
||||
|
||||
- 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.
|
||||
- Redact personal tokens in unsubscribe links.
|
||||
- Persist only SHA digest of PersonalAccessToken#token.
|
||||
- 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)
|
||||
|
||||
- Filter user sensitive data from discussions JSON. !2538
|
||||
- Filter user sensitive data from discussions JSON. !2537
|
||||
- Properly filter private references from system notes.
|
||||
- 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)
|
||||
|
||||
|
@ -45,9 +83,258 @@ entry.
|
|||
- 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)
|
||||
|
||||
- No changes.
|
||||
### Fixed (1 change)
|
||||
|
||||
- Fixed cache invalidation issue with diff lines from 11.2.2.
|
||||
|
||||
## 11.2.2 (2018-08-27)
|
||||
|
||||
|
@ -310,6 +597,25 @@ entry.
|
|||
- 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)
|
||||
|
||||
### 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.
|
||||
|
||||
|
||||
## 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)
|
||||
|
||||
### 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 -->
|
||||
**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)
|
||||
- [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)
|
||||
- [Helping others](#helping-others)
|
||||
- [I want to contribute!](#i-want-to-contribute)
|
||||
- [Contribution Flow](#contribution-flow)
|
||||
- [Workflow labels](#workflow-labels)
|
||||
- [Type labels](#type-labels)
|
||||
- [Subject labels](#subject-labels)
|
||||
- [Team labels](#team-labels)
|
||||
- [Release Scoping labels](#release-scoping-labels)
|
||||
- [Bug Priority labels](#bug-priority-labels)
|
||||
- [Bug Severity labels](#bug-severity-labels)
|
||||
- [Severity impact guidance](#severity-impact-guidance)
|
||||
- [Label for community contributors](#label-for-community-contributors)
|
||||
- [Implement design & UI elements](#implement-design-ui-elements)
|
||||
- [Type labels](#type-labels)
|
||||
- [Subject labels](#subject-labels)
|
||||
- [Team labels](#team-labels)
|
||||
- [Release Scoping labels](#release-scoping-labels)
|
||||
- [Priority labels](#priority-labels)
|
||||
- [Severity labels](#severity-labels)
|
||||
- [Severity impact guidance](#severity-impact-guidance)
|
||||
- [Label for community contributors](#label-for-community-contributors)
|
||||
- [Implement design & UI elements](#implement-design--ui-elements)
|
||||
- [Issue tracker](#issue-tracker)
|
||||
- [Issue triaging](#issue-triaging)
|
||||
- [Feature proposals](#feature-proposals)
|
||||
- [Issue tracker guidelines](#issue-tracker-guidelines)
|
||||
- [Issue weight](#issue-weight)
|
||||
- [Regression issues](#regression-issues)
|
||||
- [Technical and UX debt](#technical-and-ux-debt)
|
||||
- [Stewardship](#stewardship)
|
||||
- [Issue triaging](#issue-triaging)
|
||||
- [Feature proposals](#feature-proposals)
|
||||
- [Issue tracker guidelines](#issue-tracker-guidelines)
|
||||
- [Issue weight](#issue-weight)
|
||||
- [Regression issues](#regression-issues)
|
||||
- [Technical and UX debt](#technical-and-ux-debt)
|
||||
- [Stewardship](#stewardship)
|
||||
- [Merge requests](#merge-requests)
|
||||
- [Merge request guidelines](#merge-request-guidelines)
|
||||
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
|
||||
- [Merge request guidelines](#merge-request-guidelines)
|
||||
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
|
||||
- [Definition of done](#definition-of-done)
|
||||
- [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 -->
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
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
|
||||
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).
|
||||
|
||||
|
@ -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
|
||||
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).
|
||||
|
||||
- [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
|
||||
|
||||
|
@ -84,6 +90,36 @@ Please report suspected security vulnerabilities in private to
|
|||
Please do **NOT** create publicly viewable issues for suspected security
|
||||
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
|
||||
|
||||
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
|
||||
[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
|
||||
|
||||
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
|
||||
[testing]: doc/development/testing_guide/index.md
|
||||
[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/gemfile')
|
||||
danger.import_dangerfile(path: 'danger/database')
|
||||
danger.import_dangerfile(path: 'danger/documentation')
|
||||
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'
|
||||
|
||||
# Browser detection
|
||||
gem 'browser', '~> 2.2'
|
||||
gem 'browser', '~> 2.5'
|
||||
|
||||
# GPG
|
||||
gem 'gpgme'
|
||||
|
@ -107,7 +107,9 @@ gem 'kaminari', '~> 1.0'
|
|||
gem 'hamlit', '~> 2.8.8'
|
||||
|
||||
# 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'
|
||||
|
||||
# Drag and Drop UI
|
||||
|
@ -116,14 +118,14 @@ gem 'dropzonejs-rails', '~> 0.7.1'
|
|||
# for backups
|
||||
gem 'fog-aws', '~> 2.0.1'
|
||||
gem 'fog-core', '~> 1.44'
|
||||
gem 'fog-google', '~> 1.3.3'
|
||||
gem 'fog-google', '~> 1.7.1'
|
||||
gem 'fog-local', '~> 0.3'
|
||||
gem 'fog-openstack', '~> 0.1'
|
||||
gem 'fog-rackspace', '~> 0.1.1'
|
||||
gem 'fog-aliyun', '~> 0.2.0'
|
||||
|
||||
# for Google storage
|
||||
gem 'google-api-client', '~> 0.19.8'
|
||||
gem 'google-api-client', '~> 0.23'
|
||||
|
||||
# for aws storage
|
||||
gem 'unf', '~> 0.1.4'
|
||||
|
@ -180,7 +182,7 @@ gem 'rufus-scheduler', '~> 3.4'
|
|||
gem 'httparty', '~> 0.13.3'
|
||||
|
||||
# Colored output to console
|
||||
gem 'rainbow', '~> 2.2'
|
||||
gem 'rainbow', '~> 3.0'
|
||||
|
||||
# Progress bar
|
||||
gem 'ruby-progressbar'
|
||||
|
@ -195,6 +197,9 @@ gem 're2', '~> 1.1.1'
|
|||
|
||||
gem 'version_sorter', '~> 2.1.0'
|
||||
|
||||
# Export Ruby Regex to Javascript
|
||||
gem 'js_regex', '~> 2.2.1'
|
||||
|
||||
# User agent parsing
|
||||
gem 'device_detector'
|
||||
|
||||
|
@ -214,9 +219,6 @@ gem 'jira-ruby', '~> 1.4'
|
|||
# Flowdock integration
|
||||
gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
|
||||
|
||||
# Gemnasium integration
|
||||
gem 'gemnasium-gitlab-service', '~> 0.2'
|
||||
|
||||
# Slack integration
|
||||
gem 'slack-notifier', '~> 1.5.1'
|
||||
|
||||
|
@ -363,12 +365,11 @@ group :development, :test do
|
|||
gem 'scss_lint', '~> 0.56.0', require: false
|
||||
gem 'haml_lint', '~> 0.26.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 '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 'activerecord_sane_schema_dumper', gem_versions['activerecord_sane_schema_dumper']
|
||||
|
@ -390,6 +391,7 @@ group :test do
|
|||
gem 'sham_rack', '~> 1.3.6'
|
||||
gem 'concurrent-ruby', '~> 1.0.5'
|
||||
gem 'test-prof', '~> 0.2.5'
|
||||
gem 'rspec_junit_formatter'
|
||||
end
|
||||
|
||||
gem 'octokit', '~> 4.9'
|
||||
|
@ -423,7 +425,7 @@ group :ed25519 do
|
|||
end
|
||||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.113.0', require: 'gitaly'
|
||||
gem 'gitaly-proto', '~> 0.117.0', require: 'gitaly'
|
||||
gem 'grpc', '~> 1.11.0'
|
||||
|
||||
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
||||
|
|
84
Gemfile.lock
|
@ -86,12 +86,11 @@ GEM
|
|||
bindata (2.4.3)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
blankslate (2.1.2.4)
|
||||
bootsnap (1.3.1)
|
||||
msgpack (~> 1.0)
|
||||
bootstrap_form (2.7.0)
|
||||
brakeman (4.2.1)
|
||||
browser (2.2.0)
|
||||
browser (2.5.3)
|
||||
builder (3.2.3)
|
||||
bullet (5.5.1)
|
||||
activesupport (>= 3.0.0)
|
||||
|
@ -123,10 +122,10 @@ GEM
|
|||
numerizer (~> 0.1.1)
|
||||
chunky_png (1.3.5)
|
||||
citrus (3.0.2)
|
||||
coderay (1.1.1)
|
||||
coderay (1.1.2)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
commonmarker (0.17.8)
|
||||
commonmarker (0.17.13)
|
||||
ruby-enum (~> 0.5)
|
||||
concord (0.1.5)
|
||||
adamantium (~> 0.2.0)
|
||||
|
@ -209,12 +208,7 @@ GEM
|
|||
fast_blank (1.0.0)
|
||||
fast_gettext (1.6.0)
|
||||
ffaker (2.4.0)
|
||||
ffi (1.9.18)
|
||||
flay (2.10.0)
|
||||
erubis (~> 2.7.0)
|
||||
path_expander (~> 1.0)
|
||||
ruby_parser (~> 3.0)
|
||||
sexp_processor (~> 4.0)
|
||||
ffi (1.9.25)
|
||||
flipper (0.13.0)
|
||||
flipper-active_record (0.13.0)
|
||||
activerecord (>= 3.2, < 6)
|
||||
|
@ -239,11 +233,11 @@ GEM
|
|||
builder
|
||||
excon (~> 0.58)
|
||||
formatador (~> 0.2)
|
||||
fog-google (1.3.3)
|
||||
fog-google (1.7.1)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-xml
|
||||
google-api-client (~> 0.19.1)
|
||||
google-api-client (~> 0.23.0)
|
||||
fog-json (1.0.2)
|
||||
fog-core (~> 1.0)
|
||||
multi_json (~> 1.10)
|
||||
|
@ -269,8 +263,6 @@ GEM
|
|||
fuubar (2.2.0)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
gemnasium-gitlab-service (0.2.6)
|
||||
rugged (~> 0.21)
|
||||
gemojione (3.3.0)
|
||||
json
|
||||
get_process_mem (0.2.0)
|
||||
|
@ -284,7 +276,7 @@ GEM
|
|||
gettext_i18n_rails (>= 0.7.1)
|
||||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gitaly-proto (0.113.0)
|
||||
gitaly-proto (0.117.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.10)
|
||||
github-linguist (5.3.3)
|
||||
|
@ -331,7 +323,7 @@ GEM
|
|||
actionpack (>= 3.0)
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
google-api-client (0.19.8)
|
||||
google-api-client (0.23.4)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.7.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
|
@ -430,6 +422,8 @@ GEM
|
|||
multipart-post
|
||||
oauth (~> 0.5, >= 0.5.0)
|
||||
jquery-atwho-rails (1.3.2)
|
||||
js_regex (2.2.1)
|
||||
regexp_parser (>= 0.4.11, <= 0.5.0)
|
||||
json (1.8.6)
|
||||
json-jwt (1.9.4)
|
||||
activesupport
|
||||
|
@ -465,13 +459,12 @@ GEM
|
|||
actionmailer (>= 3.2)
|
||||
letter_opener (~> 1.0)
|
||||
railties (>= 3.2)
|
||||
license_finder (3.1.1)
|
||||
license_finder (5.4.0)
|
||||
bundler
|
||||
httparty
|
||||
rubyzip
|
||||
thor
|
||||
toml (= 0.1.2)
|
||||
with_env (> 1.0)
|
||||
toml (= 0.2.0)
|
||||
with_env (= 1.1.0)
|
||||
xml-simple
|
||||
licensee (8.9.2)
|
||||
rugged (~> 0.24)
|
||||
|
@ -494,7 +487,7 @@ GEM
|
|||
memoist (0.16.0)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
method_source (0.8.2)
|
||||
method_source (0.9.0)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
|
@ -589,9 +582,7 @@ GEM
|
|||
parallel (1.12.1)
|
||||
parser (2.5.1.0)
|
||||
ast (~> 2.4.0)
|
||||
parslet (1.5.0)
|
||||
blankslate (~> 2.0)
|
||||
path_expander (1.0.2)
|
||||
parslet (1.8.2)
|
||||
peek (1.0.1)
|
||||
concurrent-ruby (>= 0.9.0)
|
||||
concurrent-ruby-ext (>= 0.9.0)
|
||||
|
@ -636,12 +627,11 @@ GEM
|
|||
unparser
|
||||
procto (0.0.3)
|
||||
prometheus-client-mmap (0.9.4)
|
||||
pry (0.10.4)
|
||||
pry (0.11.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
pry-byebug (3.4.2)
|
||||
byebug (~> 9.0)
|
||||
method_source (~> 0.9.0)
|
||||
pry-byebug (3.4.3)
|
||||
byebug (>= 9.0, < 9.1)
|
||||
pry (~> 0.10)
|
||||
pry-rails (0.3.5)
|
||||
pry (>= 0.9.10)
|
||||
|
@ -692,8 +682,7 @@ GEM
|
|||
activesupport (= 4.2.10)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.18.0)
|
||||
rake (12.3.1)
|
||||
rb-fsevent (0.10.2)
|
||||
|
@ -730,6 +719,7 @@ GEM
|
|||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.4.1)
|
||||
redis (>= 2.2, < 5)
|
||||
regexp_parser (0.5.0)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
|
@ -742,7 +732,7 @@ GEM
|
|||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
retriable (3.1.1)
|
||||
retriable (3.1.2)
|
||||
rinku (2.0.0)
|
||||
rotp (2.1.2)
|
||||
rouge (3.2.1)
|
||||
|
@ -780,6 +770,9 @@ GEM
|
|||
rspec-core
|
||||
rspec-set (0.1.3)
|
||||
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)
|
||||
activerecord
|
||||
pg
|
||||
|
@ -808,7 +801,7 @@ GEM
|
|||
sexp_processor (~> 4.1)
|
||||
rubyntlm (0.6.2)
|
||||
rubypants (0.2.0)
|
||||
rubyzip (1.2.1)
|
||||
rubyzip (1.2.2)
|
||||
rufus-scheduler (3.4.0)
|
||||
et-orbi (~> 1.0)
|
||||
rugged (0.27.4)
|
||||
|
@ -872,7 +865,6 @@ GEM
|
|||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
slack-notifier (1.5.1)
|
||||
slop (3.6.0)
|
||||
spring (2.0.1)
|
||||
activesupport (>= 4.2)
|
||||
spring-commands-rspec (1.0.4)
|
||||
|
@ -912,8 +904,8 @@ GEM
|
|||
tilt (2.0.8)
|
||||
timecop (0.8.1)
|
||||
timfel-krb5-auth (0.8.3)
|
||||
toml (0.1.2)
|
||||
parslet (~> 1.5.0)
|
||||
toml (0.2.0)
|
||||
parslet (~> 1.8.0)
|
||||
toml-rb (1.0.0)
|
||||
citrus (~> 3.0, > 3.0)
|
||||
trollop (2.1.3)
|
||||
|
@ -999,12 +991,12 @@ DEPENDENCIES
|
|||
bootsnap (~> 1.3)
|
||||
bootstrap_form (~> 2.7.0)
|
||||
brakeman (~> 4.2)
|
||||
browser (~> 2.2)
|
||||
browser (~> 2.5)
|
||||
bullet (~> 5.5.0)
|
||||
bundler-audit (~> 0.5.0)
|
||||
capybara (~> 2.15)
|
||||
capybara-screenshot (~> 1.0.0)
|
||||
carrierwave (~> 1.2)
|
||||
carrierwave (= 1.2.3)
|
||||
charlock_holmes (~> 0.7.5)
|
||||
chronic (~> 0.10.2)
|
||||
chronic_duration (~> 0.10.6)
|
||||
|
@ -1029,26 +1021,24 @@ DEPENDENCIES
|
|||
faraday (~> 0.12)
|
||||
fast_blank
|
||||
ffaker (~> 2.4)
|
||||
flay (~> 2.10.0)
|
||||
flipper (~> 0.13.0)
|
||||
flipper-active_record (~> 0.13.0)
|
||||
flipper-active_support_cache_store (~> 0.13.0)
|
||||
fog-aliyun (~> 0.2.0)
|
||||
fog-aws (~> 2.0.1)
|
||||
fog-core (~> 1.44)
|
||||
fog-google (~> 1.3.3)
|
||||
fog-google (~> 1.7.1)
|
||||
fog-local (~> 0.3)
|
||||
fog-openstack (~> 0.1)
|
||||
fog-rackspace (~> 0.1.1)
|
||||
font-awesome-rails (~> 4.7)
|
||||
foreman (~> 0.84.0)
|
||||
fuubar (~> 2.2.0)
|
||||
gemnasium-gitlab-service (~> 0.2)
|
||||
gemojione (~> 3.3)
|
||||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly-proto (~> 0.113.0)
|
||||
gitaly-proto (~> 0.117.0)
|
||||
github-linguist (~> 5.3.3)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-gollum-lib (~> 4.2)
|
||||
|
@ -1057,7 +1047,7 @@ DEPENDENCIES
|
|||
gitlab-styles (~> 2.4)
|
||||
gitlab_omniauth-ldap (~> 2.0.4)
|
||||
gon (~> 6.2)
|
||||
google-api-client (~> 0.19.8)
|
||||
google-api-client (~> 0.23)
|
||||
google-protobuf (= 3.5.1)
|
||||
gpgme
|
||||
grape (~> 1.0)
|
||||
|
@ -1080,13 +1070,14 @@ DEPENDENCIES
|
|||
influxdb (~> 0.2)
|
||||
jira-ruby (~> 1.4)
|
||||
jquery-atwho-rails (~> 1.3.2)
|
||||
js_regex (~> 2.2.1)
|
||||
json-schema (~> 2.8.0)
|
||||
jwt (~> 1.5.6)
|
||||
kaminari (~> 1.0)
|
||||
knapsack (~> 1.16)
|
||||
kubeclient (~> 3.1.0)
|
||||
letter_opener_web (~> 1.3.0)
|
||||
license_finder (~> 3.1)
|
||||
license_finder (~> 5.4)
|
||||
licensee (~> 8.9)
|
||||
lograge (~> 0.5)
|
||||
loofah (~> 2.2)
|
||||
|
@ -1136,7 +1127,7 @@ DEPENDENCIES
|
|||
rails (= 4.2.10)
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rails-i18n (~> 4.0.9)
|
||||
rainbow (~> 2.2)
|
||||
rainbow (~> 3.0)
|
||||
raindrops (~> 0.18)
|
||||
rblineprof (~> 0.3.6)
|
||||
rbtrace (~> 0.4)
|
||||
|
@ -1155,6 +1146,7 @@ DEPENDENCIES
|
|||
rspec-rails (~> 3.7.0)
|
||||
rspec-retry (~> 0.4.5)
|
||||
rspec-set (~> 0.1.3)
|
||||
rspec_junit_formatter
|
||||
rspec_profiling (~> 0.0.5)
|
||||
rubocop (~> 0.54.0)
|
||||
rubocop-rspec (~> 1.22.1)
|
||||
|
@ -1207,4 +1199,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.2
|
||||
1.16.4
|
||||
|
|
|
@ -89,12 +89,11 @@ GEM
|
|||
bindata (2.4.3)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
blankslate (2.1.2.4)
|
||||
bootsnap (1.3.1)
|
||||
msgpack (~> 1.0)
|
||||
bootstrap_form (2.7.0)
|
||||
brakeman (4.2.1)
|
||||
browser (2.2.0)
|
||||
browser (2.5.3)
|
||||
builder (3.2.3)
|
||||
bullet (5.5.1)
|
||||
activesupport (>= 3.0.0)
|
||||
|
@ -126,7 +125,7 @@ GEM
|
|||
numerizer (~> 0.1.1)
|
||||
chunky_png (1.3.5)
|
||||
citrus (3.0.2)
|
||||
coderay (1.1.1)
|
||||
coderay (1.1.2)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
commonmarker (0.17.8)
|
||||
|
@ -212,12 +211,7 @@ GEM
|
|||
fast_blank (1.0.0)
|
||||
fast_gettext (1.6.0)
|
||||
ffaker (2.4.0)
|
||||
ffi (1.9.18)
|
||||
flay (2.10.0)
|
||||
erubis (~> 2.7.0)
|
||||
path_expander (~> 1.0)
|
||||
ruby_parser (~> 3.0)
|
||||
sexp_processor (~> 4.0)
|
||||
ffi (1.9.25)
|
||||
flipper (0.13.0)
|
||||
flipper-active_record (0.13.0)
|
||||
activerecord (>= 3.2, < 6)
|
||||
|
@ -242,11 +236,11 @@ GEM
|
|||
builder
|
||||
excon (~> 0.58)
|
||||
formatador (~> 0.2)
|
||||
fog-google (1.3.3)
|
||||
fog-google (1.7.1)
|
||||
fog-core
|
||||
fog-json
|
||||
fog-xml
|
||||
google-api-client (~> 0.19.1)
|
||||
google-api-client (~> 0.23.0)
|
||||
fog-json (1.0.2)
|
||||
fog-core (~> 1.0)
|
||||
multi_json (~> 1.10)
|
||||
|
@ -272,8 +266,6 @@ GEM
|
|||
fuubar (2.2.0)
|
||||
rspec-core (~> 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
gemnasium-gitlab-service (0.2.6)
|
||||
rugged (~> 0.21)
|
||||
gemojione (3.3.0)
|
||||
json
|
||||
get_process_mem (0.2.0)
|
||||
|
@ -287,7 +279,7 @@ GEM
|
|||
gettext_i18n_rails (>= 0.7.1)
|
||||
po_to_json (>= 1.0.0)
|
||||
rails (>= 3.2.0)
|
||||
gitaly-proto (0.113.0)
|
||||
gitaly-proto (0.117.0)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.10)
|
||||
github-linguist (5.3.3)
|
||||
|
@ -334,7 +326,7 @@ GEM
|
|||
actionpack (>= 3.0)
|
||||
multi_json
|
||||
request_store (>= 1.0)
|
||||
google-api-client (0.19.8)
|
||||
google-api-client (0.23.4)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.5, < 0.7.0)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
|
@ -433,6 +425,8 @@ GEM
|
|||
multipart-post
|
||||
oauth (~> 0.5, >= 0.5.0)
|
||||
jquery-atwho-rails (1.3.2)
|
||||
js_regex (2.2.1)
|
||||
regexp_parser (>= 0.4.11, <= 0.5.0)
|
||||
json (1.8.6)
|
||||
json-jwt (1.9.4)
|
||||
activesupport
|
||||
|
@ -468,13 +462,12 @@ GEM
|
|||
actionmailer (>= 3.2)
|
||||
letter_opener (~> 1.0)
|
||||
railties (>= 3.2)
|
||||
license_finder (3.1.1)
|
||||
license_finder (5.4.0)
|
||||
bundler
|
||||
httparty
|
||||
rubyzip
|
||||
thor
|
||||
toml (= 0.1.2)
|
||||
with_env (> 1.0)
|
||||
toml (= 0.2.0)
|
||||
with_env (= 1.1.0)
|
||||
xml-simple
|
||||
licensee (8.9.2)
|
||||
rugged (~> 0.24)
|
||||
|
@ -497,7 +490,7 @@ GEM
|
|||
memoist (0.16.0)
|
||||
memoizable (0.4.2)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
method_source (0.8.2)
|
||||
method_source (0.9.0)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
|
@ -593,9 +586,7 @@ GEM
|
|||
parallel (1.12.1)
|
||||
parser (2.5.1.0)
|
||||
ast (~> 2.4.0)
|
||||
parslet (1.5.0)
|
||||
blankslate (~> 2.0)
|
||||
path_expander (1.0.2)
|
||||
parslet (1.8.2)
|
||||
peek (1.0.1)
|
||||
concurrent-ruby (>= 0.9.0)
|
||||
concurrent-ruby-ext (>= 0.9.0)
|
||||
|
@ -640,12 +631,11 @@ GEM
|
|||
unparser
|
||||
procto (0.0.3)
|
||||
prometheus-client-mmap (0.9.4)
|
||||
pry (0.10.4)
|
||||
pry (0.11.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
pry-byebug (3.4.2)
|
||||
byebug (~> 9.0)
|
||||
method_source (~> 0.9.0)
|
||||
pry-byebug (3.4.3)
|
||||
byebug (>= 9.0, < 9.1)
|
||||
pry (~> 0.10)
|
||||
pry-rails (0.3.5)
|
||||
pry (>= 0.9.10)
|
||||
|
@ -701,8 +691,7 @@ GEM
|
|||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.18.0)
|
||||
rake (12.3.1)
|
||||
rb-fsevent (0.10.2)
|
||||
|
@ -739,6 +728,7 @@ GEM
|
|||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.4.1)
|
||||
redis (>= 2.2, < 5)
|
||||
regexp_parser (0.5.0)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
|
@ -751,10 +741,10 @@ GEM
|
|||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 4.0)
|
||||
netrc (~> 0.8)
|
||||
retriable (3.1.1)
|
||||
retriable (3.1.2)
|
||||
rinku (2.0.0)
|
||||
rotp (2.1.2)
|
||||
rouge (3.2.0)
|
||||
rouge (3.2.1)
|
||||
rqrcode (0.7.0)
|
||||
chunky_png
|
||||
rqrcode-rails3 (0.1.7)
|
||||
|
@ -789,6 +779,8 @@ GEM
|
|||
rspec-core
|
||||
rspec-set (0.1.3)
|
||||
rspec-support (3.7.1)
|
||||
rspec_junit_formatter (0.4.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rspec_profiling (0.0.5)
|
||||
activerecord
|
||||
pg
|
||||
|
@ -817,10 +809,10 @@ GEM
|
|||
sexp_processor (~> 4.1)
|
||||
rubyntlm (0.6.2)
|
||||
rubypants (0.2.0)
|
||||
rubyzip (1.2.1)
|
||||
rubyzip (1.2.2)
|
||||
rufus-scheduler (3.4.0)
|
||||
et-orbi (~> 1.0)
|
||||
rugged (0.27.2)
|
||||
rugged (0.27.4)
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (4.6.6)
|
||||
crass (~> 1.0.2)
|
||||
|
@ -881,7 +873,6 @@ GEM
|
|||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
slack-notifier (1.5.1)
|
||||
slop (3.6.0)
|
||||
spring (2.0.1)
|
||||
activesupport (>= 4.2)
|
||||
spring-commands-rspec (1.0.4)
|
||||
|
@ -919,8 +910,8 @@ GEM
|
|||
tilt (2.0.8)
|
||||
timecop (0.8.1)
|
||||
timfel-krb5-auth (0.8.3)
|
||||
toml (0.1.2)
|
||||
parslet (~> 1.5.0)
|
||||
toml (0.2.0)
|
||||
parslet (~> 1.8.0)
|
||||
toml-rb (1.0.0)
|
||||
citrus (~> 3.0, > 3.0)
|
||||
trollop (2.1.3)
|
||||
|
@ -1009,12 +1000,12 @@ DEPENDENCIES
|
|||
bootsnap (~> 1.3)
|
||||
bootstrap_form (~> 2.7.0)
|
||||
brakeman (~> 4.2)
|
||||
browser (~> 2.2)
|
||||
browser (~> 2.5)
|
||||
bullet (~> 5.5.0)
|
||||
bundler-audit (~> 0.5.0)
|
||||
capybara (~> 2.15)
|
||||
capybara-screenshot (~> 1.0.0)
|
||||
carrierwave (~> 1.2)
|
||||
carrierwave (= 1.2.3)
|
||||
charlock_holmes (~> 0.7.5)
|
||||
chronic (~> 0.10.2)
|
||||
chronic_duration (~> 0.10.6)
|
||||
|
@ -1039,26 +1030,24 @@ DEPENDENCIES
|
|||
faraday (~> 0.12)
|
||||
fast_blank
|
||||
ffaker (~> 2.4)
|
||||
flay (~> 2.10.0)
|
||||
flipper (~> 0.13.0)
|
||||
flipper-active_record (~> 0.13.0)
|
||||
flipper-active_support_cache_store (~> 0.13.0)
|
||||
fog-aliyun (~> 0.2.0)
|
||||
fog-aws (~> 2.0.1)
|
||||
fog-core (~> 1.44)
|
||||
fog-google (~> 1.3.3)
|
||||
fog-google (~> 1.7.1)
|
||||
fog-local (~> 0.3)
|
||||
fog-openstack (~> 0.1)
|
||||
fog-rackspace (~> 0.1.1)
|
||||
font-awesome-rails (~> 4.7)
|
||||
foreman (~> 0.84.0)
|
||||
fuubar (~> 2.2.0)
|
||||
gemnasium-gitlab-service (~> 0.2)
|
||||
gemojione (~> 3.3)
|
||||
gettext (~> 3.2.2)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly-proto (~> 0.113.0)
|
||||
gitaly-proto (~> 0.117.0)
|
||||
github-linguist (~> 5.3.3)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-gollum-lib (~> 4.2)
|
||||
|
@ -1067,7 +1056,7 @@ DEPENDENCIES
|
|||
gitlab-styles (~> 2.4)
|
||||
gitlab_omniauth-ldap (~> 2.0.4)
|
||||
gon (~> 6.2)
|
||||
google-api-client (~> 0.19.8)
|
||||
google-api-client (~> 0.23)
|
||||
google-protobuf (= 3.5.1)
|
||||
gpgme
|
||||
grape (~> 1.0)
|
||||
|
@ -1090,13 +1079,14 @@ DEPENDENCIES
|
|||
influxdb (~> 0.2)
|
||||
jira-ruby (~> 1.4)
|
||||
jquery-atwho-rails (~> 1.3.2)
|
||||
js_regex (~> 2.2.1)
|
||||
json-schema (~> 2.8.0)
|
||||
jwt (~> 1.5.6)
|
||||
kaminari (~> 1.0)
|
||||
knapsack (~> 1.16)
|
||||
kubeclient (~> 3.1.0)
|
||||
letter_opener_web (~> 1.3.0)
|
||||
license_finder (~> 3.1)
|
||||
license_finder (~> 5.4)
|
||||
licensee (~> 8.9)
|
||||
lograge (~> 0.5)
|
||||
loofah (~> 2.2)
|
||||
|
@ -1147,7 +1137,7 @@ DEPENDENCIES
|
|||
rails-controller-testing
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
rails-i18n (~> 5.1)
|
||||
rainbow (~> 2.2)
|
||||
rainbow (~> 3.0)
|
||||
raindrops (~> 0.18)
|
||||
rblineprof (~> 0.3.6)
|
||||
rbtrace (~> 0.4)
|
||||
|
@ -1166,6 +1156,7 @@ DEPENDENCIES
|
|||
rspec-rails (~> 3.7.0)
|
||||
rspec-retry (~> 0.4.5)
|
||||
rspec-set (~> 0.1.3)
|
||||
rspec_junit_formatter
|
||||
rspec_profiling (~> 0.0.5)
|
||||
rubocop (~> 0.54.0)
|
||||
rubocop-rspec (~> 1.22.1)
|
||||
|
@ -1217,4 +1208,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
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)
|
||||
- [On the 7th](#on-the-7th)
|
||||
- [After the 7th](#after-the-7th)
|
||||
- [Regressions](#regressions)
|
||||
- [How to manage a regression](#how-to-manage-a-regression)
|
||||
- [Bugs](#bugs)
|
||||
- [Regressions](#regressions)
|
||||
- [Managing bugs](#managing-bugs)
|
||||
- [Release retrospective and kickoff](#release-retrospective-and-kickoff)
|
||||
- [Retrospective](#retrospective)
|
||||
- [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
|
||||
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?
|
||||
|
||||
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
|
||||
|
||||
Merge requests should still be complete, following the
|
||||
[definition of done][done]. The single exception is documentation, and this can
|
||||
only be left until after the freeze if:
|
||||
Merge requests should still be complete, following the [definition of done][done].
|
||||
|
||||
* There is a follow-up issue to add documentation.
|
||||
* 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.
|
||||
#### Feature merge requests
|
||||
|
||||
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,
|
||||
|
@ -163,15 +164,32 @@ information, see
|
|||
[Automatic CE->EE merge][automatic_ce_ee_merge] and
|
||||
[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
|
||||
|
||||
Once the stable branch is frozen, the only MRs that can be cherry-picked into
|
||||
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 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)
|
||||
|
||||
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.
|
||||
|
||||
## Regressions
|
||||
## Bugs
|
||||
|
||||
A regression for a particular monthly release is a bug that exists in that
|
||||
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!
|
||||
A ~bug is a defect, error, failure which causes the system to behave incorrectly or prevents it from fulfilling the product requirements.
|
||||
|
||||
For instance, if 10.5.0 adds a feature, and that feature doesn't work correctly,
|
||||
then this is a regression in 10.5. If 10.5.1 then fixes that, but 10.5.3 somehow
|
||||
reintroduces the bug, then this bug is still a regression in 10.5.
|
||||
The level of impact of a ~bug can vary from blocking a whole functionality
|
||||
or a feature usability bug. A bug should always be linked to a severity level.
|
||||
Refer to our [severity levels](../CONTRIBUTING.md#severity-labels)
|
||||
|
||||
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 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.
|
||||
Whether the bug is also a regression or not, the triage process should start as soon as possible.
|
||||
Ensure that the Engineering Manager and/or the Product Manager for the relative area is involved to prioritize the work as needed.
|
||||
|
||||
### How to manage a regression
|
||||
### Regressions
|
||||
|
||||
Regressions are very important, and they should be considered high priority
|
||||
issues that should be solved as soon as possible, especially if they affect
|
||||
users. Despite that, ~regression label itself does not imply when the issue
|
||||
will be scheduled.
|
||||
A ~regression implies that a previously **verified working functionality** no longer works.
|
||||
Regressions are a subset of bugs. We use the ~regression label to imply that the defect caused the functionality to regress.
|
||||
The label tells us that something worked before and it needs extra attention from Engineering and Product Managers to schedule/reschedule.
|
||||
|
||||
When a regression 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
|
||||
The regression label does not apply to ~bugs for new features for which functionality was **never verified as working**.
|
||||
These, by definition, are not regressions.
|
||||
|
||||
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),
|
||||
the [subject label](../CONTRIBUTING.md#subject-labels)
|
||||
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 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).
|
||||
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 regression was introduced in an RC of the current release, label with ~Deliverable
|
||||
1. If the regression was introduced in the previous release, label with ~"Next Patch Release"
|
||||
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.
|
||||
|
||||
When a new issue is found, the fix should start as soon as possible. You can
|
||||
ping the Engineering Manager or the Product Manager for the relative area to
|
||||
make them aware of the issue earlier. They will analyze the priority and change
|
||||
it if needed.
|
||||
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).
|
||||
The counterpart Product Manager is included to weigh-in on prioritization as needed.
|
||||
1. If the ~bug is **NOT** a regression:
|
||||
1. The Engineering Manager decides which milestone the bug will be fixed. The appropriate milestone is applied.
|
||||
1. If the bug is a ~regression:
|
||||
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.
|
||||
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.
|
||||
1. This falls under regressions which worked in the last release and the current RCs. More detailed explanations in the **Prioritization** section above.
|
||||
1. If the affected version `xx.x` in `regression:xx.x` is older than the **current release**
|
||||
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
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ You can access a new installation with the login **`root`** and password **`5ive
|
|||
|
||||
## 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
|
||||
|
||||
|
@ -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 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
|
||||
|
||||
|
|
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',
|
||||
mergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions',
|
||||
groupLabelsPath: '/groups/:namespace_path/-/labels',
|
||||
templatesPath: '/api/:version/templates/:key',
|
||||
licensePath: '/api/:version/templates/licenses/:key',
|
||||
gitignorePath: '/api/:version/templates/gitignores/: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) {
|
||||
let urlRoot = '';
|
||||
if (gon.relative_url_root != null) {
|
||||
|
|
|
@ -109,8 +109,6 @@ export class AwardsHandler {
|
|||
}
|
||||
|
||||
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.is('.is-visible')) {
|
||||
$addBtn.removeClass('is-active');
|
||||
|
@ -134,9 +132,6 @@ export class AwardsHandler {
|
|||
}, 200);
|
||||
});
|
||||
}
|
||||
|
||||
$thumbsBtn.toggleClass('disabled', $userAuthored);
|
||||
$thumbsBtn.prop('disabled', $userAuthored);
|
||||
}
|
||||
|
||||
// Create the emoji menu with the first category of emojis.
|
||||
|
@ -364,10 +359,6 @@ export class AwardsHandler {
|
|||
return $emojiButton.hasClass('active');
|
||||
}
|
||||
|
||||
isUserAuthored($button) {
|
||||
return $button.hasClass('js-user-authored');
|
||||
}
|
||||
|
||||
decrementCounter($emojiButton, emoji) {
|
||||
const counter = $('.js-counter', $emojiButton);
|
||||
const counterNumber = parseInt(counter.text(), 10);
|
||||
|
@ -474,20 +465,16 @@ export class AwardsHandler {
|
|||
}
|
||||
|
||||
postEmoji($emojiButton, awardUrl, emoji, callback) {
|
||||
if (this.isUserAuthored($emojiButton)) {
|
||||
this.userAuthored($emojiButton);
|
||||
} else {
|
||||
axios
|
||||
.post(awardUrl, {
|
||||
name: emoji,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.ok) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(() => flash(__('Something went wrong on our end.')));
|
||||
}
|
||||
axios
|
||||
.post(awardUrl, {
|
||||
name: emoji,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.ok) {
|
||||
callback();
|
||||
}
|
||||
})
|
||||
.catch(() => flash(__('Something went wrong on our end.')));
|
||||
}
|
||||
|
||||
findEmojiIcon(votesBlock, emoji) {
|
||||
|
|
|
@ -23,6 +23,11 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
wasValidated: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'badgeInAddForm',
|
||||
|
@ -39,16 +44,6 @@ export default {
|
|||
|
||||
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() {
|
||||
const placeholders = ['project_path', 'project_id', 'default_branch', 'commit_sha']
|
||||
.map(placeholder => `<code>%{${placeholder}}</code>`)
|
||||
|
@ -93,11 +88,18 @@ export default {
|
|||
});
|
||||
},
|
||||
},
|
||||
submitButtonLabel() {
|
||||
if (this.isEditing) {
|
||||
return s__('Badges|Save changes');
|
||||
}
|
||||
return s__('Badges|Add badge');
|
||||
badgeImageUrlExample() {
|
||||
const exampleUrl =
|
||||
'https://example.gitlab.com/%{project_path}/badges/%{default_branch}/badge.svg';
|
||||
return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
|
||||
exampleUrl,
|
||||
});
|
||||
},
|
||||
badgeLinkUrlExample() {
|
||||
const exampleUrl = 'https://example.gitlab.com/%{project_path}';
|
||||
return sprintf(s__('Badges|e.g. %{exampleUrl}'), {
|
||||
exampleUrl,
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -109,7 +111,9 @@ export default {
|
|||
this.stopEditing();
|
||||
},
|
||||
onSubmit() {
|
||||
if (!this.canSubmit) {
|
||||
const form = this.$el;
|
||||
if (!form.checkValidity()) {
|
||||
this.wasValidated = true;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -117,6 +121,7 @@ export default {
|
|||
return this.saveBadge()
|
||||
.then(() => {
|
||||
createFlash(s__('Badges|The badge was saved.'), 'notice');
|
||||
this.wasValidated = false;
|
||||
})
|
||||
.catch(error => {
|
||||
createFlash(
|
||||
|
@ -129,6 +134,7 @@ export default {
|
|||
return this.addBadge()
|
||||
.then(() => {
|
||||
createFlash(s__('Badges|A new badge was added.'), 'notice');
|
||||
this.wasValidated = false;
|
||||
})
|
||||
.catch(error => {
|
||||
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>
|
||||
|
||||
<template>
|
||||
<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"
|
||||
>
|
||||
<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
|
||||
id="badge-link-url"
|
||||
v-model="linkUrl"
|
||||
:placeholder="$options.badgeLinkUrlPlaceholder"
|
||||
type="text"
|
||||
type="URL"
|
||||
class="form-control"
|
||||
required
|
||||
@input="debouncedPreview"
|
||||
/>
|
||||
<span
|
||||
class="form-text text-muted"
|
||||
v-html="helpText"
|
||||
></span>
|
||||
<div class="invalid-feedback">
|
||||
{{ s__('Badges|Please fill in a valid URL') }}
|
||||
</div>
|
||||
<span class="form-text text-muted">
|
||||
{{ badgeLinkUrlExample }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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
|
||||
id="badge-image-url"
|
||||
v-model="imageUrl"
|
||||
:placeholder="$options.badgeImageUrlPlaceholder"
|
||||
type="text"
|
||||
type="URL"
|
||||
class="form-control"
|
||||
required
|
||||
@input="debouncedPreview"
|
||||
/>
|
||||
<span
|
||||
class="form-text text-muted"
|
||||
v-html="helpText"
|
||||
></span>
|
||||
<div class="invalid-feedback">
|
||||
{{ s__('Badges|Please fill in a valid URL') }}
|
||||
</div>
|
||||
<span class="form-text text-muted">
|
||||
{{ badgeImageUrlExample }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
@ -200,20 +217,32 @@ export default {
|
|||
>{{ s__('Badges|No image to preview') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="row-content-block">
|
||||
<div
|
||||
v-if="isEditing"
|
||||
class="row-content-block"
|
||||
>
|
||||
<loading-button
|
||||
:disabled="!canSubmit"
|
||||
:loading="isSaving"
|
||||
:label="submitButtonLabel"
|
||||
:label="s__('Badges|Save changes')"
|
||||
type="submit"
|
||||
container-class="btn btn-success"
|
||||
/>
|
||||
<button
|
||||
v-if="isEditing"
|
||||
class="btn btn-cancel"
|
||||
type="button"
|
||||
@click="onCancel"
|
||||
>{{ __('Cancel') }}</button>
|
||||
</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>
|
||||
</template>
|
||||
|
|
|
@ -28,7 +28,7 @@ export default {
|
|||
{{ s__('Badges|Your badges') }}
|
||||
<span
|
||||
v-show="!isLoading"
|
||||
class="badge"
|
||||
class="badge badge-pill"
|
||||
>{{ badges.length }}</span>
|
||||
</div>
|
||||
<loading-icon
|
||||
|
|
|
@ -43,13 +43,13 @@ export default {
|
|||
<badge
|
||||
:image-url="badge.renderedImageUrl"
|
||||
: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>
|
||||
<div class="table-section section-10">
|
||||
<span class="badge">{{ badgeKindText }}</span>
|
||||
<span class="table-section section-30 str-truncated">{{ badge.linkUrl }}</span>
|
||||
<div class="table-section section-15">
|
||||
<span class="badge badge-pill">{{ badgeKindText }}</span>
|
||||
</div>
|
||||
<div class="table-section section-10 table-button-footer">
|
||||
<div class="table-section section-15 table-button-footer">
|
||||
<div
|
||||
v-if="canEditBadge"
|
||||
class="table-action-buttons">
|
||||
|
|
|
@ -203,7 +203,7 @@ export default {
|
|||
this.showIssueForm = !this.showIssueForm;
|
||||
},
|
||||
onScroll() {
|
||||
if (!this.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
|
||||
if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
|
||||
this.loadNextPage();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
import Flash from '../../flash';
|
||||
import { __ } from '../../locale';
|
||||
import { sprintf, __ } from '../../locale';
|
||||
import Sidebar from '../../right_sidebar';
|
||||
import eventHub from '../../sidebar/event_hub';
|
||||
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;
|
||||
},
|
||||
labelDropdownTitle() {
|
||||
return this.hasLabels ?
|
||||
`${this.issue.labels[0].title} ${this.issue.labels.length - 1}+ more` : 'Label';
|
||||
return this.hasLabels ? sprintf(__('%{firstLabel} +%{labelCount} more'), {
|
||||
firstLabel: this.issue.labels[0].title,
|
||||
labelCount: this.issue.labels.length - 1
|
||||
}) : __('Label');
|
||||
},
|
||||
selectedLabels() {
|
||||
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
/* 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 ModalHeader from './header.vue';
|
||||
import ModalList from './list.vue';
|
||||
|
@ -109,13 +109,11 @@
|
|||
loadIssues(clearIssues = false) {
|
||||
if (!this.showAddIssuesModal) return false;
|
||||
|
||||
return gl.boardService
|
||||
.getBacklog(
|
||||
queryData(this.filter.path, {
|
||||
page: this.page,
|
||||
per: this.perPage,
|
||||
}),
|
||||
)
|
||||
return gl.boardService.getBacklog({
|
||||
...urlParamsToObject(this.filter.path),
|
||||
page: this.page,
|
||||
per: this.perPage,
|
||||
})
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
if (clearIssues) {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len */
|
||||
/* global ListIssue */
|
||||
|
||||
import { __ } from '~/locale';
|
||||
import ListLabel from '~/vue_shared/models/label';
|
||||
import ListAssignee from '~/vue_shared/models/assignee';
|
||||
import queryData from '../utils/query_data';
|
||||
import { urlParamsToObject } from '~/lib/utils/common_utils';
|
||||
|
||||
const PER_PAGE = 20;
|
||||
|
||||
|
@ -30,7 +31,7 @@ class List {
|
|||
this.id = obj.id;
|
||||
this._uid = this.guid();
|
||||
this.position = obj.position;
|
||||
this.title = obj.title;
|
||||
this.title = obj.list_type === 'backlog' ? __('Open') : obj.title;
|
||||
this.type = obj.list_type;
|
||||
|
||||
const typeInfo = this.getTypeInfo(this.type);
|
||||
|
@ -114,7 +115,10 @@ class List {
|
|||
}
|
||||
|
||||
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) {
|
||||
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 Vue from 'vue';
|
||||
import initDismissableCallout from '~/dismissable_callout';
|
||||
import { s__, sprintf } from '../locale';
|
||||
import Flash from '../flash';
|
||||
import Poll from '../lib/utils/poll';
|
||||
import initSettingsPanels from '../settings_panels';
|
||||
import eventHub from './event_hub';
|
||||
import {
|
||||
APPLICATION_STATUS,
|
||||
REQUEST_LOADING,
|
||||
REQUEST_SUCCESS,
|
||||
REQUEST_FAILURE,
|
||||
} from './constants';
|
||||
import { APPLICATION_STATUS, REQUEST_LOADING, REQUEST_SUCCESS, REQUEST_FAILURE } from './constants';
|
||||
import ClustersService from './services/clusters_service';
|
||||
import ClustersStore from './stores/clusters_store';
|
||||
import applications from './components/applications.vue';
|
||||
|
@ -66,6 +62,7 @@ export default class Clusters {
|
|||
this.showTokenButton = document.querySelector('.js-show-cluster-token');
|
||||
this.tokenField = document.querySelector('.js-cluster-token');
|
||||
|
||||
initDismissableCallout('.js-cluster-security-warning');
|
||||
initSettingsPanels();
|
||||
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
|
||||
this.initApplications();
|
||||
|
@ -129,7 +126,8 @@ export default class Clusters {
|
|||
if (!Visibility.hidden()) {
|
||||
this.poll.makeRequest();
|
||||
} else {
|
||||
this.service.fetchData()
|
||||
this.service
|
||||
.fetchData()
|
||||
.then(data => this.handleSuccess(data))
|
||||
.catch(() => Clusters.handleError());
|
||||
}
|
||||
|
@ -177,15 +175,21 @@ export default class Clusters {
|
|||
|
||||
checkForNewInstalls(prevApplicationMap, newApplicationMap) {
|
||||
const appTitles = Object.keys(newApplicationMap)
|
||||
.filter(appId => newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
|
||||
prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
|
||||
prevApplicationMap[appId].status !== null)
|
||||
.filter(
|
||||
appId =>
|
||||
newApplicationMap[appId].status === APPLICATION_STATUS.INSTALLED &&
|
||||
prevApplicationMap[appId].status !== APPLICATION_STATUS.INSTALLED &&
|
||||
prevApplicationMap[appId].status !== null,
|
||||
)
|
||||
.map(appId => newApplicationMap[appId].title);
|
||||
|
||||
if (appTitles.length > 0) {
|
||||
const text = sprintf(s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'), {
|
||||
appList: appTitles.join(', '),
|
||||
});
|
||||
const text = sprintf(
|
||||
s__('ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster'),
|
||||
{
|
||||
appList: appTitles.join(', '),
|
||||
},
|
||||
);
|
||||
Flash(text, 'notice', this.successApplicationContainer);
|
||||
}
|
||||
}
|
||||
|
@ -218,13 +222,18 @@ export default class Clusters {
|
|||
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_LOADING);
|
||||
this.store.updateAppProperty(appId, 'requestReason', null);
|
||||
|
||||
this.service.installApplication(appId, data.params)
|
||||
this.service
|
||||
.installApplication(appId, data.params)
|
||||
.then(() => {
|
||||
this.store.updateAppProperty(appId, 'requestStatus', REQUEST_SUCCESS);
|
||||
})
|
||||
.catch(() => {
|
||||
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 { __ } from '~/locale';
|
||||
import setupToggleButtons from '~/toggle_buttons';
|
||||
import gcpSignupOffer from '~/clusters/components/gcp_signup_offer';
|
||||
import initDismissableCallout from '~/dismissable_callout';
|
||||
|
||||
import ClustersService from './services/clusters_service';
|
||||
|
||||
export default () => {
|
||||
const clusterList = document.querySelector('.js-clusters-list');
|
||||
|
||||
gcpSignupOffer();
|
||||
initDismissableCallout('.gcp-signup-offer');
|
||||
|
||||
// The empty state won't have a clusterList
|
||||
if (clusterList) {
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
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-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 ResolveService */
|
||||
|
||||
|
@ -25,44 +25,44 @@ const ResolveDiscussionBtn = Vue.extend({
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
showButton: function () {
|
||||
showButton: function() {
|
||||
if (this.discussion) {
|
||||
return this.discussion.isResolvable();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
isDiscussionResolved: function () {
|
||||
isDiscussionResolved: function() {
|
||||
if (this.discussion) {
|
||||
return this.discussion.isResolved();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
buttonText: function () {
|
||||
buttonText: function() {
|
||||
if (this.isDiscussionResolved) {
|
||||
return "Unresolve discussion";
|
||||
return 'Unresolve discussion';
|
||||
} else {
|
||||
return "Resolve discussion";
|
||||
return 'Resolve discussion';
|
||||
}
|
||||
},
|
||||
loading: function () {
|
||||
loading: function() {
|
||||
if (this.discussion) {
|
||||
return this.discussion.loading;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
created: function() {
|
||||
CommentsStore.createDiscussion(this.discussionId, this.canResolve);
|
||||
|
||||
this.discussion = CommentsStore.state[this.discussionId];
|
||||
},
|
||||
methods: {
|
||||
resolve: function () {
|
||||
resolve: function() {
|
||||
ResolveService.toggleResolveForDiscussion(this.mergeRequestId, this.discussionId);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -8,9 +8,7 @@ window.gl = window.gl || {};
|
|||
|
||||
class ResolveServiceClass {
|
||||
constructor(root) {
|
||||
this.noteResource = Vue.resource(
|
||||
`${root}/notes{/noteId}/resolve?html=true`,
|
||||
);
|
||||
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve?html=true`);
|
||||
this.discussionResource = Vue.resource(
|
||||
`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve?html=true`,
|
||||
);
|
||||
|
@ -51,10 +49,7 @@ class ResolveServiceClass {
|
|||
discussion.updateHeadline(data);
|
||||
})
|
||||
.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,
|
||||
}),
|
||||
...mapGetters('diffs', ['isParallelView']),
|
||||
...mapGetters(['isNotesFetched']),
|
||||
...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
|
||||
targetBranch() {
|
||||
return {
|
||||
branchName: this.targetBranchName,
|
||||
|
@ -112,18 +112,45 @@ export default {
|
|||
},
|
||||
created() {
|
||||
this.adjustView();
|
||||
eventHub.$once('fetchedNotesData', this.setDiscussions);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['setBaseConfig', 'fetchDiffFiles']),
|
||||
...mapActions('diffs', [
|
||||
'setBaseConfig',
|
||||
'fetchDiffFiles',
|
||||
'startRenderDiffsQueue',
|
||||
'assignDiscussionsToDiff',
|
||||
]),
|
||||
|
||||
fetchData() {
|
||||
this.fetchDiffFiles().catch(() => {
|
||||
createFlash(__('Something went wrong on our end. Please try again!'));
|
||||
});
|
||||
this.fetchDiffFiles()
|
||||
.then(() => {
|
||||
requestIdleCallback(
|
||||
() => {
|
||||
this.setDiscussions();
|
||||
this.startRenderDiffsQueue();
|
||||
},
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
createFlash(__('Something went wrong on our end. Please try again!'));
|
||||
});
|
||||
|
||||
if (!this.isNotesFetched) {
|
||||
eventHub.$emit('fetchNotesData');
|
||||
}
|
||||
},
|
||||
setDiscussions() {
|
||||
if (this.isNotesFetched) {
|
||||
requestIdleCallback(
|
||||
() => {
|
||||
this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
|
||||
},
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
}
|
||||
},
|
||||
adjustView() {
|
||||
if (this.shouldShow && this.isParallelView) {
|
||||
window.mrTabs.expandViewContainer();
|
||||
|
|
|
@ -63,7 +63,7 @@ export default {
|
|||
v-else
|
||||
role="button"
|
||||
class="fa fa-times dropdown-input-search"
|
||||
@click="clearSearch"
|
||||
@click.stop.prevent="clearSearch"
|
||||
></i>
|
||||
</div>
|
||||
<div class="dropdown-content">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import noteableDiscussion from '../../notes/components/noteable_discussion.vue';
|
||||
|
||||
export default {
|
||||
|
@ -11,6 +12,14 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['removeDiscussionsFromDiff']),
|
||||
deleteNoteHandler(discussion) {
|
||||
if (discussion.notes.length <= 1) {
|
||||
this.removeDiscussionsFromDiff(discussion);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -31,6 +40,7 @@ export default {
|
|||
:render-diff-file="false"
|
||||
:always-expanded="true"
|
||||
:discussions-by-diff-order="true"
|
||||
@noteDeleted="deleteNoteHandler"
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import _ from 'underscore';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import createFlash from '~/flash';
|
||||
|
@ -30,6 +30,7 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']),
|
||||
isCollapsed() {
|
||||
return this.file.collapsed || false;
|
||||
},
|
||||
|
@ -44,18 +45,27 @@ export default {
|
|||
);
|
||||
},
|
||||
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: {
|
||||
...mapActions('diffs', ['loadCollapsedDiff']),
|
||||
...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff']),
|
||||
handleToggle() {
|
||||
const { collapsed, highlightedDiffLines, parallelDiffLines } = this.file;
|
||||
|
||||
if (collapsed && !highlightedDiffLines && !parallelDiffLines.length) {
|
||||
const { highlightedDiffLines, parallelDiffLines } = this.file;
|
||||
if (!highlightedDiffLines && parallelDiffLines !== undefined && !parallelDiffLines.length) {
|
||||
this.handleLoadCollapsedDiff();
|
||||
} else {
|
||||
this.file.collapsed = !this.file.collapsed;
|
||||
this.file.renderIt = true;
|
||||
}
|
||||
},
|
||||
handleLoadCollapsedDiff() {
|
||||
|
@ -65,6 +75,15 @@ export default {
|
|||
.then(() => {
|
||||
this.isLoadingCollapsedDiff = false;
|
||||
this.file.collapsed = false;
|
||||
this.file.renderIt = true;
|
||||
})
|
||||
.then(() => {
|
||||
requestIdleCallback(
|
||||
() => {
|
||||
this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode);
|
||||
},
|
||||
{ timeout: 1000 },
|
||||
);
|
||||
})
|
||||
.catch(() => {
|
||||
this.isLoadingCollapsedDiff = false;
|
||||
|
@ -121,16 +140,16 @@ export default {
|
|||
</div>
|
||||
|
||||
<diff-content
|
||||
v-if="!isCollapsed"
|
||||
v-if="!isCollapsed && file.renderIt"
|
||||
:class="{ hidden: isCollapsed || file.tooLarge }"
|
||||
:diff-file="file"
|
||||
/>
|
||||
<loading-icon
|
||||
v-if="isLoadingCollapsedDiff"
|
||||
v-if="showLoadingIcon"
|
||||
class="diff-content loading"
|
||||
/>
|
||||
<div
|
||||
v-if="showExpandMessage"
|
||||
v-else-if="showExpandMessage"
|
||||
class="nothing-here-block diff-collapsed"
|
||||
>
|
||||
{{ __('This diff is collapsed.') }}
|
||||
|
|
|
@ -13,6 +13,10 @@ export default {
|
|||
Icon,
|
||||
},
|
||||
props: {
|
||||
line: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
fileHash: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -21,31 +25,16 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
lineType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
lineNumber: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
lineCode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
linePosition: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
metaData: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
showCommentButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
@ -76,11 +65,6 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
discussions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
|
@ -89,7 +73,7 @@ export default {
|
|||
}),
|
||||
...mapGetters(['isLoggedIn']),
|
||||
lineHref() {
|
||||
return this.lineCode ? `#${this.lineCode}` : '#';
|
||||
return `#${this.line.lineCode || ''}`;
|
||||
},
|
||||
shouldShowCommentButton() {
|
||||
return (
|
||||
|
@ -103,20 +87,19 @@ export default {
|
|||
);
|
||||
},
|
||||
hasDiscussions() {
|
||||
return this.discussions.length > 0;
|
||||
return this.line.discussions && this.line.discussions.length > 0;
|
||||
},
|
||||
shouldShowAvatarsOnGutter() {
|
||||
if (!this.lineType && this.linePosition === LINE_POSITION_RIGHT) {
|
||||
if (!this.line.type && this.linePosition === LINE_POSITION_RIGHT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.showCommentButton && this.hasDiscussions;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['loadMoreLines', 'showCommentForm']),
|
||||
handleCommentButton() {
|
||||
this.showCommentForm({ lineCode: this.lineCode });
|
||||
this.showCommentForm({ lineCode: this.line.lineCode });
|
||||
},
|
||||
handleLoadMoreLines() {
|
||||
if (this.isRequesting) {
|
||||
|
@ -125,8 +108,8 @@ export default {
|
|||
|
||||
this.isRequesting = true;
|
||||
const endpoint = this.contextLinesPath;
|
||||
const oldLineNumber = this.metaData.oldPos || 0;
|
||||
const newLineNumber = this.metaData.newPos || 0;
|
||||
const oldLineNumber = this.line.metaData.oldPos || 0;
|
||||
const newLineNumber = this.line.metaData.newPos || 0;
|
||||
const offset = newLineNumber - oldLineNumber;
|
||||
const bottom = this.isBottom;
|
||||
const { fileHash } = this;
|
||||
|
@ -201,7 +184,7 @@ export default {
|
|||
</a>
|
||||
<diff-gutter-avatars
|
||||
v-if="shouldShowAvatarsOnGutter"
|
||||
:discussions="discussions"
|
||||
:discussions="line.discussions"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
|
|
@ -6,6 +6,7 @@ import noteForm from '../../notes/components/note_form.vue';
|
|||
import { getNoteFormData } from '../store/utils';
|
||||
import autosave from '../../notes/mixins/autosave';
|
||||
import { DIFF_NOTE_TYPE } from '../constants';
|
||||
import { reduceDiscussionsToLineCodes } from '../../notes/stores/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -52,7 +53,7 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['cancelCommentForm']),
|
||||
...mapActions('diffs', ['cancelCommentForm', 'assignDiscussionsToDiff']),
|
||||
...mapActions(['saveNote', 'refetchDiscussionById']),
|
||||
handleCancelCommentForm(shouldConfirm, isDirty) {
|
||||
if (shouldConfirm && isDirty) {
|
||||
|
@ -88,7 +89,10 @@ export default {
|
|||
const endpoint = this.getNotesDataByProp('discussionsPath');
|
||||
|
||||
this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id })
|
||||
.then(() => {
|
||||
.then(selectedDiscussion => {
|
||||
const lineCodeDiscussions = reduceDiscussionsToLineCodes([selectedDiscussion]);
|
||||
this.assignDiscussionsToDiff(lineCodeDiscussions);
|
||||
|
||||
this.handleCancelCommentForm();
|
||||
})
|
||||
.catch(() => {
|
||||
|
|
|
@ -11,8 +11,6 @@ import {
|
|||
LINE_HOVER_CLASS_NAME,
|
||||
LINE_UNFOLD_CLASS_NAME,
|
||||
INLINE_DIFF_VIEW_TYPE,
|
||||
LINE_POSITION_LEFT,
|
||||
LINE_POSITION_RIGHT,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
|
@ -67,42 +65,24 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
discussions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...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() {
|
||||
return this.normalizedLine.type === MATCH_LINE_TYPE;
|
||||
return this.line.type === MATCH_LINE_TYPE;
|
||||
},
|
||||
isContextLine() {
|
||||
return this.normalizedLine.type === CONTEXT_LINE_TYPE;
|
||||
return this.line.type === CONTEXT_LINE_TYPE;
|
||||
},
|
||||
isMetaLine() {
|
||||
const { type } = this.normalizedLine;
|
||||
const { type } = this.line;
|
||||
|
||||
return (
|
||||
type === OLD_NO_NEW_LINE_TYPE || type === NEW_NO_NEW_LINE_TYPE || type === EMPTY_CELL_TYPE
|
||||
);
|
||||
},
|
||||
classNameMap() {
|
||||
const { type } = this.normalizedLine;
|
||||
const { type } = this.line;
|
||||
|
||||
return {
|
||||
[type]: type,
|
||||
|
@ -116,9 +96,9 @@ export default {
|
|||
};
|
||||
},
|
||||
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"
|
||||
>
|
||||
<diff-line-gutter-content
|
||||
:line="line"
|
||||
:file-hash="fileHash"
|
||||
:context-lines-path="contextLinesPath"
|
||||
:line-type="normalizedLine.type"
|
||||
:line-code="normalizedLine.lineCode"
|
||||
:line-position="linePosition"
|
||||
:line-number="lineNumber"
|
||||
:meta-data="normalizedLine.metaData"
|
||||
:show-comment-button="showCommentButton"
|
||||
:is-hover="isHover"
|
||||
:is-bottom="isBottom"
|
||||
:is-match-line="isMatchLine"
|
||||
:is-context-line="isContentLine"
|
||||
:is-meta-line="isMetaLine"
|
||||
:discussions="discussions"
|
||||
/>
|
||||
</td>
|
||||
</template>
|
||||
|
|
|
@ -21,18 +21,13 @@ export default {
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
discussions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
||||
}),
|
||||
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"
|
||||
>
|
||||
<td
|
||||
class="notes_line"
|
||||
colspan="2"
|
||||
></td>
|
||||
<td class="notes_content">
|
||||
class="notes_content"
|
||||
colspan="3"
|
||||
>
|
||||
<div class="content">
|
||||
<diff-discussions
|
||||
v-if="discussions.length"
|
||||
:discussions="discussions"
|
||||
v-if="line.discussions.length"
|
||||
:discussions="line.discussions"
|
||||
/>
|
||||
<diff-line-note-form
|
||||
v-if="diffLineCommentForms[line.lineCode]"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import DiffTableCell from './diff_table_cell.vue';
|
||||
import {
|
||||
NEW_LINE_TYPE,
|
||||
|
@ -33,11 +33,6 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
discussions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -68,7 +63,11 @@ export default {
|
|||
this.linePositionLeft = LINE_POSITION_LEFT;
|
||||
this.linePositionRight = LINE_POSITION_RIGHT;
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToLineIfNeededInline(this.line);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['scrollToLineIfNeededInline']),
|
||||
handleMouseMove(e) {
|
||||
// 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
|
||||
|
@ -94,7 +93,6 @@ export default {
|
|||
:is-bottom="isBottom"
|
||||
:is-hover="isHover"
|
||||
:show-comment-button="true"
|
||||
:discussions="discussions"
|
||||
class="diff-line-num old_line"
|
||||
/>
|
||||
<diff-table-cell
|
||||
|
@ -104,7 +102,6 @@ export default {
|
|||
:line-type="newLineType"
|
||||
:is-bottom="isBottom"
|
||||
:is-hover="isHover"
|
||||
:discussions="discussions"
|
||||
class="diff-line-num new_line"
|
||||
/>
|
||||
<td
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { mapGetters, mapState } from 'vuex';
|
||||
import inlineDiffTableRow from './inline_diff_table_row.vue';
|
||||
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
|
||||
import { trimFirstCharOfLineContent } from '../store/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -20,29 +19,17 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', [
|
||||
'commitId',
|
||||
'shouldRenderInlineCommentRow',
|
||||
'singleDiscussionByLineCode',
|
||||
]),
|
||||
...mapGetters('diffs', ['commitId', 'shouldRenderInlineCommentRow']),
|
||||
...mapState({
|
||||
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
||||
}),
|
||||
normalizedDiffLines() {
|
||||
return this.diffLines.map(line => (line.richText ? trimFirstCharOfLineContent(line) : line));
|
||||
},
|
||||
diffLinesLength() {
|
||||
return this.normalizedDiffLines.length;
|
||||
return this.diffLines.length;
|
||||
},
|
||||
userColorScheme() {
|
||||
return window.gon.user_color_scheme;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
discussionsList(line) {
|
||||
return line.lineCode !== undefined ? this.singleDiscussionByLineCode(line.lineCode) : [];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -53,7 +40,7 @@ export default {
|
|||
class="code diff-wrap-lines js-syntax-highlight text-file js-diff-inline-view">
|
||||
<tbody>
|
||||
<template
|
||||
v-for="(line, index) in normalizedDiffLines"
|
||||
v-for="(line, index) in diffLines"
|
||||
>
|
||||
<inline-diff-table-row
|
||||
:file-hash="diffFile.fileHash"
|
||||
|
@ -61,7 +48,6 @@ export default {
|
|||
:line="line"
|
||||
:is-bottom="index + 1 === diffLinesLength"
|
||||
:key="line.lineCode"
|
||||
:discussions="discussionsList(line)"
|
||||
/>
|
||||
<inline-diff-comment-row
|
||||
v-if="shouldRenderInlineCommentRow(line)"
|
||||
|
@ -69,7 +55,6 @@ export default {
|
|||
:line="line"
|
||||
:line-index="index"
|
||||
:key="index"
|
||||
:discussions="discussionsList(line)"
|
||||
/>
|
||||
</template>
|
||||
</tbody>
|
||||
|
|
|
@ -21,51 +21,49 @@ export default {
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
leftDiscussions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
rightDiscussions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
diffLineCommentForms: state => state.diffs.diffLineCommentForms,
|
||||
}),
|
||||
leftLineCode() {
|
||||
return this.line.left.lineCode;
|
||||
return this.line.left && this.line.left.lineCode;
|
||||
},
|
||||
rightLineCode() {
|
||||
return this.line.right.lineCode;
|
||||
return this.line.right && this.line.right.lineCode;
|
||||
},
|
||||
hasExpandedDiscussionOnLeft() {
|
||||
const discussions = this.leftDiscussions;
|
||||
|
||||
return discussions ? discussions.every(discussion => discussion.expanded) : false;
|
||||
return this.line.left && this.line.left.discussions
|
||||
? this.line.left.discussions.every(discussion => discussion.expanded)
|
||||
: false;
|
||||
},
|
||||
hasExpandedDiscussionOnRight() {
|
||||
const discussions = this.rightDiscussions;
|
||||
|
||||
return discussions ? discussions.every(discussion => discussion.expanded) : false;
|
||||
return this.line.right && this.line.right.discussions
|
||||
? this.line.right.discussions.every(discussion => discussion.expanded)
|
||||
: false;
|
||||
},
|
||||
hasAnyExpandedDiscussion() {
|
||||
return this.hasExpandedDiscussionOnLeft || this.hasExpandedDiscussionOnRight;
|
||||
},
|
||||
shouldRenderDiscussionsOnLeft() {
|
||||
return this.leftDiscussions && this.hasExpandedDiscussionOnLeft;
|
||||
return this.line.left && this.line.left.discussions && this.hasExpandedDiscussionOnLeft;
|
||||
},
|
||||
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() {
|
||||
return this.line.right.type && this.diffLineCommentForms[this.rightLineCode];
|
||||
return (
|
||||
this.line.right && this.line.right.type && this.diffLineCommentForms[this.rightLineCode]
|
||||
);
|
||||
},
|
||||
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';
|
||||
},
|
||||
|
@ -85,8 +83,8 @@ export default {
|
|||
class="content"
|
||||
>
|
||||
<diff-discussions
|
||||
v-if="leftDiscussions.length"
|
||||
:discussions="leftDiscussions"
|
||||
v-if="line.left.discussions.length"
|
||||
:discussions="line.left.discussions"
|
||||
/>
|
||||
</div>
|
||||
<diff-line-note-form
|
||||
|
@ -104,8 +102,8 @@ export default {
|
|||
class="content"
|
||||
>
|
||||
<diff-discussions
|
||||
v-if="rightDiscussions.length"
|
||||
:discussions="rightDiscussions"
|
||||
v-if="line.right.discussions.length"
|
||||
:discussions="line.right.discussions"
|
||||
/>
|
||||
</div>
|
||||
<diff-line-note-form
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import $ from 'jquery';
|
||||
import { mapGetters } from 'vuex';
|
||||
import DiffTableCell from './diff_table_cell.vue';
|
||||
import {
|
||||
NEW_LINE_TYPE,
|
||||
|
@ -10,8 +10,7 @@ import {
|
|||
OLD_NO_NEW_LINE_TYPE,
|
||||
PARALLEL_DIFF_VIEW_TYPE,
|
||||
NEW_NO_NEW_LINE_TYPE,
|
||||
LINE_POSITION_LEFT,
|
||||
LINE_POSITION_RIGHT,
|
||||
EMPTY_CELL_TYPE,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
|
@ -36,16 +35,6 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
leftDiscussions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
rightDiscussions: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -54,32 +43,33 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', ['isParallelView']),
|
||||
isContextLine() {
|
||||
return this.line.left.type === CONTEXT_LINE_TYPE;
|
||||
return this.line.left && this.line.left.type === CONTEXT_LINE_TYPE;
|
||||
},
|
||||
classNameMap() {
|
||||
return {
|
||||
[CONTEXT_LINE_CLASS_NAME]: this.isContextLine,
|
||||
[PARALLEL_DIFF_VIEW_TYPE]: this.isParallelView,
|
||||
[PARALLEL_DIFF_VIEW_TYPE]: true,
|
||||
};
|
||||
},
|
||||
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 this.line.left.type;
|
||||
return this.line.left ? this.line.left.type : EMPTY_CELL_TYPE;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.newLineType = NEW_LINE_TYPE;
|
||||
this.oldLineType = OLD_LINE_TYPE;
|
||||
this.linePositionLeft = LINE_POSITION_LEFT;
|
||||
this.linePositionRight = LINE_POSITION_RIGHT;
|
||||
this.parallelDiffViewType = PARALLEL_DIFF_VIEW_TYPE;
|
||||
},
|
||||
mounted() {
|
||||
this.scrollToLineIfNeededParallel(this.line);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['scrollToLineIfNeededParallel']),
|
||||
handleMouseMove(e) {
|
||||
const isHover = e.type === 'mouseover';
|
||||
const hoveringCell = e.target.closest('td');
|
||||
|
@ -116,47 +106,57 @@ export default {
|
|||
@mouseover="handleMouseMove"
|
||||
@mouseout="handleMouseMove"
|
||||
>
|
||||
<diff-table-cell
|
||||
:file-hash="fileHash"
|
||||
:context-lines-path="contextLinesPath"
|
||||
:line="line"
|
||||
:line-type="oldLineType"
|
||||
:line-position="linePositionLeft"
|
||||
:is-bottom="isBottom"
|
||||
:is-hover="isLeftHover"
|
||||
:show-comment-button="true"
|
||||
:diff-view-type="parallelDiffViewType"
|
||||
:discussions="leftDiscussions"
|
||||
class="diff-line-num old_line"
|
||||
/>
|
||||
<td
|
||||
:id="line.left.lineCode"
|
||||
:class="parallelViewLeftLineType"
|
||||
class="line_content parallel left-side"
|
||||
@mousedown.native="handleParallelLineMouseDown"
|
||||
v-html="line.left.richText"
|
||||
>
|
||||
</td>
|
||||
<diff-table-cell
|
||||
:file-hash="fileHash"
|
||||
:context-lines-path="contextLinesPath"
|
||||
:line="line"
|
||||
:line-type="newLineType"
|
||||
:line-position="linePositionRight"
|
||||
:is-bottom="isBottom"
|
||||
:is-hover="isRightHover"
|
||||
:show-comment-button="true"
|
||||
:diff-view-type="parallelDiffViewType"
|
||||
:discussions="rightDiscussions"
|
||||
class="diff-line-num new_line"
|
||||
/>
|
||||
<td
|
||||
:id="line.right.lineCode"
|
||||
:class="line.right.type"
|
||||
class="line_content parallel right-side"
|
||||
@mousedown.native="handleParallelLineMouseDown"
|
||||
v-html="line.right.richText"
|
||||
>
|
||||
</td>
|
||||
<template v-if="line.left">
|
||||
<diff-table-cell
|
||||
:file-hash="fileHash"
|
||||
:context-lines-path="contextLinesPath"
|
||||
:line="line.left"
|
||||
:line-type="oldLineType"
|
||||
:is-bottom="isBottom"
|
||||
:is-hover="isLeftHover"
|
||||
:show-comment-button="true"
|
||||
:diff-view-type="parallelDiffViewType"
|
||||
line-position="left"
|
||||
class="diff-line-num old_line"
|
||||
/>
|
||||
<td
|
||||
:id="line.left.lineCode"
|
||||
:class="parallelViewLeftLineType"
|
||||
class="line_content parallel left-side"
|
||||
@mousedown.native="handleParallelLineMouseDown"
|
||||
v-html="line.left.richText"
|
||||
>
|
||||
</td>
|
||||
</template>
|
||||
<template v-else>
|
||||
<td class="diff-line-num old_line empty-cell"></td>
|
||||
<td class="line_content parallel left-side empty-cell"></td>
|
||||
</template>
|
||||
<template v-if="line.right">
|
||||
<diff-table-cell
|
||||
:file-hash="fileHash"
|
||||
:context-lines-path="contextLinesPath"
|
||||
:line="line.right"
|
||||
:line-type="newLineType"
|
||||
:is-bottom="isBottom"
|
||||
:is-hover="isRightHover"
|
||||
:show-comment-button="true"
|
||||
:diff-view-type="parallelDiffViewType"
|
||||
line-position="right"
|
||||
class="diff-line-num new_line"
|
||||
/>
|
||||
<td
|
||||
:id="line.right.lineCode"
|
||||
: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>
|
||||
</template>
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
import { mapState, mapGetters } from 'vuex';
|
||||
import parallelDiffTableRow from './parallel_diff_table_row.vue';
|
||||
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
|
||||
import { EMPTY_CELL_TYPE } from '../constants';
|
||||
import { trimFirstCharOfLineContent } from '../store/utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -21,46 +19,17 @@ export default {
|
|||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters('diffs', [
|
||||
'commitId',
|
||||
'singleDiscussionByLineCode',
|
||||
'shouldRenderParallelCommentRow',
|
||||
]),
|
||||
...mapGetters('diffs', ['commitId', 'shouldRenderParallelCommentRow']),
|
||||
...mapState({
|
||||
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() {
|
||||
return this.parallelDiffLines.length;
|
||||
return this.diffLines.length;
|
||||
},
|
||||
userColorScheme() {
|
||||
return window.gon.user_color_scheme;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
discussionsByLine(line, leftOrRight) {
|
||||
return line[leftOrRight] && line[leftOrRight].lineCode !== undefined ?
|
||||
this.singleDiscussionByLineCode(line[leftOrRight].lineCode) : [];
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -73,7 +42,7 @@ export default {
|
|||
<table>
|
||||
<tbody>
|
||||
<template
|
||||
v-for="(line, index) in parallelDiffLines"
|
||||
v-for="(line, index) in diffLines"
|
||||
>
|
||||
<parallel-diff-table-row
|
||||
:file-hash="diffFile.fileHash"
|
||||
|
@ -81,8 +50,6 @@ export default {
|
|||
:line="line"
|
||||
:is-bottom="index + 1 === diffLinesLength"
|
||||
:key="index"
|
||||
:left-discussions="discussionsByLine(line, 'left')"
|
||||
:right-discussions="discussionsByLine(line, 'right')"
|
||||
/>
|
||||
<parallel-diff-comment-row
|
||||
v-if="shouldRenderParallelCommentRow(line)"
|
||||
|
@ -90,8 +57,6 @@ export default {
|
|||
:line="line"
|
||||
:diff-file-hash="diffFile.fileHash"
|
||||
:line-index="index"
|
||||
:left-discussions="discussionsByLine(line, 'left')"
|
||||
:right-discussions="discussionsByLine(line, 'right')"
|
||||
/>
|
||||
</template>
|
||||
</tbody>
|
||||
|
|
|
@ -25,3 +25,6 @@ export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
|
|||
export const UNFOLD_COUNT = 20;
|
||||
export const COUNT_OF_AVATARS_IN_GUTTER = 3;
|
||||
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 Cookies from 'js-cookie';
|
||||
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 {
|
||||
PARALLEL_DIFF_VIEW_TYPE,
|
||||
|
@ -29,6 +30,55 @@ export const fetchDiffFiles = ({ state, commit }) => {
|
|||
.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 }) => {
|
||||
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) =>
|
||||
axios.get(file.loadCollapsedDiffUrl).then(res => {
|
||||
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 => {
|
||||
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 => {
|
||||
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);
|
||||
|
||||
return (
|
||||
(discussions.length && discussions.find(discussion => discussion.expanded) !== undefined) ||
|
||||
(discussions &&
|
||||
discussions.length &&
|
||||
discussions.find(discussion => discussion.expanded) !== undefined) ||
|
||||
false
|
||||
);
|
||||
};
|
||||
|
@ -64,45 +72,38 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) =
|
|||
discussion.diff_discussion && _.isEqual(discussion.diff_file.file_hash, diff.fileHash),
|
||||
) || [];
|
||||
|
||||
export const singleDiscussionByLineCode = (state, getters, rootState, rootGetters) => lineCode => {
|
||||
if (!lineCode || lineCode === undefined) return [];
|
||||
const discussions = rootGetters.discussionsByLineCode;
|
||||
return discussions[lineCode] || [];
|
||||
};
|
||||
export const shouldRenderParallelCommentRow = state => line => {
|
||||
const hasDiscussion =
|
||||
(line.left && line.left.discussions && line.left.discussions.length) ||
|
||||
(line.right && line.right.discussions && line.right.discussions.length);
|
||||
|
||||
export const shouldRenderParallelCommentRow = (state, getters) => line => {
|
||||
const leftLineCode = line.left.lineCode;
|
||||
const rightLineCode = line.right.lineCode;
|
||||
const leftDiscussions = getters.singleDiscussionByLineCode(leftLineCode);
|
||||
const rightDiscussions = getters.singleDiscussionByLineCode(rightLineCode);
|
||||
const hasDiscussion = leftDiscussions.length || rightDiscussions.length;
|
||||
|
||||
const hasExpandedDiscussionOnLeft = leftDiscussions.length
|
||||
? leftDiscussions.every(discussion => discussion.expanded)
|
||||
: false;
|
||||
const hasExpandedDiscussionOnRight = rightDiscussions.length
|
||||
? rightDiscussions.every(discussion => discussion.expanded)
|
||||
: false;
|
||||
const hasExpandedDiscussionOnLeft =
|
||||
line.left && line.left.discussions && line.left.discussions.length
|
||||
? line.left.discussions.every(discussion => discussion.expanded)
|
||||
: false;
|
||||
const hasExpandedDiscussionOnRight =
|
||||
line.right && line.right.discussions && line.right.discussions.length
|
||||
? line.right.discussions.every(discussion => discussion.expanded)
|
||||
: false;
|
||||
|
||||
if (hasDiscussion && (hasExpandedDiscussionOnLeft || hasExpandedDiscussionOnRight)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const hasCommentFormOnLeft = state.diffLineCommentForms[leftLineCode];
|
||||
const hasCommentFormOnRight = state.diffLineCommentForms[rightLineCode];
|
||||
const hasCommentFormOnLeft = line.left && state.diffLineCommentForms[line.left.lineCode];
|
||||
const hasCommentFormOnRight = line.right && state.diffLineCommentForms[line.right.lineCode];
|
||||
|
||||
return hasCommentFormOnLeft || hasCommentFormOnRight;
|
||||
};
|
||||
|
||||
export const shouldRenderInlineCommentRow = (state, getters) => line => {
|
||||
export const shouldRenderInlineCommentRow = state => line => {
|
||||
if (state.diffLineCommentForms[line.lineCode]) return true;
|
||||
|
||||
const lineDiscussions = getters.singleDiscussionByLineCode(line.lineCode);
|
||||
if (lineDiscussions.length === 0) {
|
||||
if (!line.discussions || line.discussions.length === 0) {
|
||||
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
|
||||
|
|
|
@ -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_COLLAPSED_DIFFS = 'ADD_COLLAPSED_DIFFS';
|
||||
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 _ from 'underscore';
|
||||
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';
|
||||
|
||||
export default {
|
||||
|
@ -15,8 +21,17 @@ export default {
|
|||
},
|
||||
|
||||
[types.SET_DIFF_DATA](state, data) {
|
||||
const diffData = convertObjectPropsToCamelCase(data, { deep: true });
|
||||
prepareDiffData(diffData);
|
||||
|
||||
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 }) {
|
||||
const normalizedData = convertObjectPropsToCamelCase(data, { deep: true });
|
||||
prepareDiffData(normalizedData);
|
||||
const [newFileData] = normalizedData.diffFiles.filter(f => f.fileHash === file.fileHash);
|
||||
|
||||
if (newFileData) {
|
||||
const index = _.findIndex(state.diffFiles, f => f.fileHash === file.fileHash);
|
||||
state.diffFiles.splice(index, 1, newFileData);
|
||||
}
|
||||
const selectedFile = state.diffFiles.find(f => f.fileHash === file.fileHash);
|
||||
Object.assign(selectedFile, { ...newFileData });
|
||||
},
|
||||
|
||||
[types.EXPAND_ALL_FILES](state) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
state.diffFiles = state.diffFiles.map(file => ({
|
||||
...file,
|
||||
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,
|
||||
OLD_LINE_TYPE,
|
||||
MATCH_LINE_TYPE,
|
||||
LINES_TO_BE_RENDERED_DIRECTLY,
|
||||
MAX_LINES_TO_BE_RENDERED,
|
||||
} from '../constants';
|
||||
|
||||
export function findDiffFile(files, hash) {
|
||||
|
@ -161,6 +163,11 @@ export function addContextLines(options) {
|
|||
* @returns {Object}
|
||||
*/
|
||||
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);
|
||||
|
||||
if (line.richText) {
|
||||
|
@ -174,7 +181,44 @@ export function trimFirstCharOfLineContent(line = {}) {
|
|||
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) => {
|
||||
const { baseSha, headSha, startSha } = diffFile.diffRefs;
|
||||
const { newPath, oldPath } = diffFile;
|
||||
|
@ -186,7 +230,7 @@ export function getDiffRefsByLineCode(diffFiles) {
|
|||
const { lineCode, oldLine, newLine } = line;
|
||||
|
||||
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;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// 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 Flash from '~/flash';
|
||||
|
||||
export default function gcpSignupOffer() {
|
||||
const alertEl = document.querySelector('.gcp-signup-offer');
|
||||
export default function initDismissableCallout(alertSelector) {
|
||||
const alertEl = document.querySelector(alertSelector);
|
||||
if (!alertEl) {
|
||||
return;
|
||||
}
|
|
@ -7,6 +7,19 @@ import axios from './lib/utils/axios_utils';
|
|||
|
||||
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) {
|
||||
const divHover = '<div class="div-dropzone-hover"></div>';
|
||||
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 $uploadingErrorMessage = form.find('.uploading-error-message');
|
||||
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 formTextarea = form.find('.js-gfm-input');
|
||||
let handlePaste;
|
||||
|
@ -42,7 +55,7 @@ export default function dropzoneInput(form) {
|
|||
|
||||
if (!uploadsPath) {
|
||||
$formDropzone.addClass('js-invalid-dropzone');
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const dropzone = $formDropzone.dropzone({
|
||||
|
@ -84,9 +97,7 @@ export default function dropzoneInput(form) {
|
|||
// xhr object (xhr.responseText is error message).
|
||||
// On error we hide the 'Attach' and 'Cancel' buttons
|
||||
// and show an error.
|
||||
|
||||
// If there's xhr error message, let's show it instead of dropzone's one.
|
||||
const message = xhr ? xhr.responseText : errorMessage;
|
||||
const message = getErrorMessage(errorMessage || xhr.responseText);
|
||||
|
||||
$uploadingErrorContainer.removeClass('hide');
|
||||
$uploadingErrorMessage.html(message);
|
||||
|
@ -274,4 +285,6 @@ export default function dropzoneInput(form) {
|
|||
$(this).closest('.gfm-form').find('.div-dropzone').click();
|
||||
formTextarea.focus();
|
||||
});
|
||||
|
||||
return Dropzone.forElement($formDropzone.get(0));
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ function generateUnicodeSupportMap(testMap) {
|
|||
canvas.height = numTestEntries * fontSize;
|
||||
ctx.fillStyle = '#000000';
|
||||
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
|
||||
let writeIndex = 0;
|
||||
testMapKeys.forEach(testKey => {
|
||||
|
|
|
@ -10,6 +10,7 @@ const hideFlash = (flashEl, fadeTransition = true) => {
|
|||
|
||||
flashEl.addEventListener('transitionend', () => {
|
||||
flashEl.remove();
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown');
|
||||
}, {
|
||||
once: true,
|
||||
|
|
|
@ -65,8 +65,8 @@ export const hideMenu = (el) => {
|
|||
|
||||
const parentEl = el.parentNode;
|
||||
|
||||
el.style.display = ''; // eslint-disable-line no-param-reassign
|
||||
el.style.transform = ''; // eslint-disable-line no-param-reassign
|
||||
el.style.display = '';
|
||||
el.style.transform = '';
|
||||
el.classList.remove(IS_ABOVE_CLASS);
|
||||
parentEl.classList.remove(IS_OVER_CLASS);
|
||||
parentEl.classList.remove(IS_SHOWING_FLY_OUT_CLASS);
|
||||
|
|
|
@ -149,10 +149,16 @@ class GfmAutoComplete {
|
|||
// Team Members
|
||||
$input.atwho({
|
||||
at: '@',
|
||||
alias: 'users',
|
||||
displayTpl(value) {
|
||||
let tmpl = GfmAutoComplete.Loading.template;
|
||||
if (value.username != null) {
|
||||
tmpl = GfmAutoComplete.Members.template;
|
||||
const { avatarTag, username, title } = value;
|
||||
if (username != null) {
|
||||
tmpl = GfmAutoComplete.Members.templateFunction({
|
||||
avatarTag,
|
||||
username,
|
||||
title,
|
||||
});
|
||||
}
|
||||
return tmpl;
|
||||
},
|
||||
|
@ -512,8 +518,9 @@ GfmAutoComplete.Emoji = {
|
|||
};
|
||||
// Team Members
|
||||
GfmAutoComplete.Members = {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
template: '<li>${avatarTag} ${username} <small>${title}</small></li>',
|
||||
templateFunction({ avatarTag, username, title }) {
|
||||
return `<li>${avatarTag} ${username} <small>${_.escape(title)}</small></li>`;
|
||||
},
|
||||
};
|
||||
GfmAutoComplete.Labels = {
|
||||
// eslint-disable-next-line no-template-curly-in-string
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
/* global Flash */
|
||||
|
||||
import $ from 'jquery';
|
||||
import { s__ } from '~/locale';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import loadingIcon from '~/vue_shared/components/loading_icon.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 { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
|
||||
import eventHub from '../event_hub';
|
||||
import { COMMON_STR } from '../constants';
|
||||
import { COMMON_STR, CONTENT_LIST_CLASS } from '../constants';
|
||||
import groupsComponent from './groups.vue';
|
||||
|
||||
export default {
|
||||
|
@ -19,6 +20,16 @@ export default {
|
|||
groupsComponent,
|
||||
},
|
||||
props: {
|
||||
action: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
containerId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
store: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -56,31 +67,28 @@ export default {
|
|||
? COMMON_STR.GROUP_SEARCH_EMPTY
|
||||
: COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY;
|
||||
|
||||
eventHub.$on('fetchPage', this.fetchPage);
|
||||
eventHub.$on('toggleChildren', this.toggleChildren);
|
||||
eventHub.$on('showLeaveGroupModal', this.showLeaveGroupModal);
|
||||
eventHub.$on('updatePagination', this.updatePagination);
|
||||
eventHub.$on('updateGroups', this.updateGroups);
|
||||
eventHub.$on(`${this.action}fetchPage`, this.fetchPage);
|
||||
eventHub.$on(`${this.action}toggleChildren`, this.toggleChildren);
|
||||
eventHub.$on(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
|
||||
eventHub.$on(`${this.action}updatePagination`, this.updatePagination);
|
||||
eventHub.$on(`${this.action}updateGroups`, this.updateGroups);
|
||||
},
|
||||
mounted() {
|
||||
this.fetchAllGroups();
|
||||
|
||||
if (this.containerId) {
|
||||
this.containerEl = document.getElementById(this.containerId);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('fetchPage', this.fetchPage);
|
||||
eventHub.$off('toggleChildren', this.toggleChildren);
|
||||
eventHub.$off('showLeaveGroupModal', this.showLeaveGroupModal);
|
||||
eventHub.$off('updatePagination', this.updatePagination);
|
||||
eventHub.$off('updateGroups', this.updateGroups);
|
||||
eventHub.$off(`${this.action}fetchPage`, this.fetchPage);
|
||||
eventHub.$off(`${this.action}toggleChildren`, this.toggleChildren);
|
||||
eventHub.$off(`${this.action}showLeaveGroupModal`, this.showLeaveGroupModal);
|
||||
eventHub.$off(`${this.action}updatePagination`, this.updatePagination);
|
||||
eventHub.$off(`${this.action}updateGroups`, this.updateGroups);
|
||||
},
|
||||
methods: {
|
||||
fetchGroups({
|
||||
parentId,
|
||||
page,
|
||||
filterGroupsBy,
|
||||
sortBy,
|
||||
archived,
|
||||
updatePagination,
|
||||
}) {
|
||||
fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) {
|
||||
return this.service
|
||||
.getGroups(parentId, page, filterGroupsBy, sortBy, archived)
|
||||
.then(res => {
|
||||
|
@ -165,13 +173,13 @@ export default {
|
|||
}
|
||||
},
|
||||
showLeaveGroupModal(group, parentGroup) {
|
||||
const { fullName } = group;
|
||||
this.targetGroup = group;
|
||||
this.targetParentGroup = parentGroup;
|
||||
this.showModal = true;
|
||||
this.groupLeaveConfirmationMessage = s__(
|
||||
`GroupsTree|Are you sure you want to leave the "${
|
||||
group.fullName
|
||||
}" group?`,
|
||||
this.groupLeaveConfirmationMessage = sprintf(
|
||||
s__('GroupsTree|Are you sure you want to leave the "%{fullName}" group?'),
|
||||
{ fullName },
|
||||
);
|
||||
},
|
||||
hideLeaveGroupModal() {
|
||||
|
@ -197,16 +205,35 @@ export default {
|
|||
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) {
|
||||
this.store.setPaginationInfo(headers);
|
||||
},
|
||||
updateGroups(groups, fromSearch) {
|
||||
this.isSearchEmpty = groups ? groups.length === 0 : false;
|
||||
const hasGroups = groups && groups.length > 0;
|
||||
this.isSearchEmpty = !hasGroups;
|
||||
|
||||
if (fromSearch) {
|
||||
this.store.setSearchedGroups(groups);
|
||||
} else {
|
||||
this.store.setGroups(groups);
|
||||
}
|
||||
|
||||
if (this.action && !hasGroups && !fromSearch) {
|
||||
this.showEmptyState();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -226,6 +253,7 @@ export default {
|
|||
:search-empty="isSearchEmpty"
|
||||
:search-empty-message="searchEmptyMessage"
|
||||
:page-info="pageInfo"
|
||||
:action="action"
|
||||
/>
|
||||
<deprecated-modal
|
||||
v-show="showModal"
|
||||
|
|
|
@ -11,8 +11,12 @@ export default {
|
|||
},
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: () => ([]),
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
@ -37,6 +41,7 @@ export default {
|
|||
:key="index"
|
||||
:group="group"
|
||||
:parent-group="parentGroup"
|
||||
:action="action"
|
||||
/>
|
||||
<li
|
||||
v-if="hasMoreChildren"
|
||||
|
|
|
@ -30,6 +30,11 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
groupDomId() {
|
||||
|
@ -56,10 +61,12 @@ export default {
|
|||
methods: {
|
||||
onClickRowGroup(e) {
|
||||
const NO_EXPAND_CLS = 'no-expand';
|
||||
if (!(e.target.classList.contains(NO_EXPAND_CLS) ||
|
||||
e.target.parentElement.classList.contains(NO_EXPAND_CLS))) {
|
||||
const targetClasses = e.target.classList;
|
||||
const parentElClasses = e.target.parentElement.classList;
|
||||
|
||||
if (!(targetClasses.contains(NO_EXPAND_CLS) || parentElClasses.contains(NO_EXPAND_CLS))) {
|
||||
if (this.hasChildren) {
|
||||
eventHub.$emit('toggleChildren', this.group);
|
||||
eventHub.$emit(`${this.action}toggleChildren`, this.group);
|
||||
} else {
|
||||
visitUrl(this.group.relativePath);
|
||||
}
|
||||
|
@ -93,7 +100,7 @@ export default {
|
|||
</div>
|
||||
<div
|
||||
:class="{ 'content-loading': group.isChildrenLoading }"
|
||||
class="avatar-container s24 d-none d-sm-block"
|
||||
class="avatar-container s24 d-none d-sm-flex"
|
||||
>
|
||||
<a
|
||||
:href="group.relativePath"
|
||||
|
@ -158,6 +165,7 @@ export default {
|
|||
v-if="group.isOpen && hasChildren"
|
||||
:parent-group="group"
|
||||
:groups="group.children"
|
||||
:action="action"
|
||||
/>
|
||||
</li>
|
||||
</template>
|
||||
|
|
|
@ -1,43 +1,48 @@
|
|||
<script>
|
||||
import tablePagination from '~/vue_shared/components/table_pagination.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import { getParameterByName } from '../../lib/utils/common_utils';
|
||||
import tablePagination from '~/vue_shared/components/table_pagination.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import { getParameterByName } from '../../lib/utils/common_utils';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
tablePagination,
|
||||
export default {
|
||||
components: {
|
||||
tablePagination,
|
||||
},
|
||||
props: {
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
pageInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
searchEmpty: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
searchEmptyMessage: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
pageInfo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
methods: {
|
||||
change(page) {
|
||||
const filterGroupsParam = getParameterByName('filter_groups');
|
||||
const sortParam = getParameterByName('sort');
|
||||
const archivedParam = getParameterByName('archived');
|
||||
eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam, archivedParam);
|
||||
},
|
||||
searchEmpty: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="groups-list-tree-container">
|
||||
<div class="groups-list-tree-container qa-groups-list-tree-container">
|
||||
<div
|
||||
v-if="searchEmpty"
|
||||
class="has-no-search-results"
|
||||
|
@ -47,6 +52,7 @@
|
|||
<group-folder
|
||||
v-if="!searchEmpty"
|
||||
:groups="groups"
|
||||
:action="action"
|
||||
/>
|
||||
<table-pagination
|
||||
v-if="!searchEmpty"
|
||||
|
|
|
@ -21,6 +21,11 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
leaveBtnTitle() {
|
||||
|
@ -32,7 +37,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
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 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 = {
|
||||
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'),
|
||||
EDIT_BTN_TITLE: s__('GroupsTree|Edit group'),
|
||||
GROUP_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups matched your search'),
|
||||
GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|Sorry, no groups or projects matched your search'),
|
||||
GROUP_SEARCH_EMPTY: s__('GroupsTree|No groups matched your search'),
|
||||
GROUP_PROJECT_SEARCH_EMPTY: s__('GroupsTree|No groups or projects matched your search'),
|
||||
};
|
||||
|
||||
export const ITEM_TYPE = {
|
||||
|
@ -17,8 +27,12 @@ export const ITEM_TYPE = {
|
|||
};
|
||||
|
||||
export const GROUP_VISIBILITY_TYPE = {
|
||||
public: __('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.'),
|
||||
public: __(
|
||||
'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.'),
|
||||
};
|
||||
|
||||
|
|
|
@ -4,13 +4,23 @@ import eventHub from './event_hub';
|
|||
import { normalizeHeaders, getParameterByName } from '../lib/utils/common_utils';
|
||||
|
||||
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);
|
||||
this.form = form;
|
||||
this.filterEndpoint = filterEndpoint;
|
||||
this.pagePath = pagePath;
|
||||
this.filterInputField = filterInputField;
|
||||
this.$dropdown = $(dropdownSel);
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
getFilterEndpoint() {
|
||||
|
@ -20,15 +30,16 @@ export default class GroupFilterableList extends FilterableList {
|
|||
getPagePath(queryData) {
|
||||
const params = queryData ? $.param(queryData) : '';
|
||||
const queryString = params ? `?${params}` : '';
|
||||
return `${this.pagePath}${queryString}`;
|
||||
const path = this.pagePath || window.location.pathname;
|
||||
return `${path}${queryString}`;
|
||||
}
|
||||
|
||||
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() {
|
||||
|
@ -53,7 +64,12 @@ export default class GroupFilterableList extends FilterableList {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -65,11 +81,19 @@ export default class GroupFilterableList extends FilterableList {
|
|||
// Get type of option selected from dropdown
|
||||
const currentTargetClassList = e.currentTarget.parentElement.classList;
|
||||
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
|
||||
const sortParam = getParameterByName('sort', isOptionFilterBySort ? e.currentTarget.href : window.location.href);
|
||||
const archivedParam = getParameterByName('archived', isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href);
|
||||
const sortParam = getParameterByName(
|
||||
'sort',
|
||||
isOptionFilterBySort ? e.currentTarget.href : window.location.href,
|
||||
);
|
||||
const archivedParam = getParameterByName(
|
||||
'archived',
|
||||
isOptionFilterByArchivedProjects ? e.currentTarget.href : window.location.href,
|
||||
);
|
||||
|
||||
if (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-menu li.js-filter-sort-order a').removeClass('is-active');
|
||||
} 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');
|
||||
|
@ -98,11 +124,19 @@ export default class GroupFilterableList extends FilterableList {
|
|||
onFilterSuccess(res, queryData) {
|
||||
const currentPath = this.getPagePath(queryData);
|
||||
|
||||
window.history.replaceState({
|
||||
page: currentPath,
|
||||
}, document.title, currentPath);
|
||||
window.history.replaceState(
|
||||
{
|
||||
page: currentPath,
|
||||
},
|
||||
document.title,
|
||||
currentPath,
|
||||
);
|
||||
|
||||
eventHub.$emit('updateGroups', res.data, Object.prototype.hasOwnProperty.call(queryData, this.filterInputField));
|
||||
eventHub.$emit('updatePagination', normalizeHeaders(res.headers));
|
||||
eventHub.$emit(
|
||||
`${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 groupFolderComponent from './components/group_folder.vue';
|
||||
import groupItemComponent from './components/group_item.vue';
|
||||
import { GROUPS_LIST_HOLDER_CLASS, CONTENT_LIST_CLASS } from './constants';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
export default () => {
|
||||
const el = document.getElementById('js-groups-tree');
|
||||
export default (containerId = 'js-groups-tree', endpoint, action = '') => {
|
||||
const containerEl = document.getElementById(containerId);
|
||||
let dataEl;
|
||||
|
||||
// Don't do anything if element doesn't exist (No groups)
|
||||
// This is for when the user enters directly to the page via URL
|
||||
if (!el) {
|
||||
if (!containerEl) {
|
||||
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-item', groupItemComponent);
|
||||
|
||||
|
@ -29,20 +37,26 @@ export default () => {
|
|||
groupsApp,
|
||||
},
|
||||
data() {
|
||||
const { dataset } = this.$options.el;
|
||||
const { dataset } = dataEl || this.$options.el;
|
||||
const hideProjects = dataset.hideProjects === 'true';
|
||||
const service = new GroupsService(endpoint || dataset.endpoint);
|
||||
const store = new GroupsStore(hideProjects);
|
||||
const service = new GroupsService(dataset.endpoint);
|
||||
|
||||
return {
|
||||
action,
|
||||
store,
|
||||
service,
|
||||
hideProjects,
|
||||
loading: true,
|
||||
containerId,
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
const { dataset } = this.$options.el;
|
||||
if (this.action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { dataset } = dataEl || this.$options.el;
|
||||
let groupFilterList = null;
|
||||
const form = document.querySelector(dataset.formSel);
|
||||
const filter = document.querySelector(dataset.filterSel);
|
||||
|
@ -52,10 +66,11 @@ export default () => {
|
|||
form,
|
||||
filter,
|
||||
holder,
|
||||
filterEndpoint: dataset.endpoint,
|
||||
filterEndpoint: endpoint || dataset.endpoint,
|
||||
pagePath: dataset.path,
|
||||
dropdownSel: dataset.dropdownSel,
|
||||
filterInputField: 'filter',
|
||||
action: this.action,
|
||||
};
|
||||
|
||||
groupFilterList = new GroupFilterableList(opts);
|
||||
|
@ -64,9 +79,11 @@ export default () => {
|
|||
render(createElement) {
|
||||
return createElement('groups-app', {
|
||||
props: {
|
||||
action: this.action,
|
||||
store: this.store,
|
||||
service: this.service,
|
||||
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>
|
||||
import $ from 'jquery';
|
||||
import { mapActions } from 'vuex';
|
||||
import { __, sprintf } from '~/locale';
|
||||
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 ListItem from './list_item.vue';
|
||||
|
||||
|
@ -9,6 +11,7 @@ export default {
|
|||
components: {
|
||||
Icon,
|
||||
ListItem,
|
||||
GlModal,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
|
@ -56,6 +59,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emptyStateText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: __('No changes'),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
titleText() {
|
||||
|
@ -68,11 +76,19 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['stageAllChanges', 'unstageAllChanges']),
|
||||
...mapActions(['stageAllChanges', 'unstageAllChanges', 'discardAllChanges']),
|
||||
actionBtnClicked() {
|
||||
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>
|
||||
|
||||
|
@ -81,27 +97,32 @@ export default {
|
|||
class="ide-commit-list-container"
|
||||
>
|
||||
<header
|
||||
class="multi-file-commit-panel-header"
|
||||
class="multi-file-commit-panel-header d-flex mb-0"
|
||||
>
|
||||
<div
|
||||
class="multi-file-commit-panel-header-title"
|
||||
class="d-flex align-items-center flex-fill"
|
||||
>
|
||||
<icon
|
||||
v-once
|
||||
:name="iconName"
|
||||
:size="18"
|
||||
class="append-right-8"
|
||||
/>
|
||||
{{ titleText }}
|
||||
<strong>
|
||||
{{ titleText }}
|
||||
</strong>
|
||||
<div class="d-flex ml-auto">
|
||||
<button
|
||||
v-tooltip
|
||||
v-show="filesLength"
|
||||
:class="{
|
||||
'd-flex': filesLength
|
||||
}"
|
||||
ref="actionBtn"
|
||||
:title="actionBtnText"
|
||||
:aria-label="actionBtnText"
|
||||
:disabled="!filesLength"
|
||||
:class="{
|
||||
'disabled-content': !filesLength
|
||||
}"
|
||||
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-container="body"
|
||||
data-boundary="viewport"
|
||||
|
@ -109,18 +130,32 @@ export default {
|
|||
>
|
||||
<icon
|
||||
:name="actionBtnIcon"
|
||||
:size="12"
|
||||
:size="16"
|
||||
class="ml-auto mr-auto"
|
||||
/>
|
||||
</button>
|
||||
<span
|
||||
<button
|
||||
v-tooltip
|
||||
v-if="!stagedList"
|
||||
:title="__('Discard all changes')"
|
||||
:aria-label="__('Discard all changes')"
|
||||
:disabled="!filesLength"
|
||||
: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 }}
|
||||
</span>
|
||||
<icon
|
||||
:size="16"
|
||||
name="remove-all"
|
||||
class="ml-auto mr-auto"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -143,9 +178,19 @@ export default {
|
|||
</ul>
|
||||
<p
|
||||
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>
|
||||
<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>
|
||||
</template>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { mapActions } from 'vuex';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import FileIcon from '~/vue_shared/components/file_icon.vue';
|
||||
import StageButton from './stage_button.vue';
|
||||
import UnstageButton from './unstage_button.vue';
|
||||
import { viewerTypes } from '../../constants';
|
||||
|
@ -12,6 +13,7 @@ export default {
|
|||
Icon,
|
||||
StageButton,
|
||||
UnstageButton,
|
||||
FileIcon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
|
@ -48,7 +50,7 @@ export default {
|
|||
return `${getCommitIconMap(this.file).icon}${suffix}`;
|
||||
},
|
||||
iconClass() {
|
||||
return `${getCommitIconMap(this.file).class} append-right-8`;
|
||||
return `${getCommitIconMap(this.file).class} ml-auto mr-auto`;
|
||||
},
|
||||
fullKey() {
|
||||
return `${this.keyPrefix}-${this.file.key}`;
|
||||
|
@ -105,17 +107,24 @@ export default {
|
|||
@click="openFileInEditor"
|
||||
>
|
||||
<span class="multi-file-commit-list-file-path d-flex align-items-center">
|
||||
<icon
|
||||
:name="iconName"
|
||||
:size="16"
|
||||
:css-classes="iconClass"
|
||||
<file-icon
|
||||
:file-name="file.name"
|
||||
class="append-right-8"
|
||||
/>{{ file.name }}
|
||||
</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>
|
||||
<component
|
||||
:is="actionComponent"
|
||||
:path="file.path"
|
||||
class="d-flex position-absolute"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import { mapActions } from 'vuex';
|
||||
import { sprintf, __ } from '~/locale';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import GlModal from '~/vue_shared/components/gl_modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
GlModal,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
|
@ -16,8 +20,22 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
modalId() {
|
||||
return `discard-file-${this.path}`;
|
||||
},
|
||||
modalTitle() {
|
||||
return sprintf(
|
||||
__('Discard changes to %{path}?'),
|
||||
{ path: this.path },
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['stageChange', 'discardFileChanges']),
|
||||
showDiscardModal() {
|
||||
$(document.getElementById(this.modalId)).modal('show');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -25,51 +43,50 @@ export default {
|
|||
<template>
|
||||
<div
|
||||
v-once
|
||||
class="multi-file-discard-btn dropdown"
|
||||
class="multi-file-discard-btn d-flex"
|
||||
>
|
||||
<button
|
||||
v-tooltip
|
||||
:aria-label="__('Stage changes')"
|
||||
:title="__('Stage changes')"
|
||||
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-boundary="viewport"
|
||||
data-placement="bottom"
|
||||
@click.stop="stageChange(path)"
|
||||
@click.stop.prevent="stageChange(path)"
|
||||
>
|
||||
<icon
|
||||
:size="12"
|
||||
:size="16"
|
||||
name="mobile-issue-close"
|
||||
class="ml-auto mr-auto"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
v-tooltip
|
||||
:title="__('More actions')"
|
||||
:aria-label="__('Discard changes')"
|
||||
:title="__('Discard changes')"
|
||||
type="button"
|
||||
class="btn btn-blank d-flex align-items-center"
|
||||
class="btn btn-blank align-items-center"
|
||||
data-container="body"
|
||||
data-boundary="viewport"
|
||||
data-placement="bottom"
|
||||
data-toggle="dropdown"
|
||||
data-display="static"
|
||||
@click.stop.prevent="showDiscardModal"
|
||||
>
|
||||
<icon
|
||||
:size="12"
|
||||
name="ellipsis_h"
|
||||
:size="16"
|
||||
name="remove"
|
||||
class="ml-auto mr-auto"
|
||||
/>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<ul>
|
||||
<li>
|
||||
<button
|
||||
type="button"
|
||||
@click.stop="discardFileChanges(path)"
|
||||
>
|
||||
{{ __('Discard changes') }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<gl-modal
|
||||
:id="modalId"
|
||||
:header-title-text="modalTitle"
|
||||
:footer-primary-button-text="__('Discard changes')"
|
||||
footer-primary-button-variant="danger"
|
||||
@submit="discardFileChanges(path)"
|
||||
>
|
||||
{{ __("You will loose all changes you've made to this file. This action cannot be undone.") }}
|
||||
</gl-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -25,22 +25,23 @@ export default {
|
|||
<template>
|
||||
<div
|
||||
v-once
|
||||
class="multi-file-discard-btn"
|
||||
class="multi-file-discard-btn d-flex"
|
||||
>
|
||||
<button
|
||||
v-tooltip
|
||||
:aria-label="__('Unstage changes')"
|
||||
:title="__('Unstage changes')"
|
||||
type="button"
|
||||
class="btn btn-blank d-flex align-items-center"
|
||||
class="btn btn-blank align-items-center"
|
||||
data-container="body"
|
||||
data-boundary="viewport"
|
||||
data-placement="bottom"
|
||||
@click="unstageChange(path)"
|
||||
@click.stop.prevent="unstageChange(path)"
|
||||
>
|
||||
<icon
|
||||
:size="12"
|
||||
name="history"
|
||||
:size="16"
|
||||
name="redo"
|
||||
class="ml-auto mr-auto"
|
||||
/>
|
||||
</button>
|
||||
</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 RightPane from './panes/right.vue';
|
||||
import ErrorMessage from './error_message.vue';
|
||||
import CommitEditorHeader from './commit_sidebar/editor_header.vue';
|
||||
|
||||
const originalStopCallback = Mousetrap.stopCallback;
|
||||
|
||||
|
@ -23,6 +24,7 @@ export default {
|
|||
FindFile,
|
||||
RightPane,
|
||||
ErrorMessage,
|
||||
CommitEditorHeader,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
|
@ -34,7 +36,7 @@ export default {
|
|||
'currentProjectId',
|
||||
'errorMessage',
|
||||
]),
|
||||
...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges']),
|
||||
...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges', 'isCommitModeActive']),
|
||||
},
|
||||
mounted() {
|
||||
window.onbeforeunload = e => this.onBeforeUnload(e);
|
||||
|
@ -78,13 +80,13 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<article class="ide">
|
||||
<article class="ide position-relative d-flex flex-column align-items-stretch">
|
||||
<error-message
|
||||
v-if="errorMessage"
|
||||
:message="errorMessage"
|
||||
/>
|
||||
<div
|
||||
class="ide-view"
|
||||
class="ide-view flex-grow d-flex"
|
||||
>
|
||||
<find-file
|
||||
v-show="fileFindVisible"
|
||||
|
@ -96,7 +98,12 @@ export default {
|
|||
<template
|
||||
v-if="activeFile"
|
||||
>
|
||||
<commit-editor-header
|
||||
v-if="isCommitModeActive"
|
||||
:active-file="activeFile"
|
||||
/>
|
||||
<repo-tabs
|
||||
v-else
|
||||
:active-file="activeFile"
|
||||
:files="openFiles"
|
||||
:viewer="viewer"
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
<script>
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
|
@ -26,6 +30,11 @@ export default {
|
|||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
tooltipTitle() {
|
||||
return this.showLabel ? '' : this.label;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clicked() {
|
||||
this.$emit('click');
|
||||
|
@ -36,7 +45,9 @@ export default {
|
|||
|
||||
<template>
|
||||
<button
|
||||
v-tooltip
|
||||
:aria-label="label"
|
||||
:title="tooltipTitle"
|
||||
type="button"
|
||||
class="btn-blank"
|
||||
@click.stop.prevent="clicked"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import { __ } from '~/locale';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import GlModal from '~/vue_shared/components/gl_modal.vue';
|
||||
import { modalTypes } from '../../constants';
|
||||
|
||||
|
@ -15,6 +16,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapState(['entryModal']),
|
||||
...mapGetters('fileTemplates', ['templateTypes']),
|
||||
entryName: {
|
||||
get() {
|
||||
if (this.entryModal.type === modalTypes.rename) {
|
||||
|
@ -31,7 +33,9 @@ export default {
|
|||
if (this.entryModal.type === modalTypes.tree) {
|
||||
return __('Create new directory');
|
||||
} 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');
|
||||
|
@ -40,11 +44,16 @@ export default {
|
|||
if (this.entryModal.type === modalTypes.tree) {
|
||||
return __('Create directory');
|
||||
} 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');
|
||||
},
|
||||
isCreatingNew() {
|
||||
return this.entryModal.type !== modalTypes.rename;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...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() {
|
||||
this.$refs.fieldName.focus();
|
||||
},
|
||||
|
@ -77,6 +94,7 @@ export default {
|
|||
:header-title-text="modalTitle"
|
||||
:footer-primary-button-text="buttonLabel"
|
||||
footer-primary-button-variant="success"
|
||||
modal-size="lg"
|
||||
@submit="submitForm"
|
||||
@open="focusInput"
|
||||
@closed="closedModal"
|
||||
|
@ -84,16 +102,35 @@ export default {
|
|||
<div
|
||||
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') }}
|
||||
</label>
|
||||
<div class="col-sm-9">
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
ref="fieldName"
|
||||
v-model="entryName"
|
||||
type="text"
|
||||
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>
|
||||
</gl-modal>
|
||||
|
|
|
@ -24,12 +24,6 @@ export default {
|
|||
default: null,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.fileUpload.addEventListener('change', this.openFile);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$refs.fileUpload.removeEventListener('change', this.openFile);
|
||||
},
|
||||
methods: {
|
||||
createFile(target, file, isText) {
|
||||
const { name } = file;
|
||||
|
@ -85,6 +79,8 @@ export default {
|
|||
ref="fileUpload"
|
||||
type="file"
|
||||
class="hidden"
|
||||
multiple
|
||||
@change="openFile"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -95,8 +95,9 @@ export default {
|
|||
:file-list="changedFiles"
|
||||
:action-btn-text="__('Stage all changes')"
|
||||
:active-file-key="activeFileKey"
|
||||
:empty-state-text="__('There are no unstaged changes')"
|
||||
action="stageAllChanges"
|
||||
action-btn-icon="mobile-issue-close"
|
||||
action-btn-icon="stage-all"
|
||||
item-action-component="stage-button"
|
||||
class="is-first"
|
||||
icon-name="unstaged"
|
||||
|
@ -108,8 +109,9 @@ export default {
|
|||
:action-btn-text="__('Unstage all changes')"
|
||||
:staged-list="true"
|
||||
:active-file-key="activeFileKey"
|
||||
:empty-state-text="__('There are no staged changes')"
|
||||
action="unstageAllChanges"
|
||||
action-btn-icon="history"
|
||||
action-btn-icon="unstage-all"
|
||||
item-action-component="unstage-button"
|
||||
icon-name="staged"
|
||||
/>
|
||||
|
|
|
@ -6,12 +6,14 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
|||
import { activityBarViews, viewerTypes } from '../constants';
|
||||
import Editor from '../lib/editor';
|
||||
import ExternalLink from './external_link.vue';
|
||||
import FileTemplatesBar from './file_templates/bar.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContentViewer,
|
||||
DiffViewer,
|
||||
ExternalLink,
|
||||
FileTemplatesBar,
|
||||
},
|
||||
props: {
|
||||
file: {
|
||||
|
@ -34,6 +36,7 @@ export default {
|
|||
'isCommitModeActive',
|
||||
'isReviewModeActive',
|
||||
]),
|
||||
...mapGetters('fileTemplates', ['showFileTemplatesBar']),
|
||||
shouldHideEditor() {
|
||||
return this.file && this.file.binary && !this.file.content;
|
||||
},
|
||||
|
@ -216,7 +219,7 @@ export default {
|
|||
id="ide"
|
||||
class="blob-viewer-container blob-editor-container"
|
||||
>
|
||||
<div class="ide-mode-tabs clearfix" >
|
||||
<div class="ide-mode-tabs clearfix">
|
||||
<ul
|
||||
v-if="!shouldHideEditor && isEditModeActive"
|
||||
class="nav-links float-left"
|
||||
|
@ -249,6 +252,9 @@ export default {
|
|||
:file="file"
|
||||
/>
|
||||
</div>
|
||||
<file-templates-bar
|
||||
v-if="showFileTemplatesBar(file.name)"
|
||||
/>
|
||||
<div
|
||||
v-show="!shouldHideEditor && file.viewMode ==='editor'"
|
||||
ref="editor"
|
||||
|
|
|
@ -95,16 +95,18 @@ export default {
|
|||
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() {
|
||||
if (this.hasPathAtCurrentRoute()) {
|
||||
this.scrollIntoView(true);
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
if (this.file.type === 'blob' && this.file.active) {
|
||||
this.scrollIntoView();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['toggleTreeOpen']),
|
||||
clickFile() {
|
||||
|
|
|
@ -3,7 +3,6 @@ import VueRouter from 'vue-router';
|
|||
import { join as joinPath } from 'path';
|
||||
import flash from '~/flash';
|
||||
import store from './stores';
|
||||
import { activityBarViews } from './constants';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
|
@ -74,98 +73,23 @@ router.beforeEach((to, from, next) => {
|
|||
projectId: to.params.project,
|
||||
})
|
||||
.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 mergeRequestId = to.params.mrid;
|
||||
|
||||
if (branchId) {
|
||||
const basePath = to.params[0] || '';
|
||||
|
||||
store.dispatch('setCurrentBranchId', branchId);
|
||||
|
||||
store.dispatch('getBranchData', {
|
||||
projectId: fullProjectId,
|
||||
store.dispatch('openBranch', {
|
||||
projectId,
|
||||
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 => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { visitUrl } from '~/lib/utils/url_utility';
|
|||
import flash from '~/flash';
|
||||
import * as types from './mutation_types';
|
||||
import FilesDecoratorWorker from './workers/files_decorator_worker';
|
||||
import { stageKeys } from '../constants';
|
||||
|
||||
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, '');
|
||||
|
||||
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));
|
||||
|
||||
dispatch('openPendingTab', {
|
||||
file: state.changedFiles.find(f => f.path === openFile.path),
|
||||
keyPrefix: stageKeys.unstaged,
|
||||
});
|
||||
};
|
||||
|
||||
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 }) => {
|
||||
const entry = state.entries[entryPath || path];
|
||||
|
||||
commit(types.RENAME_ENTRY, { path, name, entryPath });
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,7 +5,7 @@ import service from '../../services';
|
|||
import * as types from '../mutation_types';
|
||||
import router from '../../ide_router';
|
||||
import { setPageTitle } from '../utils';
|
||||
import { viewerTypes } from '../../constants';
|
||||
import { viewerTypes, stageKeys } from '../../constants';
|
||||
|
||||
export const closeFile = ({ commit, state, dispatch }, file) => {
|
||||
const { path } = file;
|
||||
|
@ -54,9 +54,6 @@ export const setFileActive = ({ commit, state, getters, dispatch }, path) => {
|
|||
|
||||
commit(types.SET_FILE_ACTIVE, { path, active: true });
|
||||
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 }) => {
|
||||
|
@ -211,8 +208,9 @@ export const discardFileChanges = ({ dispatch, state, commit, getters }, path) =
|
|||
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 openFile = state.openFiles.find(f => f.path === path);
|
||||
|
||||
commit(types.STAGE_CHANGE, path);
|
||||
commit(types.SET_LAST_COMMIT_MSG, '');
|
||||
|
@ -220,21 +218,39 @@ export const stageChange = ({ commit, state }, path) => {
|
|||
if (stagedFile) {
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
state.openFiles.forEach(f => eventHub.$emit(`editor.update.model.dispose.${f.key}`));
|
||||
|
||||
commit(types.ADD_PENDING_TAB, { file, keyPrefix });
|
||||
|
||||
dispatch('scrollToTab');
|
||||
|
||||
router.push(`/project/${file.projectId}/tree/${state.currentBranchId}/`);
|
||||
|
||||
return true;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { __ } from '../../../locale';
|
||||
import flash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import service from '../../services';
|
||||
import * as types from '../mutation_types';
|
||||
import { activityBarViews } from '../../constants';
|
||||
|
||||
export const getMergeRequestData = (
|
||||
{ commit, dispatch, state },
|
||||
|
@ -104,3 +106,67 @@ export const getMergeRequestVersions = (
|
|||
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,
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|