Merge branch 'master' into 'dm-document-role-maintainer'
# Conflicts: # doc/development/code_review.md
This commit is contained in:
commit
a706b3735e
|
@ -1,22 +1,13 @@
|
|||
env:
|
||||
browser: true
|
||||
es6: true
|
||||
extends:
|
||||
- airbnb-base
|
||||
- prettier
|
||||
- plugin:vue/recommended
|
||||
- '@gitlab'
|
||||
globals:
|
||||
__webpack_public_path__: true
|
||||
gl: false
|
||||
gon: false
|
||||
localStorage: false
|
||||
parserOptions:
|
||||
parser: babel-eslint
|
||||
plugins:
|
||||
- filenames
|
||||
- import
|
||||
- html
|
||||
- promise
|
||||
settings:
|
||||
html/html-extensions:
|
||||
- '.html'
|
||||
|
@ -25,38 +16,12 @@ settings:
|
|||
webpack:
|
||||
config: './config/webpack.config.js'
|
||||
rules:
|
||||
filenames/match-regex:
|
||||
- error
|
||||
- '^[a-z0-9_]+$'
|
||||
import/no-commonjs: error
|
||||
promise/catch-or-return: error
|
||||
no-param-reassign:
|
||||
- error
|
||||
- props: true
|
||||
ignorePropertyModificationsFor:
|
||||
- 'acc' # for reduce accumulators
|
||||
- 'accumulator' # for reduce accumulators
|
||||
- 'el' # for DOM elements
|
||||
- 'element' # for DOM elements
|
||||
- 'state' # for Vuex mutations
|
||||
no-underscore-dangle:
|
||||
- error
|
||||
- allow:
|
||||
- __
|
||||
- _links
|
||||
no-mixed-operators: off
|
||||
vue/html-self-closing:
|
||||
- error
|
||||
- html:
|
||||
void: always
|
||||
normal: never
|
||||
component: always
|
||||
svg: always
|
||||
math: always
|
||||
camelcase:
|
||||
- error
|
||||
- properties: never
|
||||
ignoreDestructuring: true
|
||||
# Disabled for now, to make the airbnb-base 12.1.0 -> 13.1.0 update smoother
|
||||
no-else-return:
|
||||
- error
|
||||
|
|
15
.rubocop.yml
15
.rubocop.yml
|
@ -3,7 +3,9 @@ inherit_gem:
|
|||
- rubocop-default.yml
|
||||
|
||||
inherit_from: .rubocop_todo.yml
|
||||
require: ./rubocop/rubocop
|
||||
require:
|
||||
- ./rubocop/rubocop
|
||||
- rubocop-rspec
|
||||
|
||||
AllCops:
|
||||
TargetRailsVersion: 4.2
|
||||
|
@ -48,12 +50,20 @@ Style/FrozenStringLiteralComment:
|
|||
- 'danger/**/*'
|
||||
- 'db/**/*'
|
||||
- 'ee/**/*'
|
||||
- 'lib/**/*'
|
||||
- 'lib/gitlab/**/*'
|
||||
- 'lib/tasks/**/*'
|
||||
- 'qa/**/*'
|
||||
- 'rubocop/**/*'
|
||||
- 'scripts/**/*'
|
||||
- 'spec/**/*'
|
||||
|
||||
RSpec/FilePath:
|
||||
Exclude:
|
||||
- 'qa/**/*'
|
||||
- 'spec/javascripts/fixtures/*'
|
||||
- 'ee/spec/javascripts/fixtures/*'
|
||||
- 'spec/requests/api/v3/*'
|
||||
|
||||
Naming/FileName:
|
||||
ExpectMatchingDefinition: true
|
||||
Exclude:
|
||||
|
@ -75,6 +85,7 @@ Naming/FileName:
|
|||
- EE
|
||||
- JSON
|
||||
- LDAP
|
||||
- SAML
|
||||
- IO
|
||||
- HMAC
|
||||
- QA
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 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.3 (2018-10-04)
|
||||
|
||||
- No changes.
|
||||
|
|
198
CONTRIBUTING.md
198
CONTRIBUTING.md
|
@ -64,228 +64,56 @@ As of July 2018, all the documentation for contributing to the GitLab project ha
|
|||
|
||||
## Contribute to GitLab
|
||||
|
||||
Thank you for your interest in contributing to GitLab. This guide details how
|
||||
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 in the [Backlog (Accepting merge requests) milestone](#i-want-to-contribute).
|
||||
|
||||
GitLab comes in two flavors, GitLab Community Edition (CE) our free and open
|
||||
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.
|
||||
|
||||
To get an overview of GitLab community membership including those that would be reviewing or merging your contributions, please visit [the community roles page](doc/development/contributing/community_roles.md).
|
||||
|
||||
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/)
|
||||
This [documentation](doc/development/contributing/index.md#contribute-to-gitlab) has been moved.
|
||||
|
||||
## Security vulnerability disclosure
|
||||
|
||||
Please report suspected security vulnerabilities in private to
|
||||
`support@gitlab.com`, also see the
|
||||
[disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/).
|
||||
Please do **NOT** create publicly viewable issues for suspected security
|
||||
vulnerabilities.
|
||||
This [documentation](doc/development/contributing/index.md#security-vulnerability-disclosure) has been moved.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
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, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at conduct@gitlab.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
This [documentation](doc/development/contributing/index.md#code-of-conduct) has been moved.
|
||||
|
||||
## Closing policy for issues and merge requests
|
||||
|
||||
GitLab is a popular open source project and the capacity to deal with issues
|
||||
and merge requests is limited. Out of respect for our volunteers, issues and
|
||||
merge requests not in line with the guidelines listed in this document may be
|
||||
closed without notice.
|
||||
|
||||
Please treat our volunteers with courtesy and respect, it will go a long way
|
||||
towards getting your issue resolved.
|
||||
|
||||
Issues and merge requests should be in English and contain appropriate language
|
||||
for audiences of all ages.
|
||||
|
||||
If a contributor is no longer actively working on a submitted merge request
|
||||
we can decide that the merge request will be finished by one of our
|
||||
[Merge request coaches][team] or close the merge request. We make this decision
|
||||
based on how important the change is for our product vision. If a Merge request
|
||||
coach is going to finish the merge request we assign the
|
||||
~"coach will finish" label.
|
||||
This [documentation](doc/development/contributing/index.md#closing-policy-for-issues-and-merge-requests) has been moved.
|
||||
|
||||
## Helping others
|
||||
|
||||
Please help other GitLab users when you can.
|
||||
The methods people will use to seek help can be found on the [getting help page][getting-help].
|
||||
|
||||
Sign up for the mailing list, answer GitLab questions on StackOverflow or
|
||||
respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
|
||||
the remaining issues on the GitHub issue tracker.
|
||||
This [documentation](doc/development/contributing/index.md#helping-others) has been moved.
|
||||
|
||||
## I want to contribute!
|
||||
|
||||
If you want to contribute to GitLab, [issues in the Backlog (Accepting merge requests)](https://gitlab.com/gitlab-org/gitlab-ce/issues?scope=all&utf8=✓&state=opened&assignee_id=0&milestone_title=Backlog%20(Accepting%20merge%20requests))
|
||||
are a great place to start. Issues with a lower weight (1 or 2) are deemed
|
||||
suitable for beginners. These issues will be of reasonable size and challenge,
|
||||
for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to
|
||||
learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel
|
||||
please consider we favor
|
||||
[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution!
|
||||
This [documentation](doc/development/contributing/index.md#i-want-to-contribute) has been moved.
|
||||
|
||||
## Contribution Flow
|
||||
|
||||
When contributing to GitLab, your merge request is subject to review by merge request maintainers of a particular specialty.
|
||||
|
||||
When you submit code to GitLab, we really want it to get merged, but there will be times when it will not be merged.
|
||||
|
||||
When maintainers are reading through a merge request they may request guidance from other maintainers. If merge request maintainers conclude that the code should not be merged, our reasons will be fully disclosed. If it has been decided that the code quality is not up to GitLab’s standards, the merge request maintainer will refer the author to our docs and code style guides, and provide some guidance.
|
||||
|
||||
Sometimes style guides will be followed but the code will lack structural integrity, or the maintainer will have reservations about the code’s overall quality. When there is a reservation the maintainer will inform the author and provide some guidance. The author may then choose to update the merge request. Once the merge request has been updated and reassigned to the maintainer, they will review the code again. Once the code has been resubmitted any number of times, the maintainer may choose to close the merge request with a summary of why it will not be merged, as well as some guidance. If the merge request is closed the maintainer will be open to discussion as to how to improve the code so it can be approved in the future.
|
||||
|
||||
GitLab will do its best to review community contributions as quickly as possible. Specially appointed developers review community contributions daily. You may take a look at the [team page](https://about.gitlab.com/team/) for the merge request coach who specializes in the type of code you have written and mention them in the merge request. For example, if you have written some JavaScript in your code then you should mention the frontend merge request coach. If your code has multiple disciplines you may mention multiple merge request coaches.
|
||||
|
||||
GitLab receives a lot of community contributions, so if your code has not been reviewed within 4 days of its initial submission feel free to re-mention the appropriate merge request coach.
|
||||
|
||||
When submitting code to GitLab, you may feel that your contribution requires the aid of an external library. If your code includes an external library please provide a link to the library, as well as reasons for including it.
|
||||
|
||||
When your code contains more than 500 changes, any major breaking changes, or an external library, `@mention` a maintainer in the merge request. If you are not sure who to mention, the reviewer will add one early in the merge request process.
|
||||
|
||||
[core team]: https://about.gitlab.com/core-team/
|
||||
[team]: https://about.gitlab.com/team/
|
||||
[getting-help]: https://about.gitlab.com/getting-help/
|
||||
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
|
||||
[accepting-mrs-weight]: https://gitlab.com/gitlab-org/gitlab-ce/issues?assignee_id=0&label_name[]=Accepting%20Merge%20Requests&sort=weight_asc
|
||||
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
|
||||
[ee-tracker]: https://gitlab.com/gitlab-org/gitlab-ee/issues
|
||||
[google-group]: https://groups.google.com/forum/#!forum/gitlabhq
|
||||
[stackoverflow]: https://stackoverflow.com/questions/tagged/gitlab
|
||||
[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
|
||||
[accepting-mrs-ce]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=Accepting+Merge+Requests
|
||||
[accepting-mrs-ee]: https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name=Accepting+Merge+Requests
|
||||
[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
|
||||
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
|
||||
[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
|
||||
[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
|
||||
[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
|
||||
[contributor-covenant]: http://contributor-covenant.org
|
||||
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
|
||||
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
|
||||
[changelog]: doc/development/changelog.md "Generate a changelog entry"
|
||||
[doc-guidelines]: doc/development/documentation/index.md "Documentation guidelines"
|
||||
[js-styleguide]: doc/development/fe_guide/style_guide_js.md "JavaScript styleguide"
|
||||
[scss-styleguide]: doc/development/fe_guide/style_guide_scss.md "SCSS styleguide"
|
||||
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
|
||||
[UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
|
||||
[license-finder-doc]: doc/development/licensing.md
|
||||
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
|
||||
[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
|
||||
|
||||
This [documentation](doc/development/contributing/index.md) has been moved.
|
||||
|
||||
## 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.
|
||||
|
@ -294,17 +122,14 @@ This [documentation](doc/development/contributing/issue_workflow.md) has been mo
|
|||
|
||||
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.
|
||||
|
@ -313,7 +138,6 @@ This [documentation](doc/development/contributing/issue_workflow.md) has been mo
|
|||
|
||||
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||
|
||||
|
||||
### Feature proposals
|
||||
|
||||
This [documentation](doc/development/contributing/issue_workflow.md) has been moved.
|
||||
|
@ -322,32 +146,26 @@ This [documentation](doc/development/contributing/issue_workflow.md) has been mo
|
|||
|
||||
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.
|
||||
|
@ -357,12 +175,10 @@ This [documentation](doc/development/contributing/merge_request_workflow.md) has
|
|||
|
||||
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.
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.125.0
|
||||
0.125.1
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.2.0
|
||||
1.2.1
|
||||
|
|
|
@ -1 +1 @@
|
|||
8.3.3
|
||||
8.4.0
|
6
Gemfile
6
Gemfile
|
@ -83,9 +83,6 @@ gem 'net-ldap'
|
|||
# Only used to compute wiki page slugs
|
||||
gem 'gitlab-gollum-lib', '~> 4.2', require: false
|
||||
|
||||
# Language detection
|
||||
gem 'github-linguist', '~> 5.3.3', require: 'linguist'
|
||||
|
||||
# API
|
||||
gem 'grape', '~> 1.1'
|
||||
gem 'grape-entity', '~> 0.7.1'
|
||||
|
@ -146,6 +143,7 @@ gem 'rouge', '~> 3.1'
|
|||
gem 'truncato', '~> 0.7.9'
|
||||
gem 'bootstrap_form', '~> 2.7.0'
|
||||
gem 'nokogiri', '~> 1.8.2'
|
||||
gem 'escape_utils', '~> 1.1'
|
||||
|
||||
# Calendar rendering
|
||||
gem 'icalendar'
|
||||
|
@ -421,7 +419,7 @@ end
|
|||
|
||||
# Gitaly GRPC client
|
||||
gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly'
|
||||
gem 'grpc', '~> 1.11.0'
|
||||
gem 'grpc', '~> 1.15.0'
|
||||
|
||||
# Locked until https://github.com/google/protobuf/issues/4210 is closed
|
||||
gem 'google-protobuf', '= 3.5.1'
|
||||
|
|
29
Gemfile.lock
29
Gemfile.lock
|
@ -277,11 +277,6 @@ GEM
|
|||
gitaly-proto (0.118.1)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.10)
|
||||
github-linguist (5.3.3)
|
||||
charlock_holmes (~> 0.7.5)
|
||||
escape_utils (~> 1.1.0)
|
||||
mime-types (>= 1.19)
|
||||
rugged (>= 0.25.1)
|
||||
github-markup (1.7.0)
|
||||
gitlab-flowdock-git-hook (1.0.1)
|
||||
flowdock (~> 0.7)
|
||||
|
@ -328,15 +323,14 @@ GEM
|
|||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
google-protobuf (3.5.1)
|
||||
googleapis-common-protos-types (1.0.1)
|
||||
googleapis-common-protos-types (1.0.2)
|
||||
google-protobuf (~> 3.0)
|
||||
googleauth (0.6.2)
|
||||
googleauth (0.6.6)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
logging (~> 2.0)
|
||||
memoist (~> 0.12)
|
||||
multi_json (~> 1.11)
|
||||
os (~> 0.9)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
gpgme (2.0.13)
|
||||
mini_portile2 (~> 2.1)
|
||||
|
@ -360,10 +354,9 @@ GEM
|
|||
railties
|
||||
sprockets-rails
|
||||
graphql (1.8.1)
|
||||
grpc (1.11.0)
|
||||
grpc (1.15.0)
|
||||
google-protobuf (~> 3.1)
|
||||
googleapis-common-protos-types (~> 1.0.0)
|
||||
googleauth (>= 0.5.1, < 0.7)
|
||||
haml (5.0.4)
|
||||
temple (>= 0.8.0)
|
||||
tilt
|
||||
|
@ -465,11 +458,7 @@ GEM
|
|||
xml-simple
|
||||
licensee (8.9.2)
|
||||
rugged (~> 0.24)
|
||||
little-plugger (1.1.4)
|
||||
locale (2.1.2)
|
||||
logging (2.2.2)
|
||||
little-plugger (~> 1.1)
|
||||
multi_json (~> 1.10)
|
||||
lograge (0.10.0)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
|
@ -575,7 +564,7 @@ GEM
|
|||
org-ruby (0.9.12)
|
||||
rubypants (~> 0.2)
|
||||
orm_adapter (0.5.0)
|
||||
os (0.9.6)
|
||||
os (1.0.0)
|
||||
parallel (1.12.1)
|
||||
parser (2.5.1.0)
|
||||
ast (~> 2.4.0)
|
||||
|
@ -843,7 +832,7 @@ GEM
|
|||
sidekiq-cron (0.6.0)
|
||||
rufus-scheduler (>= 3.3.0)
|
||||
sidekiq (>= 4.2.1)
|
||||
signet (0.8.1)
|
||||
signet (0.11.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
|
@ -1006,6 +995,7 @@ DEPENDENCIES
|
|||
ed25519 (~> 1.2)
|
||||
email_reply_trimmer (~> 0.1)
|
||||
email_spec (~> 2.2.0)
|
||||
escape_utils (~> 1.1)
|
||||
factory_bot_rails (~> 4.8.2)
|
||||
faraday (~> 0.12)
|
||||
fast_blank
|
||||
|
@ -1028,7 +1018,6 @@ DEPENDENCIES
|
|||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly-proto (~> 0.118.1)
|
||||
github-linguist (~> 5.3.3)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-gollum-lib (~> 4.2)
|
||||
|
@ -1046,7 +1035,7 @@ DEPENDENCIES
|
|||
grape_logging (~> 1.7)
|
||||
graphiql-rails (~> 1.4.10)
|
||||
graphql (~> 1.8.0)
|
||||
grpc (~> 1.11.0)
|
||||
grpc (~> 1.15.0)
|
||||
haml_lint (~> 0.26.0)
|
||||
hamlit (~> 2.8.8)
|
||||
hangouts-chat (~> 0.0.5)
|
||||
|
@ -1187,4 +1176,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.4
|
||||
1.16.6
|
||||
|
|
|
@ -280,11 +280,6 @@ GEM
|
|||
gitaly-proto (0.118.1)
|
||||
google-protobuf (~> 3.1)
|
||||
grpc (~> 1.10)
|
||||
github-linguist (5.3.3)
|
||||
charlock_holmes (~> 0.7.5)
|
||||
escape_utils (~> 1.1.0)
|
||||
mime-types (>= 1.19)
|
||||
rugged (>= 0.25.1)
|
||||
github-markup (1.7.0)
|
||||
gitlab-flowdock-git-hook (1.0.1)
|
||||
flowdock (~> 0.7)
|
||||
|
@ -331,15 +326,14 @@ GEM
|
|||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
google-protobuf (3.5.1)
|
||||
googleapis-common-protos-types (1.0.1)
|
||||
googleapis-common-protos-types (1.0.2)
|
||||
google-protobuf (~> 3.0)
|
||||
googleauth (0.6.2)
|
||||
googleauth (0.6.6)
|
||||
faraday (~> 0.12)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
logging (~> 2.0)
|
||||
memoist (~> 0.12)
|
||||
multi_json (~> 1.11)
|
||||
os (~> 0.9)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.7)
|
||||
gpgme (2.0.13)
|
||||
mini_portile2 (~> 2.1)
|
||||
|
@ -363,10 +357,9 @@ GEM
|
|||
railties
|
||||
sprockets-rails
|
||||
graphql (1.8.1)
|
||||
grpc (1.11.0)
|
||||
grpc (1.15.0)
|
||||
google-protobuf (~> 3.1)
|
||||
googleapis-common-protos-types (~> 1.0.0)
|
||||
googleauth (>= 0.5.1, < 0.7)
|
||||
haml (5.0.4)
|
||||
temple (>= 0.8.0)
|
||||
tilt
|
||||
|
@ -468,11 +461,7 @@ GEM
|
|||
xml-simple
|
||||
licensee (8.9.2)
|
||||
rugged (~> 0.24)
|
||||
little-plugger (1.1.4)
|
||||
locale (2.1.2)
|
||||
logging (2.2.2)
|
||||
little-plugger (~> 1.1)
|
||||
multi_json (~> 1.10)
|
||||
lograge (0.10.0)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
|
@ -579,7 +568,7 @@ GEM
|
|||
org-ruby (0.9.12)
|
||||
rubypants (~> 0.2)
|
||||
orm_adapter (0.5.0)
|
||||
os (0.9.6)
|
||||
os (1.0.0)
|
||||
parallel (1.12.1)
|
||||
parser (2.5.1.0)
|
||||
ast (~> 2.4.0)
|
||||
|
@ -851,7 +840,7 @@ GEM
|
|||
sidekiq-cron (0.6.0)
|
||||
rufus-scheduler (>= 3.3.0)
|
||||
sidekiq (>= 4.2.1)
|
||||
signet (0.8.1)
|
||||
signet (0.11.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (~> 0.9)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
|
@ -1015,6 +1004,7 @@ DEPENDENCIES
|
|||
ed25519 (~> 1.2)
|
||||
email_reply_trimmer (~> 0.1)
|
||||
email_spec (~> 2.2.0)
|
||||
escape_utils (~> 1.1)
|
||||
factory_bot_rails (~> 4.8.2)
|
||||
faraday (~> 0.12)
|
||||
fast_blank
|
||||
|
@ -1037,7 +1027,6 @@ DEPENDENCIES
|
|||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly-proto (~> 0.118.1)
|
||||
github-linguist (~> 5.3.3)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab-gollum-lib (~> 4.2)
|
||||
|
@ -1055,7 +1044,7 @@ DEPENDENCIES
|
|||
grape_logging (~> 1.7)
|
||||
graphiql-rails (~> 1.4.10)
|
||||
graphql (~> 1.8.0)
|
||||
grpc (~> 1.11.0)
|
||||
grpc (~> 1.15.0)
|
||||
haml_lint (~> 0.26.0)
|
||||
hamlit (~> 2.8.8)
|
||||
hangouts-chat (~> 0.0.5)
|
||||
|
@ -1196,4 +1185,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.4
|
||||
1.16.6
|
||||
|
|
|
@ -208,6 +208,7 @@ the stable branch are:
|
|||
* Fixes or improvements to automated QA scenarios
|
||||
* [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)
|
||||
* Changes that are behind a feature flag and have the ~"feature flag" label
|
||||
|
||||
During the feature freeze all merge requests that are meant to go into the
|
||||
upcoming release should have the correct milestone assigned _and_ the
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 14">
|
||||
<g fill="#d6d7d9">
|
||||
<path d="M8.7 0L5.3.3l3.2 6.8-3.2 6.6 3.5.3L12 6.9z"/>
|
||||
<ellipse cx="1.7" cy="11.1" rx="1.7" ry="1.7"/>
|
||||
<ellipse cx="1.7" cy="5.6" rx="1.7" ry="1.7"/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 312 B |
|
@ -13,11 +13,11 @@ export default () => {
|
|||
if (editBlobForm.length) {
|
||||
const urlRoot = editBlobForm.data('relativeUrlRoot');
|
||||
const assetsPath = editBlobForm.data('assetsPrefix');
|
||||
const blobLanguage = editBlobForm.data('blobLanguage');
|
||||
const filePath = editBlobForm.data('blobFilename')
|
||||
const currentAction = $('.js-file-title').data('currentAction');
|
||||
const projectId = editBlobForm.data('project-id');
|
||||
|
||||
new EditBlob(`${urlRoot}${assetsPath}`, blobLanguage, currentAction, projectId);
|
||||
new EditBlob(`${urlRoot}${assetsPath}`, filePath, currentAction, projectId);
|
||||
new NewCommitForm(editBlobForm);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import axios from '~/lib/utils/axios_utils';
|
|||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import TemplateSelectorMediator from '../blob/file_template_mediator';
|
||||
import getModeByFileExtension from '~/lib/utils/ace_utils';
|
||||
|
||||
export default class EditBlob {
|
||||
constructor(assetsPath, aceMode, currentAction, projectId) {
|
||||
|
@ -14,9 +15,10 @@ export default class EditBlob {
|
|||
this.initFileSelectors(currentAction, projectId);
|
||||
}
|
||||
|
||||
configureAceEditor(aceMode, assetsPath) {
|
||||
configureAceEditor(filePath, assetsPath) {
|
||||
ace.config.set('modePath', `${assetsPath}/ace`);
|
||||
ace.config.loadModule('ace/ext/searchbox');
|
||||
ace.config.loadModule('ace/ext/modelist');
|
||||
|
||||
this.editor = ace.edit('editor');
|
||||
|
||||
|
@ -25,8 +27,8 @@ export default class EditBlob {
|
|||
|
||||
this.editor.focus();
|
||||
|
||||
if (aceMode) {
|
||||
this.editor.getSession().setMode(`ace/mode/${aceMode}`);
|
||||
if (filePath) {
|
||||
this.editor.getSession().setMode(getModeByFileExtension(filePath));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,20 +4,17 @@ import { n__ } from '~/locale';
|
|||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import Tooltip from '~/vue_shared/directives/tooltip';
|
||||
import AccessorUtilities from '../../lib/utils/accessor';
|
||||
import boardList from './board_list.vue';
|
||||
import BoardBlankState from './board_blank_state.vue';
|
||||
import './board_delete';
|
||||
import BoardDelete from './board_delete';
|
||||
import BoardList from './board_list.vue';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
import { getBoardSortableDefaultOptions, sortableEnd } from '../mixins/sortable_default_options';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.Board = Vue.extend({
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
boardList,
|
||||
'board-delete': gl.issueBoards.BoardDelete,
|
||||
BoardBlankState,
|
||||
BoardDelete,
|
||||
BoardList,
|
||||
Icon,
|
||||
},
|
||||
directives: {
|
||||
|
@ -47,8 +44,8 @@ gl.issueBoards.Board = Vue.extend({
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
detailIssue: Store.detail,
|
||||
filter: Store.filter,
|
||||
detailIssue: boardsStore.detail,
|
||||
filter: boardsStore.filter,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -70,20 +67,20 @@ gl.issueBoards.Board = Vue.extend({
|
|||
}
|
||||
},
|
||||
mounted () {
|
||||
this.sortableOptions = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||
this.sortableOptions = getBoardSortableDefaultOptions({
|
||||
disabled: this.disabled,
|
||||
group: 'boards',
|
||||
draggable: '.is-draggable',
|
||||
handle: '.js-board-handle',
|
||||
onEnd: (e) => {
|
||||
gl.issueBoards.onEnd();
|
||||
sortableEnd();
|
||||
|
||||
if (e.newIndex !== undefined && e.oldIndex !== e.newIndex) {
|
||||
const order = this.sortable.toArray();
|
||||
const list = Store.findList('id', parseInt(e.item.dataset.id, 10));
|
||||
const list = boardsStore.findList('id', parseInt(e.item.dataset.id, 10));
|
||||
|
||||
this.$nextTick(() => {
|
||||
Store.moveList(list, order);
|
||||
boardsStore.moveList(list, order);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
/* global ListLabel */
|
||||
import _ from 'underscore';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -19,7 +18,7 @@ export default {
|
|||
this.clearBlankState();
|
||||
|
||||
this.predefinedLabels.forEach((label, i) => {
|
||||
Store.addList({
|
||||
boardsStore.addList({
|
||||
title: label.title,
|
||||
position: i,
|
||||
list_type: 'label',
|
||||
|
@ -30,14 +29,14 @@ export default {
|
|||
});
|
||||
});
|
||||
|
||||
Store.state.lists = _.sortBy(Store.state.lists, 'position');
|
||||
boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
|
||||
|
||||
// Save the labels
|
||||
gl.boardService.generateDefaultLists()
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
data.forEach((listObj) => {
|
||||
const list = Store.findList('title', listObj.title);
|
||||
const list = boardsStore.findList('title', listObj.title);
|
||||
|
||||
list.id = listObj.id;
|
||||
list.label.id = listObj.label.id;
|
||||
|
@ -48,14 +47,14 @@ export default {
|
|||
});
|
||||
})
|
||||
.catch(() => {
|
||||
Store.removeList(undefined, 'label');
|
||||
boardsStore.removeList(undefined, 'label');
|
||||
Cookies.remove('issue_board_welcome_hidden', {
|
||||
path: '',
|
||||
});
|
||||
Store.addBlankState();
|
||||
boardsStore.addBlankState();
|
||||
});
|
||||
},
|
||||
clearBlankState: Store.removeBlankState.bind(Store),
|
||||
clearBlankState: boardsStore.removeBlankState.bind(boardsStore),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
/* eslint-disable vue/require-default-prop */
|
||||
import IssueCardInner from './issue_card_inner.vue';
|
||||
import eventHub from '../eventhub';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
export default {
|
||||
name: 'BoardsIssueCard',
|
||||
|
@ -42,7 +41,7 @@
|
|||
data() {
|
||||
return {
|
||||
showDetail: false,
|
||||
detailIssue: Store.detail,
|
||||
detailIssue: boardsStore.detail,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -63,11 +62,11 @@
|
|||
if (this.showDetail) {
|
||||
this.showDetail = false;
|
||||
|
||||
if (Store.detail.issue && Store.detail.issue.id === this.issue.id) {
|
||||
if (boardsStore.detail.issue && boardsStore.detail.issue.id === this.issue.id) {
|
||||
eventHub.$emit('clearDetailIssue');
|
||||
} else {
|
||||
eventHub.$emit('newDetailIssue', this.issue);
|
||||
Store.detail.list = this.list;
|
||||
boardsStore.detail.list = this.list;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
/* eslint-disable no-alert */
|
||||
|
||||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.BoardDelete = Vue.extend({
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
list: {
|
||||
type: Object,
|
||||
|
@ -14,12 +9,13 @@ gl.issueBoards.BoardDelete = Vue.extend({
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
deleteBoard () {
|
||||
deleteBoard() {
|
||||
$(this.$el).tooltip('hide');
|
||||
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm('Are you sure you want to delete this list?')) {
|
||||
this.list.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -3,8 +3,8 @@ import Sortable from 'sortablejs';
|
|||
import boardNewIssue from './board_new_issue.vue';
|
||||
import boardCard from './board_card.vue';
|
||||
import eventHub from '../eventhub';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
import boardsStore from '../stores/boards_store';
|
||||
import { getBoardSortableDefaultOptions, sortableStart } from '../mixins/sortable_default_options';
|
||||
|
||||
export default {
|
||||
name: 'BoardList',
|
||||
|
@ -46,7 +46,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
scrollOffset: 250,
|
||||
filters: Store.state.filters,
|
||||
filters: boardsStore.state.filters,
|
||||
showCount: false,
|
||||
showIssueForm: false,
|
||||
};
|
||||
|
@ -61,13 +61,14 @@ export default {
|
|||
},
|
||||
issues() {
|
||||
this.$nextTick(() => {
|
||||
if (this.scrollHeight() <= this.listHeight() &&
|
||||
this.list.issuesSize > this.list.issues.length) {
|
||||
if (
|
||||
this.scrollHeight() <= this.listHeight() &&
|
||||
this.list.issuesSize > this.list.issues.length
|
||||
) {
|
||||
this.list.page += 1;
|
||||
this.list.getIssues(false)
|
||||
.catch(() => {
|
||||
// TODO: handle request error
|
||||
});
|
||||
this.list.getIssues(false).catch(() => {
|
||||
// TODO: handle request error
|
||||
});
|
||||
}
|
||||
|
||||
if (this.scrollHeight() > Math.ceil(this.listHeight())) {
|
||||
|
@ -83,7 +84,7 @@ export default {
|
|||
eventHub.$on(`scroll-board-list-${this.list.id}`, this.scrollToTop);
|
||||
},
|
||||
mounted() {
|
||||
const options = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||
const options = getBoardSortableDefaultOptions({
|
||||
scroll: true,
|
||||
disabled: this.disabled,
|
||||
filter: '.board-list-count, .is-disabled',
|
||||
|
@ -108,7 +109,8 @@ export default {
|
|||
// So from there, we can get reference to actual container
|
||||
// and thus the container type to enable Copy or Move
|
||||
if (e.target) {
|
||||
const containerEl = e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
|
||||
const containerEl =
|
||||
e.target.closest('.js-board-list') || e.target.querySelector('.js-board-list');
|
||||
const toBoardType = containerEl.dataset.boardType;
|
||||
const cloneActions = {
|
||||
label: ['milestone', 'assignee'],
|
||||
|
@ -120,8 +122,9 @@ export default {
|
|||
const fromBoardType = this.list.type;
|
||||
// For each list we check if the destination list is
|
||||
// a the list were we should clone the issue
|
||||
const shouldClone = Object.entries(cloneActions).some(entry => (
|
||||
fromBoardType === entry[0] && entry[1].includes(toBoardType)));
|
||||
const shouldClone = Object.entries(cloneActions).some(
|
||||
entry => fromBoardType === entry[0] && entry[1].includes(toBoardType),
|
||||
);
|
||||
|
||||
if (shouldClone) {
|
||||
return 'clone';
|
||||
|
@ -133,28 +136,36 @@ export default {
|
|||
},
|
||||
revertClone: true,
|
||||
},
|
||||
onStart: (e) => {
|
||||
onStart: e => {
|
||||
const card = this.$refs.issue[e.oldIndex];
|
||||
|
||||
card.showDetail = false;
|
||||
Store.moving.list = card.list;
|
||||
Store.moving.issue = Store.moving.list.findIssue(+e.item.dataset.issueId);
|
||||
boardsStore.moving.list = card.list;
|
||||
boardsStore.moving.issue = boardsStore.moving.list.findIssue(+e.item.dataset.issueId);
|
||||
|
||||
gl.issueBoards.onStart();
|
||||
sortableStart();
|
||||
},
|
||||
onAdd: (e) => {
|
||||
gl.issueBoards.BoardsStore
|
||||
.moveIssueToList(Store.moving.list, this.list, Store.moving.issue, e.newIndex);
|
||||
onAdd: e => {
|
||||
boardsStore.moveIssueToList(
|
||||
boardsStore.moving.list,
|
||||
this.list,
|
||||
boardsStore.moving.issue,
|
||||
e.newIndex,
|
||||
);
|
||||
|
||||
this.$nextTick(() => {
|
||||
e.item.remove();
|
||||
});
|
||||
},
|
||||
onUpdate: (e) => {
|
||||
const sortedArray = this.sortable.toArray()
|
||||
.filter(id => id !== '-1');
|
||||
gl.issueBoards.BoardsStore
|
||||
.moveIssueInList(this.list, Store.moving.issue, e.oldIndex, e.newIndex, sortedArray);
|
||||
onUpdate: e => {
|
||||
const sortedArray = this.sortable.toArray().filter(id => id !== '-1');
|
||||
boardsStore.moveIssueInList(
|
||||
this.list,
|
||||
boardsStore.moving.issue,
|
||||
e.oldIndex,
|
||||
e.newIndex,
|
||||
sortedArray,
|
||||
);
|
||||
},
|
||||
onMove(e) {
|
||||
return !e.related.classList.contains('board-list-count');
|
||||
|
@ -192,16 +203,14 @@ export default {
|
|||
|
||||
if (getIssues) {
|
||||
this.list.loadingMore = true;
|
||||
getIssues
|
||||
.then(loadingDone)
|
||||
.catch(loadingDone);
|
||||
getIssues.then(loadingDone).catch(loadingDone);
|
||||
}
|
||||
},
|
||||
toggleForm() {
|
||||
this.showIssueForm = !this.showIssueForm;
|
||||
},
|
||||
onScroll() {
|
||||
if (!this.list.loadingMore && (this.scrollTop() > this.scrollHeight() - this.scrollOffset)) {
|
||||
if (!this.list.loadingMore && this.scrollTop() > this.scrollHeight() - this.scrollOffset) {
|
||||
this.loadNextPage();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -4,8 +4,7 @@ import { Button } from '@gitlab-org/gitlab-ui';
|
|||
import eventHub from '../eventhub';
|
||||
import ProjectSelect from './project_select.vue';
|
||||
import ListIssue from '../models/issue';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
export default {
|
||||
name: 'BoardNewIssue',
|
||||
|
@ -68,8 +67,8 @@ export default {
|
|||
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||
$(this.$refs.submitButton).enable();
|
||||
|
||||
Store.detail.issue = issue;
|
||||
Store.detail.list = this.list;
|
||||
boardsStore.detail.issue = issue;
|
||||
boardsStore.detail.list = this.list;
|
||||
})
|
||||
.catch(() => {
|
||||
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||
|
|
|
@ -14,13 +14,9 @@ import IssuableContext from '../../issuable_context';
|
|||
import LabelsSelect from '../../labels_select';
|
||||
import Subscriptions from '../../sidebar/components/subscriptions/subscriptions.vue';
|
||||
import MilestoneSelect from '../../milestone_select';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.BoardSidebar = Vue.extend({
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
AssigneeTitle,
|
||||
Assignees,
|
||||
|
@ -35,7 +31,7 @@ gl.issueBoards.BoardSidebar = Vue.extend({
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
detail: Store.detail,
|
||||
detail: boardsStore.detail,
|
||||
issue: {},
|
||||
list: {},
|
||||
loadingAssignees: false,
|
||||
|
@ -117,18 +113,18 @@ gl.issueBoards.BoardSidebar = Vue.extend({
|
|||
this.saveAssignees();
|
||||
},
|
||||
removeAssignee (a) {
|
||||
gl.issueBoards.BoardsStore.detail.issue.removeAssignee(a);
|
||||
boardsStore.detail.issue.removeAssignee(a);
|
||||
},
|
||||
addAssignee (a) {
|
||||
gl.issueBoards.BoardsStore.detail.issue.addAssignee(a);
|
||||
boardsStore.detail.issue.addAssignee(a);
|
||||
},
|
||||
removeAllAssignees () {
|
||||
gl.issueBoards.BoardsStore.detail.issue.removeAllAssignees();
|
||||
boardsStore.detail.issue.removeAllAssignees();
|
||||
},
|
||||
saveAssignees () {
|
||||
this.loadingAssignees = true;
|
||||
|
||||
gl.issueBoards.BoardsStore.detail.issue.update()
|
||||
boardsStore.detail.issue.update()
|
||||
.then(() => {
|
||||
this.loadingAssignees = false;
|
||||
})
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import eventHub from '../eventhub';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserAvatarLink,
|
||||
Icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
|
@ -110,7 +111,7 @@
|
|||
filterByLabel(label, e) {
|
||||
if (!this.updateFilters) return;
|
||||
|
||||
const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
|
||||
const filterPath = boardsStore.filter.path.split('&');
|
||||
const labelTitle = encodeURIComponent(label.title);
|
||||
const param = `label_name[]=${labelTitle}`;
|
||||
const labelIndex = filterPath.indexOf(param);
|
||||
|
@ -122,9 +123,9 @@
|
|||
filterPath.splice(labelIndex, 1);
|
||||
}
|
||||
|
||||
gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
|
||||
boardsStore.filter.path = filterPath.join('&');
|
||||
|
||||
Store.updateFiltersUrl();
|
||||
boardsStore.updateFiltersUrl();
|
||||
|
||||
eventHub.$emit('updateTokens');
|
||||
},
|
||||
|
@ -141,11 +142,11 @@
|
|||
<div>
|
||||
<div class="board-card-header">
|
||||
<h4 class="board-card-title">
|
||||
<i
|
||||
<icon
|
||||
v-if="issue.confidential"
|
||||
class="fa fa-eye-slash confidential-icon"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
name="eye-slash"
|
||||
class="confidential-icon"
|
||||
/>
|
||||
<a
|
||||
:href="issue.path"
|
||||
:title="issue.title"
|
||||
|
|
|
@ -5,6 +5,7 @@ import ListsDropdown from './lists_dropdown.vue';
|
|||
import { pluralize } from '../../../lib/utils/text_utility';
|
||||
import ModalStore from '../../stores/modal_store';
|
||||
import modalMixin from '../../mixins/modal_mixins';
|
||||
import boardsStore from '../../stores/boards_store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -14,7 +15,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
state: gl.issueBoards.BoardsStore.state,
|
||||
state: boardsStore.state,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import bp from '../../../breakpoints';
|
||||
import ModalStore from '../../stores/modal_store';
|
||||
import IssueCardInner from '../issue_card_inner.vue';
|
||||
|
@ -6,6 +7,7 @@
|
|||
export default {
|
||||
components: {
|
||||
IssueCardInner,
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
issueLinkBase: {
|
||||
|
@ -147,13 +149,13 @@
|
|||
:issue="issue"
|
||||
:issue-link-base="issueLinkBase"
|
||||
:root-path="rootPath"/>
|
||||
<span
|
||||
<icon
|
||||
v-if="issue.selected"
|
||||
:aria-label="'Issue #' + issue.id + ' selected'"
|
||||
name="mobile-issue-close"
|
||||
aria-checked="true"
|
||||
class="issue-card-selected text-center">
|
||||
<i class="fa fa-check"></i>
|
||||
</span>
|
||||
class="issue-card-selected text-center"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
<script>
|
||||
import { Link } from '@gitlab-org/gitlab-ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import ModalStore from '../../stores/modal_store';
|
||||
import boardsStore from '../../stores/boards_store';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
'gl-link': Link,
|
||||
Icon,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
state: gl.issueBoards.BoardsStore.state,
|
||||
state: boardsStore.state,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -34,7 +37,9 @@ export default {
|
|||
class="dropdown-label-box">
|
||||
</span>
|
||||
{{ selected.title }}
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
<icon
|
||||
name="chevron-down"
|
||||
/>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
|
||||
<ul>
|
||||
|
|
|
@ -4,16 +4,12 @@ import $ from 'jquery';
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
import _ from 'underscore';
|
||||
import CreateLabelDropdown from '../../create_label';
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
$(document).off('created.label').on('created.label', (e, label) => {
|
||||
Store.new({
|
||||
boardsStore.new({
|
||||
title: label.title,
|
||||
position: Store.state.lists.length - 2,
|
||||
position: boardsStore.state.lists.length - 2,
|
||||
list_type: 'label',
|
||||
label: {
|
||||
id: label.id,
|
||||
|
@ -23,7 +19,7 @@ $(document).off('created.label').on('created.label', (e, label) => {
|
|||
});
|
||||
});
|
||||
|
||||
gl.issueBoards.newListDropdownInit = () => {
|
||||
export default function initNewListDropdown() {
|
||||
$('.js-new-board-list').each(function () {
|
||||
const $this = $(this);
|
||||
new CreateLabelDropdown($this.closest('.dropdown').find('.dropdown-new-label'), $this.data('namespacePath'), $this.data('projectPath'));
|
||||
|
@ -36,7 +32,7 @@ gl.issueBoards.newListDropdownInit = () => {
|
|||
});
|
||||
},
|
||||
renderRow (label) {
|
||||
const active = Store.findList('title', label.title);
|
||||
const active = boardsStore.findList('title', label.title);
|
||||
const $li = $('<li />');
|
||||
const $a = $('<a />', {
|
||||
class: (active ? `is-active js-board-list-${active.id}` : ''),
|
||||
|
@ -62,10 +58,10 @@ gl.issueBoards.newListDropdownInit = () => {
|
|||
const label = options.selectedObj;
|
||||
e.preventDefault();
|
||||
|
||||
if (!Store.findList('title', label.title)) {
|
||||
Store.new({
|
||||
if (!boardsStore.findList('title', label.title)) {
|
||||
boardsStore.new({
|
||||
title: label.title,
|
||||
position: Store.state.lists.length - 2,
|
||||
position: boardsStore.state.lists.length - 2,
|
||||
list_type: 'label',
|
||||
label: {
|
||||
id: label.id,
|
||||
|
@ -74,9 +70,9 @@ gl.issueBoards.newListDropdownInit = () => {
|
|||
},
|
||||
});
|
||||
|
||||
Store.state.lists = _.sortBy(Store.state.lists, 'position');
|
||||
boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import _ from 'underscore';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import eventHub from '../eventhub';
|
||||
import Api from '../../api';
|
||||
|
||||
export default {
|
||||
name: 'BoardProjectSelect',
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
groupId: {
|
||||
type: Number,
|
||||
|
@ -78,11 +82,9 @@ export default {
|
|||
aria-expanded="false"
|
||||
>
|
||||
{{ selectedProjectName }}
|
||||
<i
|
||||
class="fa fa-chevron-down"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<icon
|
||||
name="chevron-down"
|
||||
/>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-full-width">
|
||||
<div class="dropdown-title">
|
||||
|
@ -92,12 +94,11 @@ export default {
|
|||
type="button"
|
||||
class="dropdown-title-button dropdown-menu-close"
|
||||
>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
<icon
|
||||
name="merge-request-close-m"
|
||||
data-hidden="true"
|
||||
class="fa fa-times dropdown-menu-close-icon"
|
||||
>
|
||||
</i>
|
||||
class="dropdown-menu-close-icon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown-input">
|
||||
|
@ -106,12 +107,11 @@ export default {
|
|||
type="search"
|
||||
placeholder="Search projects"
|
||||
/>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
<icon
|
||||
name="search"
|
||||
class="dropdown-input-search"
|
||||
data-hidden="true"
|
||||
class="fa fa-search dropdown-input-search"
|
||||
>
|
||||
</i>
|
||||
/>
|
||||
</div>
|
||||
<div class="dropdown-content"></div>
|
||||
<div class="dropdown-loading">
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
import Vue from 'vue';
|
||||
import Flash from '../../../flash';
|
||||
import { __ } from '../../../locale';
|
||||
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
import boardsStore from '../../stores/boards_store';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
|
@ -49,7 +48,7 @@
|
|||
list.removeIssue(issue);
|
||||
});
|
||||
|
||||
Store.detail.issue = {};
|
||||
boardsStore.detail.issue = {};
|
||||
},
|
||||
/**
|
||||
* Build the default patch request.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import FilteredSearchContainer from '../filtered_search/container';
|
||||
import FilteredSearchManager from '../filtered_search/filtered_search_manager';
|
||||
import boardsStore from './stores/boards_store';
|
||||
|
||||
export default class FilteredSearchBoards extends FilteredSearchManager {
|
||||
constructor(store, updateUrl = false, cantEdit = []) {
|
||||
|
@ -23,7 +24,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
|
|||
this.store.path = path.substr(1);
|
||||
|
||||
if (this.updateUrl) {
|
||||
gl.issueBoards.BoardsStore.updateFiltersUrl();
|
||||
boardsStore.updateFiltersUrl();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,24 +14,22 @@ import './models/issue';
|
|||
import './models/list';
|
||||
import './models/milestone';
|
||||
import './models/project';
|
||||
import './stores/boards_store';
|
||||
import boardsStore from './stores/boards_store';
|
||||
import ModalStore from './stores/modal_store';
|
||||
import BoardService from './services/board_service';
|
||||
import modalMixin from './mixins/modal_mixins';
|
||||
import './mixins/sortable_default_options';
|
||||
import './filters/due_date_filters';
|
||||
import './components/board';
|
||||
import './components/board_sidebar';
|
||||
import './components/new_list_dropdown';
|
||||
import Board from './components/board';
|
||||
import BoardSidebar from './components/board_sidebar';
|
||||
import initNewListDropdown from './components/new_list_dropdown';
|
||||
import BoardAddIssuesModal from './components/modal/index.vue';
|
||||
import '~/vue_shared/vue_resource_interceptor';
|
||||
import { NavigationType } from '~/lib/utils/common_utils';
|
||||
|
||||
let issueBoardsApp;
|
||||
|
||||
export default () => {
|
||||
const $boardApp = document.getElementById('board-app');
|
||||
const Store = gl.issueBoards.BoardsStore;
|
||||
|
||||
window.gl = window.gl || {};
|
||||
|
||||
// check for browser back and trigger a hard reload to circumvent browser caching.
|
||||
window.addEventListener('pageshow', (event) => {
|
||||
|
@ -43,25 +41,21 @@ export default () => {
|
|||
}
|
||||
});
|
||||
|
||||
if (gl.IssueBoardsApp) {
|
||||
gl.IssueBoardsApp.$destroy(true);
|
||||
if (issueBoardsApp) {
|
||||
issueBoardsApp.$destroy(true);
|
||||
}
|
||||
|
||||
Store.create();
|
||||
boardsStore.create();
|
||||
|
||||
// hack to allow sidebar scripts like milestone_select manipulate the BoardsStore
|
||||
gl.issueBoards.boardStoreIssueSet = (...args) => Vue.set(Store.detail.issue, ...args);
|
||||
gl.issueBoards.boardStoreIssueDelete = (...args) => Vue.delete(Store.detail.issue, ...args);
|
||||
|
||||
gl.IssueBoardsApp = new Vue({
|
||||
issueBoardsApp = new Vue({
|
||||
el: $boardApp,
|
||||
components: {
|
||||
board: gl.issueBoards.Board,
|
||||
'board-sidebar': gl.issueBoards.BoardSidebar,
|
||||
Board,
|
||||
BoardSidebar,
|
||||
BoardAddIssuesModal,
|
||||
},
|
||||
data: {
|
||||
state: Store.state,
|
||||
state: boardsStore.state,
|
||||
loading: true,
|
||||
boardsEndpoint: $boardApp.dataset.boardsEndpoint,
|
||||
listsEndpoint: $boardApp.dataset.listsEndpoint,
|
||||
|
@ -70,7 +64,7 @@ export default () => {
|
|||
issueLinkBase: $boardApp.dataset.issueLinkBase,
|
||||
rootPath: $boardApp.dataset.rootPath,
|
||||
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
|
||||
detailIssue: Store.detail,
|
||||
detailIssue: boardsStore.detail,
|
||||
defaultAvatar: $boardApp.dataset.defaultAvatar,
|
||||
},
|
||||
computed: {
|
||||
|
@ -85,7 +79,7 @@ export default () => {
|
|||
bulkUpdatePath: this.bulkUpdatePath,
|
||||
boardId: this.boardId,
|
||||
});
|
||||
Store.rootPath = this.boardsEndpoint;
|
||||
boardsStore.rootPath = this.boardsEndpoint;
|
||||
|
||||
eventHub.$on('updateTokens', this.updateTokens);
|
||||
eventHub.$on('newDetailIssue', this.updateDetailIssue);
|
||||
|
@ -99,16 +93,16 @@ export default () => {
|
|||
sidebarEventHub.$off('toggleSubscription', this.toggleSubscription);
|
||||
},
|
||||
mounted() {
|
||||
this.filterManager = new FilteredSearchBoards(Store.filter, true, Store.cantEdit);
|
||||
this.filterManager = new FilteredSearchBoards(boardsStore.filter, true, boardsStore.cantEdit);
|
||||
this.filterManager.setup();
|
||||
|
||||
Store.disabled = this.disabled;
|
||||
boardsStore.disabled = this.disabled;
|
||||
gl.boardService
|
||||
.all()
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
data.forEach(board => {
|
||||
const list = Store.addList(board, this.defaultAvatar);
|
||||
const list = boardsStore.addList(board, this.defaultAvatar);
|
||||
|
||||
if (list.type === 'closed') {
|
||||
list.position = Infinity;
|
||||
|
@ -119,7 +113,7 @@ export default () => {
|
|||
|
||||
this.state.lists = _.sortBy(this.state.lists, 'position');
|
||||
|
||||
Store.addBlankState();
|
||||
boardsStore.addBlankState();
|
||||
this.loading = false;
|
||||
})
|
||||
.catch(() => {
|
||||
|
@ -148,13 +142,13 @@ export default () => {
|
|||
});
|
||||
}
|
||||
|
||||
Store.detail.issue = newIssue;
|
||||
boardsStore.detail.issue = newIssue;
|
||||
},
|
||||
clearDetailIssue() {
|
||||
Store.detail.issue = {};
|
||||
boardsStore.detail.issue = {};
|
||||
},
|
||||
toggleSubscription(id) {
|
||||
const { issue } = Store.detail;
|
||||
const { issue } = boardsStore.detail;
|
||||
if (issue.id === id && issue.toggleSubscriptionEndpoint) {
|
||||
issue.setFetchingState('subscriptions', true);
|
||||
BoardService.toggleIssueSubscription(issue.toggleSubscriptionEndpoint)
|
||||
|
@ -173,26 +167,28 @@ export default () => {
|
|||
},
|
||||
});
|
||||
|
||||
gl.IssueBoardsSearch = new Vue({
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: document.getElementById('js-add-list'),
|
||||
data: {
|
||||
filters: Store.state.filters,
|
||||
filters: boardsStore.state.filters,
|
||||
},
|
||||
mounted() {
|
||||
gl.issueBoards.newListDropdownInit();
|
||||
initNewListDropdown();
|
||||
},
|
||||
});
|
||||
|
||||
const issueBoardsModal = document.getElementById('js-add-issues-btn');
|
||||
|
||||
if (issueBoardsModal) {
|
||||
gl.IssueBoardsModalAddBtn = new Vue({
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: issueBoardsModal,
|
||||
mixins: [modalMixin],
|
||||
data() {
|
||||
return {
|
||||
modal: ModalStore.store,
|
||||
store: Store.state,
|
||||
store: boardsStore.state,
|
||||
canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
|
||||
};
|
||||
},
|
||||
|
|
|
@ -3,32 +3,29 @@
|
|||
import $ from 'jquery';
|
||||
import sortableConfig from '../../sortable/sortable_config';
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.onStart = () => {
|
||||
export function sortableStart() {
|
||||
$('.has-tooltip').tooltip('hide')
|
||||
.tooltip('disable');
|
||||
document.body.classList.add('is-dragging');
|
||||
};
|
||||
}
|
||||
|
||||
gl.issueBoards.onEnd = () => {
|
||||
export function sortableEnd() {
|
||||
$('.has-tooltip').tooltip('enable');
|
||||
document.body.classList.remove('is-dragging');
|
||||
};
|
||||
}
|
||||
|
||||
gl.issueBoards.touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
|
||||
export function getBoardSortableDefaultOptions(obj) {
|
||||
const touchEnabled = ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch;
|
||||
|
||||
gl.issueBoards.getBoardSortableDefaultOptions = (obj) => {
|
||||
const defaultSortOptions = Object.assign({}, sortableConfig, {
|
||||
filter: '.board-delete, .btn',
|
||||
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
||||
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
||||
delay: touchEnabled ? 100 : 0,
|
||||
scrollSensitivity: touchEnabled ? 60 : 100,
|
||||
scrollSpeed: 20,
|
||||
onStart: gl.issueBoards.onStart,
|
||||
onEnd: gl.issueBoards.onEnd,
|
||||
onStart: sortableStart,
|
||||
onEnd: sortableEnd,
|
||||
});
|
||||
|
||||
Object.keys(obj).forEach((key) => { defaultSortOptions[key] = obj[key]; });
|
||||
return defaultSortOptions;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
import Vue from 'vue';
|
||||
import '~/vue_shared/models/label';
|
||||
import IssueProject from './project';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
class ListIssue {
|
||||
constructor (obj, defaultAvatar) {
|
||||
|
@ -86,7 +87,7 @@ class ListIssue {
|
|||
}
|
||||
|
||||
getLists () {
|
||||
return gl.issueBoards.BoardsStore.state.lists.filter(list => list.findIssue(this.id));
|
||||
return boardsStore.state.lists.filter(list => list.findIssue(this.id));
|
||||
}
|
||||
|
||||
updateData(newData) {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { __ } from '~/locale';
|
|||
import ListLabel from '~/vue_shared/models/label';
|
||||
import ListAssignee from '~/vue_shared/models/assignee';
|
||||
import { urlParamsToObject } from '~/lib/utils/common_utils';
|
||||
import boardsStore from '../stores/boards_store';
|
||||
|
||||
const PER_PAGE = 20;
|
||||
|
||||
|
@ -89,9 +90,9 @@ class List {
|
|||
}
|
||||
|
||||
destroy() {
|
||||
const index = gl.issueBoards.BoardsStore.state.lists.indexOf(this);
|
||||
gl.issueBoards.BoardsStore.state.lists.splice(index, 1);
|
||||
gl.issueBoards.BoardsStore.updateNewListDropdown(this.id);
|
||||
const index = boardsStore.state.lists.indexOf(this);
|
||||
boardsStore.state.lists.splice(index, 1);
|
||||
boardsStore.updateNewListDropdown(this.id);
|
||||
|
||||
gl.boardService.destroyList(this.id).catch(() => {
|
||||
// TODO: handle request error
|
||||
|
@ -116,7 +117,7 @@ class List {
|
|||
|
||||
getIssues(emptyIssues = true) {
|
||||
const data = {
|
||||
...urlParamsToObject(gl.issueBoards.BoardsStore.filter.path),
|
||||
...urlParamsToObject(boardsStore.filter.path),
|
||||
page: this.page,
|
||||
};
|
||||
|
||||
|
|
|
@ -3,13 +3,11 @@
|
|||
|
||||
import $ from 'jquery';
|
||||
import _ from 'underscore';
|
||||
import Vue from 'vue';
|
||||
import Cookies from 'js-cookie';
|
||||
import { getUrlParamsArray } from '~/lib/utils/common_utils';
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.issueBoards = window.gl.issueBoards || {};
|
||||
|
||||
gl.issueBoards.BoardsStore = {
|
||||
const boardsStore = {
|
||||
disabled: false,
|
||||
filter: {
|
||||
path: '',
|
||||
|
@ -167,3 +165,16 @@ gl.issueBoards.BoardsStore = {
|
|||
window.history.pushState(null, null, `?${this.filter.path}`);
|
||||
}
|
||||
};
|
||||
|
||||
// hacks added in order to allow milestone_select to function properly
|
||||
// TODO: remove these
|
||||
|
||||
export function boardStoreIssueSet(...args) {
|
||||
Vue.set(boardsStore.detail.issue, ...args);
|
||||
}
|
||||
|
||||
export function boardStoreIssueDelete(...args) {
|
||||
Vue.delete(boardsStore.detail.issue, ...args);
|
||||
}
|
||||
|
||||
export default boardsStore;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Visibility from 'visibilityjs';
|
||||
import Vue from 'vue';
|
||||
import PersistentUserCallout from '../persistent_user_callout';
|
||||
import initDismissableCallout from '~/dismissable_callout';
|
||||
import { s__, sprintf } from '../locale';
|
||||
import Flash from '../flash';
|
||||
import Poll from '../lib/utils/poll';
|
||||
|
@ -62,7 +62,7 @@ export default class Clusters {
|
|||
this.showTokenButton = document.querySelector('.js-show-cluster-token');
|
||||
this.tokenField = document.querySelector('.js-cluster-token');
|
||||
|
||||
Clusters.initDismissableCallout();
|
||||
initDismissableCallout('.js-cluster-security-warning');
|
||||
initSettingsPanels();
|
||||
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
|
||||
this.initApplications();
|
||||
|
@ -105,12 +105,6 @@ export default class Clusters {
|
|||
});
|
||||
}
|
||||
|
||||
static initDismissableCallout() {
|
||||
const callout = document.querySelector('.js-cluster-security-warning');
|
||||
|
||||
if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
|
||||
}
|
||||
|
||||
addListeners() {
|
||||
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
|
||||
eventHub.$on('installApplication', this.installApplication);
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import createFlash from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
import setupToggleButtons from '~/toggle_buttons';
|
||||
import PersistentUserCallout from '../persistent_user_callout';
|
||||
import initDismissableCallout from '~/dismissable_callout';
|
||||
|
||||
import ClustersService from './services/clusters_service';
|
||||
|
||||
export default () => {
|
||||
const clusterList = document.querySelector('.js-clusters-list');
|
||||
|
||||
const callout = document.querySelector('.gcp-signup-offer');
|
||||
if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new
|
||||
initDismissableCallout('.gcp-signup-offer');
|
||||
|
||||
// The empty state won't have a clusterList
|
||||
if (clusterList) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import helmInstallIllustration from '@gitlab-org/gitlab-svgs/illustrations/kubernetes-installation.svg';
|
||||
import helmInstallIllustration from '@gitlab-org/gitlab-svgs/dist/illustrations/kubernetes-installation.svg';
|
||||
import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png';
|
||||
import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
|
||||
import helmLogo from 'images/cluster_app_logos/helm.png';
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
<script>
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
documentationLink: {
|
||||
type: String,
|
||||
|
@ -28,10 +32,9 @@
|
|||
type="button"
|
||||
@click="dismissOverviewDialog"
|
||||
>
|
||||
<i
|
||||
class="fa fa-times"
|
||||
aria-hidden="true">
|
||||
</i>
|
||||
<icon
|
||||
name="close"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="svg-container"
|
||||
|
|
|
@ -115,7 +115,7 @@ export default {
|
|||
<span>
|
||||
{{ selectedVersionName }}
|
||||
</span>
|
||||
<Icon
|
||||
<icon
|
||||
:size="12"
|
||||
name="angle-down"
|
||||
class="position-absolute"
|
||||
|
|
|
@ -20,6 +20,11 @@ export default {
|
|||
Tooltip,
|
||||
},
|
||||
props: {
|
||||
discussionPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
diffFile: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -65,8 +70,7 @@ export default {
|
|||
if (this.diffFile.submodule) {
|
||||
return this.diffFile.submoduleTreeUrl || this.diffFile.submoduleLink;
|
||||
}
|
||||
|
||||
return `#${this.diffFile.fileHash}`;
|
||||
return this.discussionPath;
|
||||
},
|
||||
filePath() {
|
||||
if (this.diffFile.submodule) {
|
||||
|
@ -152,7 +156,7 @@ export default {
|
|||
v-once
|
||||
ref="titleWrapper"
|
||||
:href="titleLink"
|
||||
class="append-right-4"
|
||||
class="append-right-4 js-title-wrapper"
|
||||
>
|
||||
<file-icon
|
||||
:file-name="filePath"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Cookies from 'js-cookie';
|
||||
import { getParameterValues } from '~/lib/utils/url_utility';
|
||||
import bp from '~/breakpoints';
|
||||
import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants';
|
||||
|
||||
const viewTypeFromQueryString = getParameterValues('view')[0];
|
||||
|
@ -20,6 +21,7 @@ export default () => ({
|
|||
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
|
||||
tree: [],
|
||||
treeEntries: {},
|
||||
showTreeList: storedTreeShow === null ? true : storedTreeShow === 'true',
|
||||
showTreeList:
|
||||
storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : storedTreeShow === 'true',
|
||||
currentDiffFileId: '',
|
||||
});
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import DirtySubmitForm from './dirty_submit_form';
|
||||
|
||||
class DirtySubmitCollection {
|
||||
constructor(forms) {
|
||||
this.forms = forms;
|
||||
|
||||
this.dirtySubmits = [];
|
||||
|
||||
this.forms.forEach(form => this.dirtySubmits.push(new DirtySubmitForm(form)));
|
||||
}
|
||||
}
|
||||
|
||||
export default DirtySubmitCollection;
|
|
@ -0,0 +1,9 @@
|
|||
import DirtySubmitCollection from './dirty_submit_collection';
|
||||
import DirtySubmitForm from './dirty_submit_form';
|
||||
|
||||
export default function dirtySubmitFactory(formOrForms) {
|
||||
const isCollection = formOrForms instanceof NodeList || formOrForms instanceof Array;
|
||||
const DirtySubmitClass = isCollection ? DirtySubmitCollection : DirtySubmitForm;
|
||||
|
||||
return new DirtySubmitClass(formOrForms);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
import _ from 'underscore';
|
||||
|
||||
class DirtySubmitForm {
|
||||
constructor(form) {
|
||||
this.form = form;
|
||||
this.dirtyInputs = [];
|
||||
this.isDisabled = true;
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.inputs = this.form.querySelectorAll('input, textarea, select');
|
||||
this.submits = this.form.querySelectorAll('input[type=submit], button[type=submit]');
|
||||
|
||||
this.inputs.forEach(DirtySubmitForm.initInput);
|
||||
this.toggleSubmission();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
registerListeners() {
|
||||
const throttledUpdateDirtyInput = _.throttle(
|
||||
event => this.updateDirtyInput(event),
|
||||
DirtySubmitForm.THROTTLE_DURATION,
|
||||
);
|
||||
this.form.addEventListener('input', throttledUpdateDirtyInput);
|
||||
this.form.addEventListener('submit', event => this.formSubmit(event));
|
||||
}
|
||||
|
||||
updateDirtyInput(event) {
|
||||
const input = event.target;
|
||||
|
||||
if (!input.dataset.dirtySubmitOriginalValue) return;
|
||||
|
||||
this.updateDirtyInputs(input);
|
||||
this.toggleSubmission();
|
||||
}
|
||||
|
||||
updateDirtyInputs(input) {
|
||||
const { name } = input;
|
||||
const isDirty =
|
||||
input.dataset.dirtySubmitOriginalValue !== DirtySubmitForm.inputCurrentValue(input);
|
||||
const indexOfInputName = this.dirtyInputs.indexOf(name);
|
||||
const isExisting = indexOfInputName !== -1;
|
||||
|
||||
if (isDirty && !isExisting) this.dirtyInputs.push(name);
|
||||
if (!isDirty && isExisting) this.dirtyInputs.splice(indexOfInputName, 1);
|
||||
}
|
||||
|
||||
toggleSubmission() {
|
||||
this.isDisabled = this.dirtyInputs.length === 0;
|
||||
this.submits.forEach(element => {
|
||||
element.disabled = this.isDisabled;
|
||||
});
|
||||
}
|
||||
|
||||
formSubmit(event) {
|
||||
if (this.isDisabled) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
return !this.isDisabled;
|
||||
}
|
||||
|
||||
static initInput(element) {
|
||||
element.dataset.dirtySubmitOriginalValue = DirtySubmitForm.inputCurrentValue(element);
|
||||
}
|
||||
|
||||
static isInputCheckable(input) {
|
||||
return input.type === 'checkbox' || input.type === 'radio';
|
||||
}
|
||||
|
||||
static inputCurrentValue(input) {
|
||||
return DirtySubmitForm.isInputCheckable(input) ? input.checked.toString() : input.value;
|
||||
}
|
||||
}
|
||||
|
||||
DirtySubmitForm.THROTTLE_DURATION = 500;
|
||||
|
||||
export default DirtySubmitForm;
|
|
@ -0,0 +1,27 @@
|
|||
import $ from 'jquery';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { __ } from '~/locale';
|
||||
import Flash from '~/flash';
|
||||
|
||||
export default function initDismissableCallout(alertSelector) {
|
||||
const alertEl = document.querySelector(alertSelector);
|
||||
if (!alertEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const closeButtonEl = alertEl.getElementsByClassName('close')[0];
|
||||
const { dismissEndpoint, featureId } = closeButtonEl.dataset;
|
||||
|
||||
closeButtonEl.addEventListener('click', () => {
|
||||
axios
|
||||
.post(dismissEndpoint, {
|
||||
feature_name: featureId,
|
||||
})
|
||||
.then(() => {
|
||||
$(alertEl).alert('close');
|
||||
})
|
||||
.catch(() => {
|
||||
Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -5,6 +5,7 @@ import { __ } from '~/locale';
|
|||
import axios from './lib/utils/axios_utils';
|
||||
import { timeFor } from './lib/utils/datetime_utility';
|
||||
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
|
||||
import boardsStore from './boards/stores/boards_store';
|
||||
|
||||
class DueDateSelect {
|
||||
constructor({ $dropdown, $loading } = {}) {
|
||||
|
@ -58,7 +59,7 @@ class DueDateSelect {
|
|||
$dueDateInput.val(calendar.toString(dateText));
|
||||
|
||||
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
|
||||
gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
|
||||
boardsStore.detail.issue.dueDate = $dueDateInput.val();
|
||||
this.updateIssueBoardIssue();
|
||||
} else {
|
||||
this.saveDueDate(true);
|
||||
|
@ -79,7 +80,7 @@ class DueDateSelect {
|
|||
calendar.setDate(null);
|
||||
|
||||
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
|
||||
gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
|
||||
boardsStore.detail.issue.dueDate = '';
|
||||
this.updateIssueBoardIssue();
|
||||
} else {
|
||||
$(`input[name='${this.fieldName}']`).val('');
|
||||
|
@ -123,7 +124,7 @@ class DueDateSelect {
|
|||
this.$loading.fadeOut();
|
||||
};
|
||||
|
||||
gl.issueBoards.BoardsStore.detail.issue
|
||||
boardsStore.detail.issue
|
||||
.update(this.$dropdown.attr('data-issue-update'))
|
||||
.then(fadeOutLoader)
|
||||
.catch(fadeOutLoader);
|
||||
|
|
|
@ -60,11 +60,9 @@ export default {
|
|||
>
|
||||
<span>
|
||||
<icon name="play" />
|
||||
<i
|
||||
class="fa fa-caret-down"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<icon
|
||||
name="chevron-down"
|
||||
/>
|
||||
<gl-loading-icon v-if="isLoading" />
|
||||
</span>
|
||||
</button>
|
||||
|
|
|
@ -4,6 +4,7 @@ import _ from 'underscore';
|
|||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
import { humanize } from '~/lib/utils/text_utility';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import ActionsComponent from './environment_actions.vue';
|
||||
import ExternalUrlComponent from './environment_external_url.vue';
|
||||
import StopComponent from './environment_stop.vue';
|
||||
|
@ -24,6 +25,7 @@ export default {
|
|||
components: {
|
||||
UserAvatarLink,
|
||||
CommitComponent,
|
||||
Icon,
|
||||
ActionsComponent,
|
||||
ExternalUrlComponent,
|
||||
StopComponent,
|
||||
|
@ -448,6 +450,10 @@ export default {
|
|||
this.canRetry
|
||||
);
|
||||
},
|
||||
|
||||
folderIconName() {
|
||||
return this.model.isOpen ? 'chevron-down' : 'chevron-right';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -494,27 +500,15 @@ export default {
|
|||
role="button"
|
||||
@click="onClickFolder">
|
||||
|
||||
<span class="folder-icon">
|
||||
<i
|
||||
v-show="model.isOpen"
|
||||
class="fa fa-caret-down"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
<i
|
||||
v-show="!model.isOpen"
|
||||
class="fa fa-caret-right"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
</span>
|
||||
<icon
|
||||
:name="folderIconName"
|
||||
class="folder-icon"
|
||||
/>
|
||||
|
||||
<span class="folder-icon">
|
||||
<i
|
||||
class="fa fa-folder"
|
||||
aria-hidden="true">
|
||||
</i>
|
||||
</span>
|
||||
<icon
|
||||
name="folder"
|
||||
class="folder-icon"
|
||||
/>
|
||||
|
||||
<span>
|
||||
{{ model.folderName }}
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import { mapActions } from 'vuex';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import frequentItemsMixin from './frequent_items_mixin';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
mixins: [frequentItemsMixin],
|
||||
data() {
|
||||
return {
|
||||
|
@ -45,11 +49,10 @@ export default {
|
|||
type="search"
|
||||
class="form-control"
|
||||
/>
|
||||
<i
|
||||
<icon
|
||||
v-if="!searchQuery"
|
||||
class="search-icon fa fa-fw fa-search"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</i>
|
||||
name="search"
|
||||
class="search-icon"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -117,7 +117,7 @@ export default {
|
|||
<button
|
||||
:disabled="!hasChanges"
|
||||
type="button"
|
||||
class="btn btn-primary btn-sm btn-block"
|
||||
class="btn btn-primary btn-sm btn-block qa-begin-commit-button"
|
||||
@click="toggleIsSmall"
|
||||
>
|
||||
{{ __('Commit…') }}
|
||||
|
@ -147,7 +147,7 @@ export default {
|
|||
<loading-button
|
||||
:loading="submitCommitLoading"
|
||||
:label="commitButtonText"
|
||||
container-class="btn btn-success btn-sm float-left"
|
||||
container-class="btn btn-success btn-sm float-left qa-commit-button"
|
||||
@click="commitChanges"
|
||||
/>
|
||||
<button
|
||||
|
|
|
@ -38,14 +38,18 @@ export default {
|
|||
return this.modifiedFilesLength ? 'multi-file-modified' : '';
|
||||
},
|
||||
additionsTooltip() {
|
||||
return sprintf(n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength), {
|
||||
type: this.title.toLowerCase(),
|
||||
count: this.addedFilesLength,
|
||||
});
|
||||
return sprintf(
|
||||
n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength),
|
||||
{
|
||||
type: this.title.toLowerCase(),
|
||||
count: this.addedFilesLength,
|
||||
},
|
||||
);
|
||||
},
|
||||
modifiedTooltip() {
|
||||
return sprintf(
|
||||
n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength), {
|
||||
n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength),
|
||||
{
|
||||
type: this.title.toLowerCase(),
|
||||
count: this.modifiedFilesLength,
|
||||
},
|
||||
|
|
|
@ -25,10 +25,7 @@ export default {
|
|||
return `discard-file-${this.path}`;
|
||||
},
|
||||
modalTitle() {
|
||||
return sprintf(
|
||||
__('Discard changes to %{path}?'),
|
||||
{ path: this.path },
|
||||
);
|
||||
return sprintf(__('Discard changes to %{path}?'), { path: this.path });
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -47,7 +47,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-flex align-items-center ide-file-templates">
|
||||
<div class="d-flex align-items-center ide-file-templates qa-file-templates-bar">
|
||||
<strong class="append-right-default">
|
||||
{{ __('File templates') }}
|
||||
</strong>
|
||||
|
@ -63,7 +63,7 @@ export default {
|
|||
:is-async-data="true"
|
||||
:searchable="true"
|
||||
:title="__('File templates')"
|
||||
class="mr-2"
|
||||
class="mr-2 qa-file-template-dropdown"
|
||||
@click="selectTemplate"
|
||||
/>
|
||||
<transition name="fade">
|
||||
|
|
|
@ -92,7 +92,7 @@ export default {
|
|||
v-model="search"
|
||||
:placeholder="__('Filter...')"
|
||||
type="search"
|
||||
class="dropdown-input-field"
|
||||
class="dropdown-input-field qa-dropdown-filter-input"
|
||||
/>
|
||||
<i
|
||||
aria-hidden="true"
|
||||
|
|
|
@ -24,13 +24,7 @@ export default {
|
|||
IdeProjectHeader,
|
||||
},
|
||||
computed: {
|
||||
...mapState([
|
||||
'loading',
|
||||
'currentActivityView',
|
||||
'changedFiles',
|
||||
'stagedFiles',
|
||||
'lastCommitMsg',
|
||||
]),
|
||||
...mapState(['loading', 'currentActivityView', 'changedFiles', 'stagedFiles', 'lastCommitMsg']),
|
||||
...mapGetters(['currentProject', 'someUncommitedChanges']),
|
||||
showSuccessMessage() {
|
||||
return (
|
||||
|
|
|
@ -45,7 +45,7 @@ export default {
|
|||
<new-entry-button
|
||||
:label="__('New file')"
|
||||
:show-label="false"
|
||||
class="d-flex border-0 p-0 mr-3"
|
||||
class="d-flex border-0 p-0 mr-3 qa-new-file"
|
||||
icon="doc-new"
|
||||
@click="openNewEntryModal({ type: 'blob' })"
|
||||
/>
|
||||
|
|
|
@ -43,7 +43,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div
|
||||
class="ide-file-list"
|
||||
class="ide-file-list qa-file-list"
|
||||
>
|
||||
<template v-if="showLoading">
|
||||
<div
|
||||
|
|
|
@ -37,14 +37,10 @@ export default {
|
|||
return this.hasSearchFocus && !this.search && !this.currentSearchType;
|
||||
},
|
||||
type() {
|
||||
return this.currentSearchType
|
||||
? this.currentSearchType.type
|
||||
: '';
|
||||
return this.currentSearchType ? this.currentSearchType.type : '';
|
||||
},
|
||||
searchTokens() {
|
||||
return this.currentSearchType
|
||||
? [this.currentSearchType]
|
||||
: [];
|
||||
return this.currentSearchType ? [this.currentSearchType] : [];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -13,9 +13,7 @@ export default {
|
|||
computed: {
|
||||
...mapState(['currentBranchId', 'currentMergeRequestId']),
|
||||
mergeRequestLabel() {
|
||||
return this.currentMergeRequestId
|
||||
? `!${this.currentMergeRequestId}`
|
||||
: EMPTY_LABEL;
|
||||
return this.currentMergeRequestId ? `!${this.currentMergeRequestId}` : EMPTY_LABEL;
|
||||
},
|
||||
branchLabel() {
|
||||
return this.currentBranchId || EMPTY_LABEL;
|
||||
|
|
|
@ -110,12 +110,12 @@ export default {
|
|||
ref="fieldName"
|
||||
v-model="entryName"
|
||||
type="text"
|
||||
class="form-control"
|
||||
class="form-control qa-full-file-path"
|
||||
placeholder="/dir/file_name"
|
||||
/>
|
||||
<ul
|
||||
v-if="isCreatingNew"
|
||||
class="prepend-top-default list-inline"
|
||||
class="prepend-top-default list-inline qa-template-list"
|
||||
>
|
||||
<li
|
||||
v-for="(template, index) in templateTypes"
|
||||
|
|
|
@ -43,34 +43,25 @@ export default {
|
|||
{
|
||||
show: this.currentMergeRequestId,
|
||||
title: __('Merge Request'),
|
||||
views: [
|
||||
rightSidebarViews.mergeRequestInfo,
|
||||
],
|
||||
views: [rightSidebarViews.mergeRequestInfo],
|
||||
icon: 'text-description',
|
||||
},
|
||||
{
|
||||
show: true,
|
||||
title: __('Pipelines'),
|
||||
views: [
|
||||
rightSidebarViews.pipelines,
|
||||
rightSidebarViews.jobsDetail,
|
||||
],
|
||||
views: [rightSidebarViews.pipelines, rightSidebarViews.jobsDetail],
|
||||
icon: 'rocket',
|
||||
},
|
||||
{
|
||||
show: this.showLivePreview,
|
||||
title: __('Live preview'),
|
||||
views: [
|
||||
rightSidebarViews.clientSidePreview,
|
||||
],
|
||||
views: [rightSidebarViews.clientSidePreview],
|
||||
icon: 'live-preview',
|
||||
},
|
||||
];
|
||||
},
|
||||
tabs() {
|
||||
return this.defaultTabs
|
||||
.concat(this.extensionTabs)
|
||||
.filter(tab => tab.show);
|
||||
return this.defaultTabs.concat(this.extensionTabs).filter(tab => tab.show);
|
||||
},
|
||||
tabViews() {
|
||||
return _.flatten(this.tabs.map(tab => tab.views));
|
||||
|
|
|
@ -25,12 +25,7 @@ export default {
|
|||
...mapState('rightPane', {
|
||||
rightPaneIsOpen: 'isOpen',
|
||||
}),
|
||||
...mapState([
|
||||
'rightPanelCollapsed',
|
||||
'viewer',
|
||||
'panelResizing',
|
||||
'currentActivityView',
|
||||
]),
|
||||
...mapState(['rightPanelCollapsed', 'viewer', 'panelResizing', 'currentActivityView']),
|
||||
...mapGetters([
|
||||
'currentMergeRequest',
|
||||
'getStagedFile',
|
||||
|
|
|
@ -30,9 +30,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
placeholderText() {
|
||||
return this.tokens.length
|
||||
? ''
|
||||
: this.placeholder;
|
||||
return this.tokens.length ? '' : this.placeholder;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
|
|
@ -21,10 +21,7 @@ Vue.use(Translate);
|
|||
export function initIde(el, options = {}) {
|
||||
if (!el) return null;
|
||||
|
||||
const {
|
||||
extraInitialData = () => ({}),
|
||||
rootComponent = ide,
|
||||
} = options;
|
||||
const { extraInitialData = () => ({}), rootComponent = ide } = options;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
|
|
|
@ -11,14 +11,16 @@ export const computeDiff = (originalContent, newContent) => {
|
|||
if (findOnLine) {
|
||||
Object.assign(findOnLine, change, {
|
||||
modified: true,
|
||||
endLineNumber: (lineNumber + change.count) - 1,
|
||||
endLineNumber: lineNumber + change.count - 1,
|
||||
});
|
||||
} else if ('added' in change || 'removed' in change) {
|
||||
acc.push(Object.assign({}, change, {
|
||||
lineNumber,
|
||||
modified: undefined,
|
||||
endLineNumber: (lineNumber + change.count) - 1,
|
||||
}));
|
||||
acc.push(
|
||||
Object.assign({}, change, {
|
||||
lineNumber,
|
||||
modified: undefined,
|
||||
endLineNumber: lineNumber + change.count - 1,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (!change.removed) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { computeDiff } from './diff';
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
self.addEventListener('message', (e) => {
|
||||
self.addEventListener('message', e => {
|
||||
const { data } = e;
|
||||
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
|
|
|
@ -116,57 +116,57 @@ export const openMergeRequest = (
|
|||
targetProjectId,
|
||||
mergeRequestId,
|
||||
})
|
||||
.then(mr => {
|
||||
dispatch('setCurrentBranchId', mr.source_branch);
|
||||
.then(mr => {
|
||||
dispatch('setCurrentBranchId', mr.source_branch);
|
||||
|
||||
dispatch('getBranchData', {
|
||||
projectId,
|
||||
branchId: 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,
|
||||
});
|
||||
}
|
||||
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;
|
||||
});
|
||||
})
|
||||
.catch(e => {
|
||||
flash(__('Error while loading the merge request. Please try again.'));
|
||||
throw e;
|
||||
});
|
||||
|
|
|
@ -125,10 +125,7 @@ export const showBranchNotFoundError = ({ dispatch }, branchId) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const openBranch = (
|
||||
{ dispatch, state },
|
||||
{ projectId, branchId, basePath },
|
||||
) => {
|
||||
export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath }) => {
|
||||
dispatch('setCurrentBranchId', branchId);
|
||||
|
||||
dispatch('getBranchData', {
|
||||
|
@ -136,23 +133,20 @@ export const openBranch = (
|
|||
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];
|
||||
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);
|
||||
}
|
||||
if (treeEntry) {
|
||||
dispatch('handleTreeEntryAction', treeEntry);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ export default {
|
|||
},
|
||||
[types.SET_SELECTED_TEMPLATE_TYPE](state, type) {
|
||||
state.selectedTemplateType = type;
|
||||
state.templates = [];
|
||||
},
|
||||
[types.SET_UPDATE_SUCCESS](state, success) {
|
||||
state.updateSuccess = success;
|
||||
|
|
|
@ -3,8 +3,7 @@ import Api from '../../../../api';
|
|||
import { scopes } from './constants';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
export const requestMergeRequests = ({ commit }) =>
|
||||
commit(types.REQUEST_MERGE_REQUESTS);
|
||||
export const requestMergeRequests = ({ commit }) => commit(types.REQUEST_MERGE_REQUESTS);
|
||||
export const receiveMergeRequestsError = ({ commit, dispatch }, { type, search }) => {
|
||||
dispatch(
|
||||
'setErrorMessage',
|
||||
|
|
|
@ -1,281 +1,279 @@
|
|||
<script>
|
||||
import Visibility from 'visibilityjs';
|
||||
import { visitUrl } from '../../lib/utils/url_utility';
|
||||
import Poll from '../../lib/utils/poll';
|
||||
import eventHub from '../event_hub';
|
||||
import Service from '../services/index';
|
||||
import Store from '../stores';
|
||||
import titleComponent from './title.vue';
|
||||
import descriptionComponent from './description.vue';
|
||||
import editedComponent from './edited.vue';
|
||||
import formComponent from './form.vue';
|
||||
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
|
||||
import Visibility from 'visibilityjs';
|
||||
import { visitUrl } from '../../lib/utils/url_utility';
|
||||
import Poll from '../../lib/utils/poll';
|
||||
import eventHub from '../event_hub';
|
||||
import Service from '../services/index';
|
||||
import Store from '../stores';
|
||||
import titleComponent from './title.vue';
|
||||
import descriptionComponent from './description.vue';
|
||||
import editedComponent from './edited.vue';
|
||||
import formComponent from './form.vue';
|
||||
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
descriptionComponent,
|
||||
titleComponent,
|
||||
editedComponent,
|
||||
formComponent,
|
||||
export default {
|
||||
components: {
|
||||
descriptionComponent,
|
||||
titleComponent,
|
||||
editedComponent,
|
||||
formComponent,
|
||||
},
|
||||
mixins: [recaptchaModalImplementor],
|
||||
props: {
|
||||
endpoint: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
mixins: [
|
||||
recaptchaModalImplementor,
|
||||
],
|
||||
props: {
|
||||
endpoint: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
updateEndpoint: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
canUpdate: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
canDestroy: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
showInlineEditButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
showDeleteButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
enableAutocomplete: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
issuableRef: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialTitleHtml: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialTitleText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialDescriptionHtml: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
initialDescriptionText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
initialTaskStatus: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedAt: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedByName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedByPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
issuableTemplates: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
markdownPreviewPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownDocsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownVersion: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectNamespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'issue',
|
||||
},
|
||||
canAttachFile: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
updateEndpoint: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
data() {
|
||||
const store = new Store({
|
||||
titleHtml: this.initialTitleHtml,
|
||||
titleText: this.initialTitleText,
|
||||
descriptionHtml: this.initialDescriptionHtml,
|
||||
descriptionText: this.initialDescriptionText,
|
||||
updatedAt: this.updatedAt,
|
||||
updatedByName: this.updatedByName,
|
||||
updatedByPath: this.updatedByPath,
|
||||
taskStatus: this.initialTaskStatus,
|
||||
});
|
||||
canUpdate: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
canDestroy: {
|
||||
required: true,
|
||||
type: Boolean,
|
||||
},
|
||||
showInlineEditButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
showDeleteButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
enableAutocomplete: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
issuableRef: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialTitleHtml: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialTitleText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
initialDescriptionHtml: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
initialDescriptionText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
initialTaskStatus: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedAt: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedByName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedByPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
issuableTemplates: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
markdownPreviewPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownDocsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownVersion: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectNamespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'issue',
|
||||
},
|
||||
canAttachFile: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const store = new Store({
|
||||
titleHtml: this.initialTitleHtml,
|
||||
titleText: this.initialTitleText,
|
||||
descriptionHtml: this.initialDescriptionHtml,
|
||||
descriptionText: this.initialDescriptionText,
|
||||
updatedAt: this.updatedAt,
|
||||
updatedByName: this.updatedByName,
|
||||
updatedByPath: this.updatedByPath,
|
||||
taskStatus: this.initialTaskStatus,
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
state: store.state,
|
||||
showForm: false,
|
||||
};
|
||||
return {
|
||||
store,
|
||||
state: store.state,
|
||||
showForm: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
formState() {
|
||||
return this.store.formState;
|
||||
},
|
||||
computed: {
|
||||
formState() {
|
||||
return this.store.formState;
|
||||
},
|
||||
hasUpdated() {
|
||||
return !!this.state.updatedAt;
|
||||
},
|
||||
issueChanged() {
|
||||
const descriptionChanged =
|
||||
this.initialDescriptionText !== this.store.formState.description;
|
||||
const titleChanged =
|
||||
this.initialTitleText !== this.store.formState.title;
|
||||
return descriptionChanged || titleChanged;
|
||||
},
|
||||
hasUpdated() {
|
||||
return !!this.state.updatedAt;
|
||||
},
|
||||
created() {
|
||||
this.service = new Service(this.endpoint);
|
||||
this.poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'getData',
|
||||
successCallback: res => this.store.updateState(res.data),
|
||||
errorCallback(err) {
|
||||
throw new Error(err);
|
||||
},
|
||||
});
|
||||
issueChanged() {
|
||||
const descriptionChanged = this.initialDescriptionText !== this.store.formState.description;
|
||||
const titleChanged = this.initialTitleText !== this.store.formState.title;
|
||||
return descriptionChanged || titleChanged;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.service = new Service(this.endpoint);
|
||||
this.poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'getData',
|
||||
successCallback: res => this.store.updateState(res.data),
|
||||
errorCallback(err) {
|
||||
throw new Error(err);
|
||||
},
|
||||
});
|
||||
|
||||
if (!Visibility.hidden()) {
|
||||
this.poll.makeRequest();
|
||||
}
|
||||
|
||||
Visibility.change(() => {
|
||||
if (!Visibility.hidden()) {
|
||||
this.poll.makeRequest();
|
||||
this.poll.restart();
|
||||
} else {
|
||||
this.poll.stop();
|
||||
}
|
||||
});
|
||||
|
||||
Visibility.change(() => {
|
||||
if (!Visibility.hidden()) {
|
||||
this.poll.restart();
|
||||
} else {
|
||||
this.poll.stop();
|
||||
}
|
||||
});
|
||||
window.addEventListener('beforeunload', this.handleBeforeUnloadEvent);
|
||||
|
||||
window.addEventListener('beforeunload', this.handleBeforeUnloadEvent);
|
||||
|
||||
eventHub.$on('delete.issuable', this.deleteIssuable);
|
||||
eventHub.$on('update.issuable', this.updateIssuable);
|
||||
eventHub.$on('close.form', this.closeForm);
|
||||
eventHub.$on('open.form', this.openForm);
|
||||
eventHub.$on('delete.issuable', this.deleteIssuable);
|
||||
eventHub.$on('update.issuable', this.updateIssuable);
|
||||
eventHub.$on('close.form', this.closeForm);
|
||||
eventHub.$on('open.form', this.openForm);
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('delete.issuable', this.deleteIssuable);
|
||||
eventHub.$off('update.issuable', this.updateIssuable);
|
||||
eventHub.$off('close.form', this.closeForm);
|
||||
eventHub.$off('open.form', this.openForm);
|
||||
window.removeEventListener('beforeunload', this.handleBeforeUnloadEvent);
|
||||
},
|
||||
methods: {
|
||||
handleBeforeUnloadEvent(e) {
|
||||
const event = e;
|
||||
if (this.showForm && this.issueChanged) {
|
||||
event.returnValue = 'Are you sure you want to lose your issue information?';
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('delete.issuable', this.deleteIssuable);
|
||||
eventHub.$off('update.issuable', this.updateIssuable);
|
||||
eventHub.$off('close.form', this.closeForm);
|
||||
eventHub.$off('open.form', this.openForm);
|
||||
window.removeEventListener('beforeunload', this.handleBeforeUnloadEvent);
|
||||
},
|
||||
methods: {
|
||||
handleBeforeUnloadEvent(e) {
|
||||
const event = e;
|
||||
if (this.showForm && this.issueChanged) {
|
||||
event.returnValue = 'Are you sure you want to lose your issue information?';
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
openForm() {
|
||||
if (!this.showForm) {
|
||||
this.showForm = true;
|
||||
this.store.setFormState({
|
||||
title: this.state.titleText,
|
||||
description: this.state.descriptionText,
|
||||
lockedWarningVisible: false,
|
||||
updateLoading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
closeForm() {
|
||||
this.showForm = false;
|
||||
},
|
||||
|
||||
updateIssuable() {
|
||||
return this.service.updateIssuable(this.store.formState)
|
||||
.then(res => res.data)
|
||||
.then(data => this.checkForSpam(data))
|
||||
.then((data) => {
|
||||
if (window.location.pathname !== data.web_url) {
|
||||
visitUrl(data.web_url);
|
||||
}
|
||||
|
||||
return this.service.getData();
|
||||
})
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
this.store.updateState(data);
|
||||
eventHub.$emit('close.form');
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error && error.name === 'SpamError') {
|
||||
this.openRecaptcha();
|
||||
} else {
|
||||
eventHub.$emit('close.form');
|
||||
window.Flash(`Error updating ${this.issuableType}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
closeRecaptchaModal() {
|
||||
openForm() {
|
||||
if (!this.showForm) {
|
||||
this.showForm = true;
|
||||
this.store.setFormState({
|
||||
title: this.state.titleText,
|
||||
description: this.state.descriptionText,
|
||||
lockedWarningVisible: false,
|
||||
updateLoading: false,
|
||||
});
|
||||
|
||||
this.closeRecaptcha();
|
||||
},
|
||||
|
||||
deleteIssuable() {
|
||||
this.service.deleteIssuable()
|
||||
.then(res => res.data)
|
||||
.then((data) => {
|
||||
// Stop the poll so we don't get 404's with the issuable not existing
|
||||
this.poll.stop();
|
||||
|
||||
visitUrl(data.web_url);
|
||||
})
|
||||
.catch(() => {
|
||||
eventHub.$emit('close.form');
|
||||
window.Flash(`Error deleting ${this.issuableType}`);
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
closeForm() {
|
||||
this.showForm = false;
|
||||
},
|
||||
|
||||
updateIssuable() {
|
||||
return this.service
|
||||
.updateIssuable(this.store.formState)
|
||||
.then(res => res.data)
|
||||
.then(data => this.checkForSpam(data))
|
||||
.then(data => {
|
||||
if (window.location.pathname !== data.web_url) {
|
||||
visitUrl(data.web_url);
|
||||
}
|
||||
|
||||
return this.service.getData();
|
||||
})
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
this.store.updateState(data);
|
||||
eventHub.$emit('close.form');
|
||||
})
|
||||
.catch(error => {
|
||||
if (error && error.name === 'SpamError') {
|
||||
this.openRecaptcha();
|
||||
} else {
|
||||
eventHub.$emit('close.form');
|
||||
window.Flash(`Error updating ${this.issuableType}`);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
closeRecaptchaModal() {
|
||||
this.store.setFormState({
|
||||
updateLoading: false,
|
||||
});
|
||||
|
||||
this.closeRecaptcha();
|
||||
},
|
||||
|
||||
deleteIssuable() {
|
||||
this.service
|
||||
.deleteIssuable()
|
||||
.then(res => res.data)
|
||||
.then(data => {
|
||||
// Stop the poll so we don't get 404's with the issuable not existing
|
||||
this.poll.stop();
|
||||
|
||||
visitUrl(data.web_url);
|
||||
})
|
||||
.catch(() => {
|
||||
eventHub.$emit('close.form');
|
||||
window.Flash(`Error deleting ${this.issuableType}`);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,110 +1,105 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import animateMixin from '../mixins/animate';
|
||||
import TaskList from '../../task_list';
|
||||
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
|
||||
import $ from 'jquery';
|
||||
import animateMixin from '../mixins/animate';
|
||||
import TaskList from '../../task_list';
|
||||
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
animateMixin,
|
||||
recaptchaModalImplementor,
|
||||
],
|
||||
export default {
|
||||
mixins: [animateMixin, recaptchaModalImplementor],
|
||||
|
||||
props: {
|
||||
canUpdate: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
descriptionHtml: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
descriptionText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
taskStatus: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'issue',
|
||||
},
|
||||
updateUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
props: {
|
||||
canUpdate: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
preAnimation: false,
|
||||
pulseAnimation: false,
|
||||
};
|
||||
descriptionHtml: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
watch: {
|
||||
descriptionHtml() {
|
||||
this.animateChange();
|
||||
descriptionText: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
taskStatus: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'issue',
|
||||
},
|
||||
updateUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
preAnimation: false,
|
||||
pulseAnimation: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
descriptionHtml() {
|
||||
this.animateChange();
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.renderGFM();
|
||||
});
|
||||
},
|
||||
taskStatus() {
|
||||
this.updateTaskStatusText();
|
||||
},
|
||||
this.$nextTick(() => {
|
||||
this.renderGFM();
|
||||
});
|
||||
},
|
||||
mounted() {
|
||||
this.renderGFM();
|
||||
taskStatus() {
|
||||
this.updateTaskStatusText();
|
||||
},
|
||||
methods: {
|
||||
renderGFM() {
|
||||
$(this.$refs['gfm-content']).renderGFM();
|
||||
},
|
||||
mounted() {
|
||||
this.renderGFM();
|
||||
this.updateTaskStatusText();
|
||||
},
|
||||
methods: {
|
||||
renderGFM() {
|
||||
$(this.$refs['gfm-content']).renderGFM();
|
||||
|
||||
if (this.canUpdate) {
|
||||
// eslint-disable-next-line no-new
|
||||
new TaskList({
|
||||
dataType: this.issuableType,
|
||||
fieldName: 'description',
|
||||
selector: '.detail-page-description',
|
||||
onSuccess: this.taskListUpdateSuccess.bind(this),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
taskListUpdateSuccess(data) {
|
||||
try {
|
||||
this.checkForSpam(data);
|
||||
this.closeRecaptcha();
|
||||
} catch (error) {
|
||||
if (error && error.name === 'SpamError') this.openRecaptcha();
|
||||
}
|
||||
},
|
||||
|
||||
updateTaskStatusText() {
|
||||
const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
|
||||
const $issuableHeader = $('.issuable-meta');
|
||||
const $tasks = $('#task_status', $issuableHeader);
|
||||
const $tasksShort = $('#task_status_short', $issuableHeader);
|
||||
|
||||
if (taskRegexMatches) {
|
||||
$tasks.text(this.taskStatus);
|
||||
$tasksShort.text(
|
||||
`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ?
|
||||
's' :
|
||||
''}`,
|
||||
);
|
||||
} else {
|
||||
$tasks.text('');
|
||||
$tasksShort.text('');
|
||||
}
|
||||
},
|
||||
if (this.canUpdate) {
|
||||
// eslint-disable-next-line no-new
|
||||
new TaskList({
|
||||
dataType: this.issuableType,
|
||||
fieldName: 'description',
|
||||
selector: '.detail-page-description',
|
||||
onSuccess: this.taskListUpdateSuccess.bind(this),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
taskListUpdateSuccess(data) {
|
||||
try {
|
||||
this.checkForSpam(data);
|
||||
this.closeRecaptcha();
|
||||
} catch (error) {
|
||||
if (error && error.name === 'SpamError') this.openRecaptcha();
|
||||
}
|
||||
},
|
||||
|
||||
updateTaskStatusText() {
|
||||
const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
|
||||
const $issuableHeader = $('.issuable-meta');
|
||||
const $tasks = $('#task_status', $issuableHeader);
|
||||
const $tasksShort = $('#task_status_short', $issuableHeader);
|
||||
|
||||
if (taskRegexMatches) {
|
||||
$tasks.text(this.taskStatus);
|
||||
$tasksShort.text(
|
||||
`${taskRegexMatches[1]}/${taskRegexMatches[2]} task${taskRegexMatches[2] > 1 ? 's' : ''}`,
|
||||
);
|
||||
} else {
|
||||
$tasks.text('');
|
||||
$tasksShort.text('');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,64 +1,64 @@
|
|||
<script>
|
||||
import { __, sprintf } from '~/locale';
|
||||
import updateMixin from '../mixins/update';
|
||||
import eventHub from '../event_hub';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import updateMixin from '../mixins/update';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
const issuableTypes = {
|
||||
issue: __('Issue'),
|
||||
epic: __('Epic'),
|
||||
};
|
||||
const issuableTypes = {
|
||||
issue: __('Issue'),
|
||||
epic: __('Epic'),
|
||||
};
|
||||
|
||||
export default {
|
||||
mixins: [updateMixin],
|
||||
props: {
|
||||
canDestroy: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showDeleteButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
export default {
|
||||
mixins: [updateMixin],
|
||||
props: {
|
||||
canDestroy: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deleteLoading: false,
|
||||
};
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
isSubmitEnabled() {
|
||||
return this.formState.title.trim() !== '';
|
||||
},
|
||||
shouldShowDeleteButton() {
|
||||
return this.canDestroy && this.showDeleteButton;
|
||||
},
|
||||
showDeleteButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
methods: {
|
||||
closeForm() {
|
||||
eventHub.$emit('close.form');
|
||||
},
|
||||
deleteIssuable() {
|
||||
const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), {
|
||||
issuableType: issuableTypes[this.issuableType],
|
||||
});
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(confirmMessage)) {
|
||||
this.deleteLoading = true;
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deleteLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isSubmitEnabled() {
|
||||
return this.formState.title.trim() !== '';
|
||||
},
|
||||
shouldShowDeleteButton() {
|
||||
return this.canDestroy && this.showDeleteButton;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
closeForm() {
|
||||
eventHub.$emit('close.form');
|
||||
},
|
||||
deleteIssuable() {
|
||||
const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), {
|
||||
issuableType: issuableTypes[this.issuableType],
|
||||
});
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(confirmMessage)) {
|
||||
this.deleteLoading = true;
|
||||
|
||||
eventHub.$emit('delete.issuable');
|
||||
}
|
||||
},
|
||||
eventHub.$emit('delete.issuable');
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
<script>
|
||||
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
|
||||
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
timeAgoTooltip,
|
||||
export default {
|
||||
components: {
|
||||
timeAgoTooltip,
|
||||
},
|
||||
props: {
|
||||
updatedAt: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
props: {
|
||||
updatedAt: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedByName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedByPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
updatedByName: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
computed: {
|
||||
hasUpdatedBy() {
|
||||
return this.updatedByName && this.updatedByPath;
|
||||
},
|
||||
updatedByPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasUpdatedBy() {
|
||||
return this.updatedByName && this.updatedByPath;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -53,4 +53,3 @@
|
|||
</span>
|
||||
</small>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,45 +1,45 @@
|
|||
<script>
|
||||
import updateMixin from '../../mixins/update';
|
||||
import markdownField from '../../../vue_shared/components/markdown/field.vue';
|
||||
import updateMixin from '../../mixins/update';
|
||||
import markdownField from '../../../vue_shared/components/markdown/field.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
markdownField,
|
||||
export default {
|
||||
components: {
|
||||
markdownField,
|
||||
},
|
||||
mixins: [updateMixin],
|
||||
props: {
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mixins: [updateMixin],
|
||||
props: {
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
markdownPreviewPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownDocsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownVersion: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
canAttachFile: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
enableAutocomplete: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
markdownPreviewPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.textarea.focus();
|
||||
markdownDocsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
markdownVersion: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
canAttachFile: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
enableAutocomplete: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$refs.textarea.focus();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
|
||||
import $ from 'jquery';
|
||||
import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
issuableTemplates: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectNamespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
export default {
|
||||
props: {
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
issuableTemplatesJson() {
|
||||
return JSON.stringify(this.issuableTemplates);
|
||||
},
|
||||
issuableTemplates: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
mounted() {
|
||||
// Create the editor for the template
|
||||
const editor = document.querySelector('.detail-page-description .note-textarea') || {};
|
||||
editor.setValue = (val) => {
|
||||
this.formState.description = val;
|
||||
};
|
||||
editor.getValue = () => this.formState.description;
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectNamespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
issuableTemplatesJson() {
|
||||
return JSON.stringify(this.issuableTemplates);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// Create the editor for the template
|
||||
const editor = document.querySelector('.detail-page-description .note-textarea') || {};
|
||||
editor.setValue = val => {
|
||||
this.formState.description = val;
|
||||
};
|
||||
editor.getValue = () => this.formState.description;
|
||||
|
||||
this.issuableTemplate = new IssuableTemplateSelectors({
|
||||
$dropdowns: $(this.$refs.toggle),
|
||||
editor,
|
||||
});
|
||||
},
|
||||
};
|
||||
this.issuableTemplate = new IssuableTemplateSelectors({
|
||||
$dropdowns: $(this.$refs.toggle),
|
||||
editor,
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<script>
|
||||
import updateMixin from '../../mixins/update';
|
||||
import updateMixin from '../../mixins/update';
|
||||
|
||||
export default {
|
||||
mixins: [updateMixin],
|
||||
props: {
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
export default {
|
||||
mixins: [updateMixin],
|
||||
props: {
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,79 +1,79 @@
|
|||
<script>
|
||||
import lockedWarning from './locked_warning.vue';
|
||||
import titleField from './fields/title.vue';
|
||||
import descriptionField from './fields/description.vue';
|
||||
import editActions from './edit_actions.vue';
|
||||
import descriptionTemplate from './fields/description_template.vue';
|
||||
import lockedWarning from './locked_warning.vue';
|
||||
import titleField from './fields/title.vue';
|
||||
import descriptionField from './fields/description.vue';
|
||||
import editActions from './edit_actions.vue';
|
||||
import descriptionTemplate from './fields/description_template.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
lockedWarning,
|
||||
titleField,
|
||||
descriptionField,
|
||||
descriptionTemplate,
|
||||
editActions,
|
||||
export default {
|
||||
components: {
|
||||
lockedWarning,
|
||||
titleField,
|
||||
descriptionField,
|
||||
descriptionTemplate,
|
||||
editActions,
|
||||
},
|
||||
props: {
|
||||
canDestroy: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
canDestroy: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
issuableTemplates: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownPreviewPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownDocsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownVersion: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectNamespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
showDeleteButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
canAttachFile: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
enableAutocomplete: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
formState: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
hasIssuableTemplates() {
|
||||
return this.issuableTemplates.length;
|
||||
},
|
||||
issuableTemplates: {
|
||||
type: Array,
|
||||
required: false,
|
||||
default: () => [],
|
||||
},
|
||||
};
|
||||
issuableType: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownPreviewPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownDocsPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
markdownVersion: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 0,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectNamespace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
showDeleteButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
canAttachFile: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
enableAutocomplete: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasIssuableTemplates() {
|
||||
return this.issuableTemplates.length;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
currentPath() {
|
||||
return window.location.pathname;
|
||||
},
|
||||
export default {
|
||||
computed: {
|
||||
currentPath() {
|
||||
return window.location.pathname;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -25,8 +25,10 @@ export default class Store {
|
|||
}
|
||||
|
||||
stateShouldUpdate(data) {
|
||||
return this.state.titleText !== data.title_text ||
|
||||
this.state.descriptionText !== data.description_text;
|
||||
return (
|
||||
this.state.titleText !== data.title_text ||
|
||||
this.state.descriptionText !== data.description_text
|
||||
);
|
||||
}
|
||||
|
||||
setFormState(state) {
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
<script>
|
||||
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TimeagoTooltip,
|
||||
export default {
|
||||
components: {
|
||||
TimeagoTooltip,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
artifact: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mixins: [
|
||||
timeagoMixin,
|
||||
],
|
||||
props: {
|
||||
artifact: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isExpired() {
|
||||
return this.artifact.expired;
|
||||
},
|
||||
computed: {
|
||||
isExpired() {
|
||||
return this.artifact.expired;
|
||||
},
|
||||
// Only when the key is `false` we can render this block
|
||||
willExpire() {
|
||||
return this.artifact.expired === false;
|
||||
},
|
||||
// Only when the key is `false` we can render this block
|
||||
willExpire() {
|
||||
return this.artifact.expired === false;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="block">
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
<script>
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ClipboardButton,
|
||||
export default {
|
||||
components: {
|
||||
ClipboardButton,
|
||||
},
|
||||
props: {
|
||||
commit: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
commit: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mergeRequest: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
isLastBlock: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
mergeRequest: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
};
|
||||
isLastBlock: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
illustrationPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
illustrationSizeClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
action: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
validator(value) {
|
||||
return (
|
||||
value === null ||
|
||||
(Object.prototype.hasOwnProperty.call(value, 'path') &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'method') &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'button_title'))
|
||||
);
|
||||
},
|
||||
export default {
|
||||
props: {
|
||||
illustrationPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
illustrationSizeClass: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
action: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
validator(value) {
|
||||
return (
|
||||
value === null ||
|
||||
(Object.prototype.hasOwnProperty.call(value, 'path') &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'method') &&
|
||||
Object.prototype.hasOwnProperty.call(value, 'button_title'))
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="row empty-state">
|
||||
|
|
|
@ -1,129 +1,131 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import { sprintf, __ } from '../../locale';
|
||||
import _ from 'underscore';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import { sprintf, __ } from '../../locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CiIcon,
|
||||
export default {
|
||||
components: {
|
||||
CiIcon,
|
||||
},
|
||||
props: {
|
||||
deploymentStatus: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
deploymentStatus: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
iconStatus: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
iconStatus: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
environment() {
|
||||
let environmentText;
|
||||
switch (this.deploymentStatus.status) {
|
||||
case 'last':
|
||||
},
|
||||
computed: {
|
||||
environment() {
|
||||
let environmentText;
|
||||
switch (this.deploymentStatus.status) {
|
||||
case 'last':
|
||||
environmentText = sprintf(
|
||||
__('This job is the most recent deployment to %{link}.'),
|
||||
{ link: this.environmentLink },
|
||||
false,
|
||||
);
|
||||
break;
|
||||
case 'out_of_date':
|
||||
if (this.hasLastDeployment) {
|
||||
environmentText = sprintf(
|
||||
__('This job is the most recent deployment to %{link}.'),
|
||||
{ link: this.environmentLink },
|
||||
__(
|
||||
'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.',
|
||||
),
|
||||
{
|
||||
environmentLink: this.environmentLink,
|
||||
deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
|
||||
},
|
||||
false,
|
||||
);
|
||||
break;
|
||||
case 'out_of_date':
|
||||
if (this.hasLastDeployment) {
|
||||
environmentText = sprintf(
|
||||
__(
|
||||
'This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}.',
|
||||
),
|
||||
{
|
||||
environmentLink: this.environmentLink,
|
||||
deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
|
||||
},
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
environmentText = sprintf(
|
||||
__('This job is an out-of-date deployment to %{environmentLink}.'),
|
||||
{ environmentLink: this.environmentLink },
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
case 'failed':
|
||||
} else {
|
||||
environmentText = sprintf(
|
||||
__('The deployment of this job to %{environmentLink} did not succeed.'),
|
||||
__('This job is an out-of-date deployment to %{environmentLink}.'),
|
||||
{ environmentLink: this.environmentLink },
|
||||
false,
|
||||
);
|
||||
break;
|
||||
case 'creating':
|
||||
if (this.hasLastDeployment) {
|
||||
environmentText = sprintf(
|
||||
__(
|
||||
'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
|
||||
),
|
||||
{
|
||||
environmentLink: this.environmentLink,
|
||||
deploymentLink: this.deploymentLink(__('latest deployment')),
|
||||
},
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
environmentText = sprintf(
|
||||
__('This job is creating a deployment to %{environmentLink}.'),
|
||||
{ environmentLink: this.environmentLink },
|
||||
false,
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return environmentText;
|
||||
},
|
||||
environmentLink() {
|
||||
if (this.hasEnvironment) {
|
||||
return sprintf(
|
||||
'%{startLink}%{name}%{endLink}',
|
||||
{
|
||||
startLink: `<a href="${
|
||||
this.deploymentStatus.environment.environment_path
|
||||
}" class="js-environment-link">`,
|
||||
name: _.escape(this.deploymentStatus.environment.name),
|
||||
endLink: '</a>',
|
||||
},
|
||||
}
|
||||
|
||||
break;
|
||||
case 'failed':
|
||||
environmentText = sprintf(
|
||||
__('The deployment of this job to %{environmentLink} did not succeed.'),
|
||||
{ environmentLink: this.environmentLink },
|
||||
false,
|
||||
);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
hasLastDeployment() {
|
||||
return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
|
||||
},
|
||||
lastDeployment() {
|
||||
return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
|
||||
},
|
||||
hasEnvironment() {
|
||||
return !_.isEmpty(this.deploymentStatus.environment);
|
||||
},
|
||||
lastDeploymentPath() {
|
||||
return !_.isEmpty(this.lastDeployment.deployable) ? this.lastDeployment.deployable.build_path : '';
|
||||
},
|
||||
break;
|
||||
case 'creating':
|
||||
if (this.hasLastDeployment) {
|
||||
environmentText = sprintf(
|
||||
__(
|
||||
'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
|
||||
),
|
||||
{
|
||||
environmentLink: this.environmentLink,
|
||||
deploymentLink: this.deploymentLink(__('latest deployment')),
|
||||
},
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
environmentText = sprintf(
|
||||
__('This job is creating a deployment to %{environmentLink}.'),
|
||||
{ environmentLink: this.environmentLink },
|
||||
false,
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return environmentText;
|
||||
},
|
||||
methods: {
|
||||
deploymentLink(name) {
|
||||
environmentLink() {
|
||||
if (this.hasEnvironment) {
|
||||
return sprintf(
|
||||
'%{startLink}%{name}%{endLink}',
|
||||
{
|
||||
startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
|
||||
name,
|
||||
startLink: `<a href="${
|
||||
this.deploymentStatus.environment.environment_path
|
||||
}" class="js-environment-link">`,
|
||||
name: _.escape(this.deploymentStatus.environment.name),
|
||||
endLink: '</a>',
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
}
|
||||
return '';
|
||||
},
|
||||
};
|
||||
hasLastDeployment() {
|
||||
return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
|
||||
},
|
||||
lastDeployment() {
|
||||
return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
|
||||
},
|
||||
hasEnvironment() {
|
||||
return !_.isEmpty(this.deploymentStatus.environment);
|
||||
},
|
||||
lastDeploymentPath() {
|
||||
return !_.isEmpty(this.lastDeployment.deployable)
|
||||
? this.lastDeployment.deployable.build_path
|
||||
: '';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
deploymentLink(name) {
|
||||
return sprintf(
|
||||
'%{startLink}%{name}%{endLink}',
|
||||
{
|
||||
startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
|
||||
name,
|
||||
endLink: '</a>',
|
||||
},
|
||||
false,
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="prepend-top-default js-environment-container">
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import _ from 'underscore';
|
||||
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TimeagoTooltip,
|
||||
export default {
|
||||
components: {
|
||||
TimeagoTooltip,
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
erasedAt: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
erasedAt: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
isErasedByUser() {
|
||||
return !_.isEmpty(this.user);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isErasedByUser() {
|
||||
return !_.isEmpty(this.user);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="prepend-top-default js-build-erased">
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
StuckBlock,
|
||||
},
|
||||
props: {
|
||||
runnerHelpUrl: {
|
||||
runnerSettingsUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
|
@ -30,7 +30,7 @@
|
|||
'headerActions',
|
||||
'headerTime',
|
||||
'shouldRenderCalloutMessage',
|
||||
'jobHasStarted',
|
||||
'shouldRenderTriggeredLabel',
|
||||
'hasEnvironment',
|
||||
'isJobStuck',
|
||||
'hasTrace',
|
||||
|
@ -58,7 +58,7 @@
|
|||
:user="job.user"
|
||||
:actions="headerActions"
|
||||
:has-sidebar-button="true"
|
||||
:should-render-triggered-label="jobHasStarted"
|
||||
:should-render-triggered-label="shouldRenderTriggeredLabel"
|
||||
:item-name="__('Job')"
|
||||
/>
|
||||
</div>
|
||||
|
@ -76,7 +76,7 @@
|
|||
class="js-job-stuck"
|
||||
:has-no-runners-for-project="job.runners.available"
|
||||
:tags="job.tags"
|
||||
:runners-path="runnerHelpUrl"
|
||||
:runners-path="runnerSettingsUrl"
|
||||
/>
|
||||
|
||||
<environments-block
|
||||
|
@ -87,8 +87,8 @@
|
|||
/>
|
||||
|
||||
<erased-block
|
||||
v-if="job.erased"
|
||||
class="js-job-erased"
|
||||
v-if="job.erased_at"
|
||||
class="js-job-erased-block"
|
||||
:user="job.erased_by"
|
||||
:erased-at="job.erased_at"
|
||||
/>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'JobLog',
|
||||
props: {
|
||||
trace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
isComplete: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
export default {
|
||||
name: 'JobLog',
|
||||
props: {
|
||||
trace: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
isComplete: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<pre class="build-trace">
|
||||
|
|
|
@ -1,72 +1,71 @@
|
|||
<script>
|
||||
import { polyfillSticky } from '~/lib/utils/sticky';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { sprintf } from '~/locale';
|
||||
import { polyfillSticky } from '~/lib/utils/sticky';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { sprintf } from '~/locale';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
erasePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
size: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
erasePath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
rawPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
isScrollTopDisabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isScrollBottomDisabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isScrollingDown: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isTraceSizeVisible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
rawPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
computed: {
|
||||
jobLogSize() {
|
||||
return sprintf('Showing last %{size} of log -', {
|
||||
size: numberToHumanSize(this.size),
|
||||
});
|
||||
},
|
||||
isScrollTopDisabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
mounted() {
|
||||
polyfillSticky(this.$el);
|
||||
isScrollBottomDisabled: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
methods: {
|
||||
handleScrollToTop() {
|
||||
this.$emit('scrollJobLogTop');
|
||||
},
|
||||
handleScrollToBottom() {
|
||||
this.$emit('scrollJobLogBottom');
|
||||
},
|
||||
isScrollingDown: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
|
||||
};
|
||||
isTraceSizeVisible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
jobLogSize() {
|
||||
return sprintf('Showing last %{size} of log -', {
|
||||
size: numberToHumanSize(this.size),
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
polyfillSticky(this.$el);
|
||||
},
|
||||
methods: {
|
||||
handleScrollToTop() {
|
||||
this.$emit('scrollJobLogTop');
|
||||
},
|
||||
handleScrollToBottom() {
|
||||
this.$emit('scrollJobLogBottom');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="top-bar">
|
||||
<div class="top-bar affix js-top-bar">
|
||||
<!-- truncate information -->
|
||||
<div class="js-truncated-info truncated-info d-none d-sm-block float-left">
|
||||
<template v-if="isTraceSizeVisible">
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import _ from 'underscore';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CiIcon,
|
||||
Icon,
|
||||
export default {
|
||||
components: {
|
||||
CiIcon,
|
||||
Icon,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
jobs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
jobId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
jobs: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
jobId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isJobActive(currentJobId) {
|
||||
return this.jobId === currentJobId;
|
||||
},
|
||||
methods: {
|
||||
isJobActive(currentJobId) {
|
||||
return this.jobId === currentJobId;
|
||||
},
|
||||
tooltipText(job) {
|
||||
return `${_.escape(job.name)} - ${job.status.tooltip}`;
|
||||
},
|
||||
tooltipText(job) {
|
||||
return `${_.escape(job.name)} - ${job.status.tooltip}`;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="js-jobs-container builds-container">
|
||||
|
|
|
@ -1,116 +1,116 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import DetailRow from './sidebar_detail_row.vue';
|
||||
import ArtifactsBlock from './artifacts_block.vue';
|
||||
import TriggerBlock from './trigger_block.vue';
|
||||
import CommitBlock from './commit_block.vue';
|
||||
import StagesDropdown from './stages_dropdown.vue';
|
||||
import JobsContainer from './jobs_container.vue';
|
||||
import _ from 'underscore';
|
||||
import { mapActions, mapState } from 'vuex';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import DetailRow from './sidebar_detail_row.vue';
|
||||
import ArtifactsBlock from './artifacts_block.vue';
|
||||
import TriggerBlock from './trigger_block.vue';
|
||||
import CommitBlock from './commit_block.vue';
|
||||
import StagesDropdown from './stages_dropdown.vue';
|
||||
import JobsContainer from './jobs_container.vue';
|
||||
|
||||
export default {
|
||||
name: 'JobSidebar',
|
||||
components: {
|
||||
ArtifactsBlock,
|
||||
CommitBlock,
|
||||
DetailRow,
|
||||
Icon,
|
||||
TriggerBlock,
|
||||
StagesDropdown,
|
||||
JobsContainer,
|
||||
export default {
|
||||
name: 'JobSidebar',
|
||||
components: {
|
||||
ArtifactsBlock,
|
||||
CommitBlock,
|
||||
DetailRow,
|
||||
Icon,
|
||||
TriggerBlock,
|
||||
StagesDropdown,
|
||||
JobsContainer,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
runnerHelpUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
runnerHelpUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
terminalPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
terminalPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
computed: {
|
||||
...mapState(['job', 'isLoading', 'stages', 'jobs']),
|
||||
coverage() {
|
||||
return `${this.job.coverage}%`;
|
||||
},
|
||||
duration() {
|
||||
return timeIntervalInWords(this.job.duration);
|
||||
},
|
||||
queued() {
|
||||
return timeIntervalInWords(this.job.queued);
|
||||
},
|
||||
runnerId() {
|
||||
return `${this.job.runner.description} (#${this.job.runner.id})`;
|
||||
},
|
||||
retryButtonClass() {
|
||||
let className =
|
||||
'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
|
||||
className +=
|
||||
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
|
||||
return className;
|
||||
},
|
||||
hasTimeout() {
|
||||
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
|
||||
},
|
||||
timeout() {
|
||||
if (this.job.metadata == null) {
|
||||
return '';
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['job', 'isLoading', 'stages', 'jobs', 'selectedStage']),
|
||||
coverage() {
|
||||
return `${this.job.coverage}%`;
|
||||
},
|
||||
duration() {
|
||||
return timeIntervalInWords(this.job.duration);
|
||||
},
|
||||
queued() {
|
||||
return timeIntervalInWords(this.job.queued);
|
||||
},
|
||||
runnerId() {
|
||||
return `${this.job.runner.description} (#${this.job.runner.id})`;
|
||||
},
|
||||
retryButtonClass() {
|
||||
let className =
|
||||
'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
|
||||
className +=
|
||||
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
|
||||
return className;
|
||||
},
|
||||
hasTimeout() {
|
||||
return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null;
|
||||
},
|
||||
timeout() {
|
||||
if (this.job.metadata == null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let t = this.job.metadata.timeout_human_readable;
|
||||
if (this.job.metadata.timeout_source !== '') {
|
||||
t += ` (from ${this.job.metadata.timeout_source})`;
|
||||
}
|
||||
let t = this.job.metadata.timeout_human_readable;
|
||||
if (this.job.metadata.timeout_source !== '') {
|
||||
t += ` (from ${this.job.metadata.timeout_source})`;
|
||||
}
|
||||
|
||||
return t;
|
||||
},
|
||||
renderBlock() {
|
||||
return (
|
||||
this.job.merge_request ||
|
||||
this.job.duration ||
|
||||
this.job.finished_data ||
|
||||
this.job.erased_at ||
|
||||
this.job.queued ||
|
||||
this.job.runner ||
|
||||
this.job.coverage ||
|
||||
this.job.tags.length ||
|
||||
this.job.cancel_path
|
||||
);
|
||||
},
|
||||
hasArtifact() {
|
||||
return !_.isEmpty(this.job.artifact);
|
||||
},
|
||||
hasTriggers() {
|
||||
return !_.isEmpty(this.job.trigger);
|
||||
},
|
||||
hasStages() {
|
||||
return (
|
||||
(this.job &&
|
||||
this.job.pipeline &&
|
||||
this.job.pipeline.stages &&
|
||||
this.job.pipeline.stages.length > 0) ||
|
||||
false
|
||||
);
|
||||
},
|
||||
commit() {
|
||||
return this.job.pipeline.commit || {};
|
||||
},
|
||||
return t;
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchJobsForStage']),
|
||||
renderBlock() {
|
||||
return (
|
||||
this.job.merge_request ||
|
||||
this.job.duration ||
|
||||
this.job.finished_data ||
|
||||
this.job.erased_at ||
|
||||
this.job.queued ||
|
||||
this.job.runner ||
|
||||
this.job.coverage ||
|
||||
this.job.tags.length ||
|
||||
this.job.cancel_path
|
||||
);
|
||||
},
|
||||
};
|
||||
hasArtifact() {
|
||||
return !_.isEmpty(this.job.artifact);
|
||||
},
|
||||
hasTriggers() {
|
||||
return !_.isEmpty(this.job.trigger);
|
||||
},
|
||||
hasStages() {
|
||||
return (
|
||||
(this.job &&
|
||||
this.job.pipeline &&
|
||||
this.job.pipeline.stages &&
|
||||
this.job.pipeline.stages.length > 0) ||
|
||||
false
|
||||
);
|
||||
},
|
||||
commit() {
|
||||
return this.job.pipeline.commit || {};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchJobsForStage']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<aside
|
||||
class="right-sidebar right-sidebar-expanded build-sidebar"
|
||||
class="js-build-sidebar right-sidebar right-sidebar-expanded build-sidebar"
|
||||
data-offset-top="101"
|
||||
data-spy="affix"
|
||||
>
|
||||
|
@ -276,6 +276,7 @@
|
|||
<stages-dropdown
|
||||
:stages="stages"
|
||||
:pipeline="job.pipeline"
|
||||
:selected-stage="selectedStage"
|
||||
@requestSidebarStageDropdown="fetchJobsForStage"
|
||||
/>
|
||||
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'SidebarDetailRow',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
helpUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
export default {
|
||||
name: 'SidebarDetailRow',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
computed: {
|
||||
hasTitle() {
|
||||
return this.title.length > 0;
|
||||
},
|
||||
hasHelpURL() {
|
||||
return this.helpUrl.length > 0;
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
helpUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasTitle() {
|
||||
return this.title.length > 0;
|
||||
},
|
||||
hasHelpURL() {
|
||||
return this.helpUrl.length > 0;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<p class="build-detail-row">
|
||||
|
|
|
@ -1,50 +1,39 @@
|
|||
<script>
|
||||
import _ from 'underscore';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
import { __ } from '~/locale';
|
||||
import _ from 'underscore';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CiIcon,
|
||||
Icon,
|
||||
export default {
|
||||
components: {
|
||||
CiIcon,
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
pipeline: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
props: {
|
||||
pipeline: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
stages: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
stages: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedStage: this.stages.length > 0 ? this.stages[0].name : __('More'),
|
||||
};
|
||||
selectedStage: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
computed: {
|
||||
hasRef() {
|
||||
return !_.isEmpty(this.pipeline.ref);
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasRef() {
|
||||
return !_.isEmpty(this.pipeline.ref);
|
||||
},
|
||||
watch: {
|
||||
// When the component is initially mounted it may start with an empty stages array.
|
||||
// Once the prop is updated, we set the first stage as the selected one
|
||||
stages(newVal) {
|
||||
if (newVal.length) {
|
||||
this.selectedStage = newVal[0].name;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onStageClick(stage) {
|
||||
this.$emit('requestSidebarStageDropdown', stage);
|
||||
},
|
||||
methods: {
|
||||
onStageClick(stage) {
|
||||
this.$emit('requestSidebarStageDropdown', stage);
|
||||
this.selectedStage = stage.name;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="block-last dropdown">
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
trigger: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
export default {
|
||||
props: {
|
||||
trigger: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
areVariablesVisible: false,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
areVariablesVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
hasVariables() {
|
||||
return this.trigger.variables && this.trigger.variables.length > 0;
|
||||
},
|
||||
computed: {
|
||||
hasVariables() {
|
||||
return this.trigger.variables && this.trigger.variables.length > 0;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
revealVariables() {
|
||||
this.areVariablesVisible = true;
|
||||
},
|
||||
methods: {
|
||||
revealVariables() {
|
||||
this.areVariablesVisible = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -9,8 +9,7 @@ import createStore from './store';
|
|||
export default () => {
|
||||
const { dataset } = document.getElementById('js-job-details-vue');
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Job();
|
||||
|
||||
|
||||
const store = createStore();
|
||||
store.dispatch('setJobEndpoint', dataset.endpoint);
|
||||
|
@ -33,7 +32,7 @@ export default () => {
|
|||
props: {
|
||||
isLoading: this.isLoading,
|
||||
job: this.job,
|
||||
runnerHelpUrl: dataset.runnerHelpUrl,
|
||||
runnerSettingsUrl: dataset.runnerSettingsUrl,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -71,4 +70,7 @@ export default () => {
|
|||
});
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Job();
|
||||
};
|
||||
|
|
|
@ -139,10 +139,12 @@ export const fetchStages = ({ state, dispatch }) => {
|
|||
dispatch('requestStages');
|
||||
|
||||
axios
|
||||
.get(state.job.pipeline.path)
|
||||
.get(`${state.job.pipeline.path}.json`)
|
||||
.then(({ data }) => {
|
||||
// Set selected stage
|
||||
dispatch('receiveStagesSuccess', data.details.stages);
|
||||
dispatch('fetchJobsForStage', data.details.stages[0]);
|
||||
const selectedStage = data.details.stages.find(stage => stage.name === state.selectedStage);
|
||||
dispatch('fetchJobsForStage', selectedStage);
|
||||
})
|
||||
.catch(() => dispatch('receiveStagesError'));
|
||||
};
|
||||
|
@ -156,11 +158,12 @@ export const receiveStagesError = ({ commit }) => {
|
|||
/**
|
||||
* Jobs list on sidebar - depend on stages dropdown
|
||||
*/
|
||||
export const requestJobsForStage = ({ commit }) => commit(types.REQUEST_JOBS_FOR_STAGE);
|
||||
export const requestJobsForStage = ({ commit }, stage) =>
|
||||
commit(types.REQUEST_JOBS_FOR_STAGE, stage);
|
||||
|
||||
// On stage click, set selected stage + fetch job
|
||||
export const fetchJobsForStage = ({ dispatch }, stage) => {
|
||||
dispatch('requestJobsForStage');
|
||||
dispatch('requestJobsForStage', stage);
|
||||
|
||||
axios
|
||||
.get(stage.dropdown_path, {
|
||||
|
|
|
@ -22,10 +22,10 @@ export const shouldRenderCalloutMessage = state =>
|
|||
!_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
|
||||
|
||||
/**
|
||||
* When job has not started the key will be `false`
|
||||
* When job has not started the key will be null
|
||||
* When job started the key will be a string with a date.
|
||||
*/
|
||||
export const jobHasStarted = state => !(state.job.started === false);
|
||||
export const shouldRenderTriggeredLabel = state => _.isString(state.job.started);
|
||||
|
||||
export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
|
||||
|
||||
|
|
|
@ -7,9 +7,10 @@ import mutations from './mutations';
|
|||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default () => new Vuex.Store({
|
||||
actions,
|
||||
mutations,
|
||||
getters,
|
||||
state: state(),
|
||||
});
|
||||
export default () =>
|
||||
new Vuex.Store({
|
||||
actions,
|
||||
mutations,
|
||||
getters,
|
||||
state: state(),
|
||||
});
|
||||
|
|
|
@ -53,6 +53,16 @@ export default {
|
|||
state.isLoading = false;
|
||||
state.hasError = false;
|
||||
state.job = job;
|
||||
|
||||
/**
|
||||
* We only update it on the first request
|
||||
* The dropdown can be changed by the user
|
||||
* after the first request,
|
||||
* and we do not want to hijack that
|
||||
*/
|
||||
if (state.selectedStage === 'More' && job.stage) {
|
||||
state.selectedStage = job.stage;
|
||||
}
|
||||
},
|
||||
[types.RECEIVE_JOB_ERROR](state) {
|
||||
state.isLoading = false;
|
||||
|
@ -81,8 +91,9 @@ export default {
|
|||
state.stages = [];
|
||||
},
|
||||
|
||||
[types.REQUEST_JOBS_FOR_STAGE](state) {
|
||||
[types.REQUEST_JOBS_FOR_STAGE](state, stage) {
|
||||
state.isLoadingJobs = true;
|
||||
state.selectedStage = stage.name;
|
||||
},
|
||||
[types.RECEIVE_JOBS_FOR_STAGE_SUCCESS](state, jobs) {
|
||||
state.isLoadingJobs = false;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { __ } from '~/locale';
|
||||
|
||||
export default () => ({
|
||||
jobEndpoint: null,
|
||||
traceEndpoint: null,
|
||||
|
@ -34,7 +36,7 @@ export default () => ({
|
|||
// sidebar dropdown
|
||||
isLoadingStages: false,
|
||||
isLoadingJobs: false,
|
||||
selectedStage: null,
|
||||
selectedStage: __('More'),
|
||||
stages: [],
|
||||
jobs: [],
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import DropdownUtils from './filtered_search/dropdown_utils';
|
|||
import CreateLabelDropdown from './create_label';
|
||||
import flash from './flash';
|
||||
import ModalStore from './boards/stores/modal_store';
|
||||
import boardsStore from './boards/stores/boards_store';
|
||||
|
||||
export default class LabelsSelect {
|
||||
constructor(els, options = {}) {
|
||||
|
@ -378,7 +379,7 @@ export default class LabelsSelect {
|
|||
}
|
||||
else if ($dropdown.hasClass('js-issue-board-sidebar')) {
|
||||
if ($el.hasClass('is-active')) {
|
||||
gl.issueBoards.BoardsStore.detail.issue.labels.push(new ListLabel({
|
||||
boardsStore.detail.issue.labels.push(new ListLabel({
|
||||
id: label.id,
|
||||
title: label.title,
|
||||
color: label.color[0],
|
||||
|
@ -386,16 +387,16 @@ export default class LabelsSelect {
|
|||
}));
|
||||
}
|
||||
else {
|
||||
var { labels } = gl.issueBoards.BoardsStore.detail.issue;
|
||||
var { labels } = boardsStore.detail.issue;
|
||||
labels = labels.filter(function (selectedLabel) {
|
||||
return selectedLabel.id !== label.id;
|
||||
});
|
||||
gl.issueBoards.BoardsStore.detail.issue.labels = labels;
|
||||
boardsStore.detail.issue.labels = labels;
|
||||
}
|
||||
|
||||
$loading.fadeIn();
|
||||
|
||||
gl.issueBoards.BoardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
|
||||
boardsStore.detail.issue.update($dropdown.attr('data-issue-update'))
|
||||
.then(fadeOutLoader)
|
||||
.catch(fadeOutLoader);
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue