New upstream version 11.5.3+dfsg

This commit is contained in:
Pirate Praveen 2018-12-13 13:39:08 +05:30
parent 4aecc802ff
commit e0c922f897
3195 changed files with 301857 additions and 41743 deletions

View file

@ -1,90 +1,28 @@
---
env:
browser: true
es6: true
extends:
- airbnb-base
- 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"
- ".html.raw"
- '.html'
- '.html.raw'
import/resolver:
webpack:
config: "./config/webpack.config.js"
config: './config/webpack.config.js'
rules:
filenames/match-regex:
- error
- "^[a-z0-9_]+$"
import/no-commonjs: error
no-multiple-empty-lines:
- error
- max: 1
promise/catch-or-return: error
no-param-reassign:
- error
- props: true
ignorePropertyModificationsFor:
- "acc" # for reduce accumulators
- "accumulator" # for reduce accumulators
- "el" # for DOM elements
- "element" # for DOM elements
- "state" # for Vuex mutations
no-underscore-dangle:
- error
- allow:
- __
- _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
## Conflicting rules with prettier:
space-before-function-paren: off
curly: off
arrow-parens: off
function-paren-newline: off
object-curly-newline: off
padded-blocks: off
# Disabled for now, to make the eslint 3 -> eslint 5 update smoother
## Indent rule. We are using the old for now: https://eslint.org/docs/user-guide/migrating-to-4.0.0#indent-rewrite
indent: off
indent-legacy:
- error
- 2
- SwitchCase: 1
VariableDeclarator: 1
outerIIFEBody: 1
FunctionDeclaration:
parameters: 1
body: 1
FunctionExpression:
parameters: 1
body: 1
# Disabled for now, to make the airbnb-base 12.1.0 -> 13.1.0 update smoother
operator-linebreak: off
implicit-arrow-linebreak: off
no-else-return:
- error
- allowElseIf: true

1
.gitattributes vendored
View file

@ -1 +1,2 @@
Dangerfile gitlab-language=ruby
db/schema.rb merge=merge_db_schema

1
.gitignore vendored
View file

@ -40,6 +40,7 @@ eslint-report.html
/config/redis.queues.yml
/config/redis.shared_state.yml
/config/unicorn.rb
/config/puma.rb
/config/secrets.yml
/config/sidekiq.yml
/config/registry.key

View file

@ -1,4 +1,4 @@
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-69.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.5-golang-1.9-git-2.18-chrome-69.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
.dedicated-runner: &dedicated-runner
retry: 1
@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git
- gitlab-org
.default-cache: &default-cache
key: "ruby-2.4.4-debian-stretch-with-yarn"
key: "ruby-2.4.5-debian-stretch-with-yarn"
paths:
- vendor/ruby
- .yarn-cache/
@ -75,11 +75,6 @@ stages:
- mysql:5.7
- redis:alpine
.rails5-variables: &rails5-variables
script:
- export RAILS5=${RAILS5}
- export BUNDLE_GEMFILE=${BUNDLE_GEMFILE}
.rails5: &rails5
allow_failure: true
only:
@ -139,7 +134,7 @@ stages:
- export SCRIPT_NAME="${SCRIPT_NAME:-$CI_JOB_NAME}"
- apk add --update openssl
- wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME
- chmod 755 $SCRIPT_NAME
- chmod 755 $(basename $SCRIPT_NAME)
.rake-exec: &rake-exec
<<: *dedicated-no-docs-no-db-pull-cache-job
@ -150,7 +145,6 @@ stages:
<<: *dedicated-runner
<<: *except-docs-and-qa
<<: *pull-cache
<<: *rails5-variables
stage: test
script:
- JOB_NAME=( $CI_JOB_NAME )
@ -271,7 +265,7 @@ package-and-qa:
SCRIPT_NAME: trigger-build-docs
environment:
name: review-docs/$CI_COMMIT_REF_SLUG
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are secret variables
# DOCS_REVIEW_APPS_DOMAIN and DOCS_GITLAB_REPO_SUFFIX are CI variables
# Discussion: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14236/diffs#note_40140693
url: http://$CI_ENVIRONMENT_SLUG.$DOCS_REVIEW_APPS_DOMAIN/$DOCS_GITLAB_REPO_SUFFIX
on_stop: review-docs-cleanup
@ -324,7 +318,8 @@ review-docs-cleanup:
cloud-native-image:
image: ruby:2.4-alpine
before_script: []
stage: test
dependencies: []
stage: post-test
allow_failure: true
variables:
GIT_DEPTH: "1"
@ -594,7 +589,7 @@ static-analysis:
script:
- scripts/static-analysis
cache:
key: "ruby-2.4.4-debian-stretch-with-yarn-and-rubocop"
key: "ruby-2.4.5-debian-stretch-with-yarn-and-rubocop"
paths:
- vendor/ruby
- .yarn-cache/
@ -700,7 +695,10 @@ gitlab:setup-mysql:
# Frontend-related jobs
gitlab:assets:compile:
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-git-2.18-chrome-69.0-node-8.x-yarn-1.2-graphicsmagick-1.3.29-docker-18.06.1
dependencies: []
services:
- docker:stable-dind
variables:
NODE_ENV: "production"
RAILS_ENV: "production"
@ -709,21 +707,26 @@ gitlab:assets:compile:
WEBPACK_REPORT: "true"
# we override the max_old_space_size to prevent OOM errors
NODE_OPTIONS: --max_old_space_size=3584
DOCKER_DRIVER: overlay2
DOCKER_HOST: tcp://docker:2375
script:
- date
- yarn install --frozen-lockfile --production --cache-folder .yarn-cache
- date
- free -m
- bundle exec rake gitlab:assets:compile
- scripts/build_assets_image
artifacts:
name: webpack-report
expire_in: 31d
paths:
- webpack-report/
- public/assets/
tags:
- docker
karma:
<<: *dedicated-no-docs-and-no-qa-pull-cache-job
<<: *dedicated-no-docs-pull-cache-job
<<: *use-pg
dependencies:
- compile-assets
@ -929,3 +932,93 @@ no_ee_check:
- scripts/no-ee-check
only:
- //@gitlab-org/gitlab-ce
# GitLab Review apps
review:
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
stage: test
allow_failure: true
before_script:
- gem install gitlab --no-document
variables:
GIT_DEPTH: "1"
HOST_SUFFIX: "$CI_ENVIRONMENT_SLUG"
DOMAIN: "-$CI_ENVIRONMENT_SLUG.$REVIEW_APPS_DOMAIN"
GITLAB_HELM_CHART_REF: "master"
script:
- export GITLAB_SHELL_VERSION=$(<GITLAB_SHELL_VERSION)
- export GITALY_VERSION=$(<GITALY_SERVER_VERSION)
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
- source ./scripts/review_apps/review-apps.sh
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
- check_kube_domain
- download_gitlab_chart
- ensure_namespace
- install_tiller
- create_secret
- install_external_dns
- deploy
environment:
name: review/$CI_COMMIT_REF_NAME
url: https://gitlab-$CI_ENVIRONMENT_SLUG.$REVIEW_APPS_DOMAIN
on_stop: stop_review
only:
refs:
- branches@gitlab-org/gitlab-ce
- branches@gitlab-org/gitlab-ee
kubernetes: active
except:
refs:
- master
- /(^docs[\/-].*|.*-docs$)/
stop_review:
<<: *single-script-job
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
stage: test
allow_failure: true
cache: {}
dependencies: []
variables:
SCRIPT_NAME: "review_apps/review-apps.sh"
script:
- source $(basename "${SCRIPT_NAME}")
- delete
- cleanup
when: manual
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
only:
refs:
- branches@gitlab-org/gitlab-ce
- branches@gitlab-org/gitlab-ee
kubernetes: active
except:
- master
- /(^docs[\/-].*|.*-docs$)/
schedule:review_apps_cleanup:
<<: *dedicated-no-docs-pull-cache-job
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
stage: build
allow_failure: true
cache: {}
dependencies: []
before_script:
- gem install gitlab --no-document
variables:
GIT_DEPTH: "1"
script:
- ruby -rrubygems scripts/review_apps/automated_cleanup.rb
environment:
name: review/auto-cleanup
action: stop
only:
refs:
- schedules@gitlab-org/gitlab-ce
- schedules@gitlab-org/gitlab-ee
kubernetes: active
except:
- tags
- /(^docs[\/-].*|.*-docs$)/

View file

@ -16,7 +16,6 @@ Set the title to: `[Security] Description of the original issue`
- [ ] Add a link to the MR to the [links section](#links)
- [ ] Add a link to an EE MR if required
- [ ] Make sure the MR remains in-progress and gets approved after the review cycle, **but never merged**.
- [ ] Assign the MR to a RM once is reviewed and ready to be merged. Check the [RM list] to see who to ping.
#### Backports
@ -26,7 +25,8 @@ Set the title to: `[Security] Description of the original issue`
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
- [ ] Create each MR targetting the security branch `security-X-Y`
- [ ] Add the ~security label and prefix with the version `WIP: [X.Y]` the title of the MR
- [ ] Make sure all MRs have a link in the [links section](#links) and are assigned to a Release Manager.
- [ ] Add the ~"Merge into Security" label to all of the MRs.
- [ ] Make sure all MRs have a link in the [links section](#links)
[secpick documentation]: https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#secpick-script

View file

@ -38,22 +38,22 @@ test plan](https://testing.googleblog.com/2011/09/10-minute-test-plan.html) and
[this wiki page from an open-source tool that implements the ACC
model](https://code.google.com/archive/p/test-analytics/wikis/AccExplained.wiki). -->
| | Simple | Secure | Responsive | Obvious | Stable |
|------------|:------:|:------:|:----------:|:-------:|:------:|
| Admin | | | | | |
| Groups | | | | | |
| Project | | | | | |
| Repository | | | | | |
| Issues | | | | | |
| MRs | | | | | |
| CI/CD | | | | | |
| Ops | | | | | |
| Registry | | | | | |
| Wiki | | | | | |
| Snippets | | | | | |
| Settings | | | | | |
| Tracking | | | | | |
| API | | | | | |
| | Secure | Responsive | Intuitive | Reliable |
|------------|:------:|:----------:|:---------:|:--------:|
| Admin | | | | |
| Groups | | | | |
| Project | | | | |
| Repository | | | | |
| Issues | | | | |
| MRs | | | | |
| CI/CD | | | | |
| Ops | | | | |
| Registry | | | | |
| Wiki | | | | |
| Snippets | | | | |
| Settings | | | | |
| Tracking | | | | |
| API | | | | |
## Capabilities
@ -65,7 +65,7 @@ more complex features could involve multiple or even all.
Example (from https://gitlab.com/gitlab-org/gitlab-ce/issues/50353):
* Respository is
* Simple
* Intuitive
* It's easy to select the desired file template
* It doesn't require unnecessary actions to save the change
* It's easy to undo the change after selecting a template
@ -93,4 +93,4 @@ When adding new automated tests, please keep [testing levels](https://docs.gitla
in mind.
-->
/label ~Quality
/label ~Quality ~"test plan"

View file

@ -1,8 +1,23 @@
Add a description of your merge request here. Merge requests without an adequate
description will not be reviewed until one is added.
## What does this MR do?
<!--
Describe in detail what your merge request does, why it does that, etc. Merge
requests without an adequate description will not be reviewed until one is
added.
Please also keep this description up-to-date with any discussion that takes
place so that reviewers can understand your intent. This is especially
important if they didn't participate in the discussion.
Make sure to remove this comment when you are done.
-->
Add a description of your merge request here.
## Database checklist
- [ ] Conforms to the [database guides](https://docs.gitlab.com/ee/development/README.html#databases-guides)
When adding migrations:
- [ ] Updated `db/schema.rb`
@ -35,16 +50,9 @@ When removing columns, tables, indexes or other structures:
- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/index.html#contributing-to-docs)
- [ ] [API support added](https://docs.gitlab.com/ee/development/api_styleguide.html)
- [ ] [Tests added for this feature/bug](https://docs.gitlab.com/ee/development/testing_guide/index.html)
- Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
- [ ] Has been reviewed by a Backend [maintainer](https://about.gitlab.com/handbook/engineering/#maintainer)
- [ ] Has been reviewed by a Database [specialist](https://about.gitlab.com/team/structure/#specialist)
- [ ] Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
- [ ] Conforms to the [merge request performance guidelines](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
- [ ] Conforms to the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
- [ ] If you have multiple commits, please combine them into a few logically organized commits by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
- [ ] [Internationalization required/considered](https://docs.gitlab.com/ee/development/i18n/index.html)
- [ ] For a paid feature, have we considered GitLab.com plans, how it works for groups, and is there a design for promoting it to users who aren't on the correct plan?
- [ ] [End-to-end tests](https://docs.gitlab.com/ee/development/testing_guide/end_to_end_tests.html#testing-code-in-merge-requests) pass (`package-and-qa` manual pipeline job)
/label ~database

View file

@ -19,6 +19,7 @@ Closes
- [ ] [Apply the correct labels and milestone](https://docs.gitlab.com/ee/development/documentation/workflow.html#2-developer-s-role-in-the-documentation-process)
- [ ] Crosslink the document from the higher-level index
- [ ] Crosslink the document from other subject-related docs
- [ ] Feature moving tiers? Make sure the change is also reflected in [`features.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml)
- [ ] Correctly apply the product [badges](https://docs.gitlab.com/ee/development/documentation/styleguide.html#product-badges) and [tiers](https://docs.gitlab.com/ee/development/documentation/styleguide.html#gitlab-versions-and-tiers)
- [ ] [Port the MR to EE (or backport from CE)](https://docs.gitlab.com/ee/development/documentation/index.html#cherry-picking-from-ce-to-ee): _always recommended, required when the `ee-compat-check` job fails_

View file

@ -3,3 +3,7 @@
/public/
/vendor/
/tmp/
# ignore stylesheets for now as this clashes with our linter
*.css
*.scss

View file

@ -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:
@ -66,15 +76,20 @@ Naming/FileName:
- 'qa/qa/specs/**/*'
- 'qa/bin/*'
- 'config/**/*'
- 'ee/config/**/*'
- 'lib/generators/**/*'
- 'locale/unfound_translations.rb'
- 'ee/locale/unfound_translations.rb'
- 'ee/lib/generators/**/*'
- 'qa/qa/scenario/test/integration/ldap_no_tls.rb'
- 'qa/qa/scenario/test/integration/ldap_tls.rb'
IgnoreExecutableScripts: true
AllowedAcronyms:
- EE
- JSON
- LDAP
- SAML
- IO
- HMAC
- QA

View file

@ -1 +1 @@
2.4.4
2.4.5

View file

@ -2,53 +2,312 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 11.4.9 (2018-12-03)
## 11.5.3 (2018-12-06)
### Fixed (2 changes)
### Security (1 change)
- Prevent a path traversal attack on global file templates.
## 11.5.2 (2018-12-03)
### Removed (1 change)
- Removed Site Statistics optimization as it was causing problems. !23314
### Fixed (6 changes, 1 of them is from the community)
- Display impersonation token value only after creation. !22916
- Fix not render emoji in filter dropdown. !23112 (Hiroyuki Sato)
- Fixes stuck tooltip on stop env button. !23244
- Correctly handle data-loss scenarios when encrypting columns. !23306
- Clear BatchLoader context between Sidekiq jobs. !23308
- Fix handling of filenames with hash characters in tree view. !23368
## 11.4.8 (2018-11-27)
## 11.5.1 (2018-11-26)
### Security (24 changes)
### Security (17 changes)
- Escape entity title while autocomplete template rendering to prevent XSS. !2571
- Resolve reflected XSS in Ouath authorize window.
- Fix XSS in merge request source branch name.
- Escape user fullname while rendering autocomplete template to prevent XSS.
- Fix CRLF vulnerability in Project hooks.
- Fix possible XSS attack in Markdown urls with spaces.
- Redact sensitive information on gitlab-workhorse log.
- Do not follow redirects in Prometheus service when making http requests to the configured api url.
- Persist only SHA digest of PersonalAccessToken#token.
- Don't expose confidential information in commit message list.
- Provide email notification when a user changes their email address.
- Restrict Personal Access Tokens to API scope on web requests.
- Redact personal tokens in unsubscribe links.
- Resolve reflected XSS in Ouath authorize window.
- Fix SSRF in project integrations.
- Fixed ability to comment on locked/confidential issues.
- Fixed ability of guest users to edit/delete comments on locked or confidential issues.
- Fix milestone promotion authorization check.
- Monkey kubeclient to not follow any redirects.
- Configure mermaid to not render HTML content in diagrams.
- Fix a possible symlink time of check to time of use race condition in GitLab Pages.
- Removed ability to see private group names when the group id is entered in the url.
- Fix stored XSS for Environments.
- Prevent SSRF attacks in HipChat integration.
- Validate Wiki attachments are valid temporary files.
## 11.4.7 (2018-11-20)
## 11.5.0 (2018-11-22)
- No changes.
## 11.4.6 (2018-11-18)
### Security (1 change)
### Security (10 changes, 1 of them is from the community)
- Escape entity title while autocomplete template rendering to prevent XSS. !2556
- Update moment to 2.22.2. !22648 (Takuya Noguchi)
- Redact personal tokens in unsubscribe links.
- Escape user fullname while rendering autocomplete template to prevent XSS.
- Persist only SHA digest of PersonalAccessToken#token.
- Monkey kubeclient to not follow any redirects.
- Prevent SSRF attacks in HipChat integration.
- Prevent templated services from being imported.
- Validate Wiki attachments are valid temporary files.
- Fix XSS in merge request source branch name.
### Removed (2 changes)
- Remove Git circuit breaker. !22212
- Remove Koding integration and documentation. !22334
### Fixed (74 changes, 15 of them are from the community)
- Hide all tables on Pipeline when no Jobs for the Pipeline. !18540 (Takuya Noguchi)
- Fixing count on Milestones. !21446
- Use case insensitve username lookups. !21728 (William George)
- Correctly process Bamboo API result array. !21970 (Alex Lossent)
- Fix 'merged with' UI being displayed when merge request has no merge commit. !22022
- Fix broken file name navigation on MRs. !22109
- Fix incorrect spacing between buttons when commenting on a MR. !22135
- Vertical align Pipeline Graph in Commit Page. !22173 (Johann Hubert Sonntagbauer)
- Reject invalid branch names in repository compare controller. !22186
- Fix size of emojis of user status in user menu. !22194
- Use the standard PIP_CACHE_DIR for Python dependency caching template. !22211 (Takuya Noguchi)
- Fix bug with wiki attachments content disposition. !22220
- Does not allow a SSH URI when importing new projects. !22309
- fix duplicated key in license management job auto devops gitlab ci template. !22311 (Adam Lemanski)
- Fix commit signature error when project is disabled. !22344
- Show available clusters when installed or updated. !22356
- Fix auto-corrected upload URLs in webhooks. !22361
- Fix a bug displaying certain wiki pages. !22377
- Fix prometheus graphs in firefox. !22400
- Resolve assign-me quick action doesn't work if there is extra white space. !22402
- Remove base64 encoding from files that contain plain text. !22425
- Strip whitespace around GitHub personal access tokens. !22432
- Fix 500 error when testing webhooks with redirect loops. !22447 (Heinrich Lee Yu)
- Fix rendering of 'Protected' value on Runner details page. !22459
- Fix bug stopping non-admin users from changing visibility level on group creation. !22468
- Make Issue Board sidebar show project-specific labels based on selected Issue. !22475
- Fix EOF detection with CI artifacts metadata. !22479
- Fix transient spec error in the bar_chart component. !22495
- Resolve LFS not correctly showing enabled. !22501
- If user was not found, service hooks won't run on post receive background job. !22519
- Fix broken "Show whitespace changes" button on MRs. !22539
- Always show new issue button in boards' Open list. !22557 (Heinrich Lee Yu)
- Add transparent background to markdown header tabs. !22565 (George Tsiolis)
- Use gitlab_environment for ldap rake task. !22582
- Add commit message to commit tree anchor title. !22585
- Cache pipeline status per SHA. !22589
- Change HELM_HOST in Auto-DevOps template to work behind proxy. !22596 (Sergej Nikolaev <kinolaev@gmail.com>)
- Show user status for label events in system notes. !22609
- Fix extra merge request versions created from forked merge requests. !22611
- Remove PersonalAccessTokensFinder#find_by method. !22617
- Fix search "all in GitLab" not working with relative URLs. !22644
- Fix quick links button styles. !22657 (George Tsiolis)
- Fix #53298: JupyterHub restarts should work without errors. !22671 (Amit Rathi)
- Fix incompatibility with IE11 due to non-transpiled gitlab-ui components. !22695
- Fix bug when links in tabs of the labels index pages ends with .html. !22716
- Fixed label removal from issue. !22762
- Align toggle sidebar button across all browsers and OSs. !22771
- Disable replication lag check for Aurora PostgreSQL databases. !22786
- Render unescaped link for failed pipeline status. !22807
- Fix misaligned approvers dropdown. !22832
- Fix bug with wiki page create message. !22849
- Fix rendering of filter bar tokens for special values. !22865 (Heinrich Lee Yu)
- Align sign in button. !22888 (George Tsiolis)
- Fix error handling bugs in kubernetes integration. !22922
- Fix deployment jobs using nil KUBE_TOKEN due to migration issue. !23009
- Avoid returning deployment metrics url to MR widget when the deployment is not successful. !23010
- Fix a race condition intermittently breaking GitLab startup. !23028
- Adds margin after a deleted branch name in the activity feed. !23038
- Ignore environment validation failure. !23100
- Fixes broken borders for reports section in MR widget.
- Adds CI favicon back to jobs page.
- Redirect to the pipeline builds page when a build is canceled. (Eva Kadlecova)
- Fixed diff stats not showing when performance bar is enabled.
- Show expand all diffs button when a single diff file is collapsed.
- Clear fetched file templates when changing template type in Web IDE.
- Fix bug causing not all emails to show up in commit email selectbox.
- Remove duplicate escape in job sidebar.
- Fixing styling issues on the scheduled pipelines page.
- Renders stuck block when runners are stuck.
- Removes extra border from test reports in the merge request widget.
- Only render link to branch when branch still exists in pipeline page.
- Fixed source project not filtering in merge request creation compare form.
- Do not reload self on hooks when creating deployment.
- Fixes broken test in master.
### Changed (38 changes, 12 of them are from the community)
- Link button in markdown editor recognize URLs. !1983 (Johann Hubert Sonntagbauer)
- Replace i to icons in vue components. !20748 (George Tsiolis)
- Remove Linguist gem, reducing Rails memory usage by 128MB per process. !21008
- Issue board card design. !21229
- On deletion of a file in sub directory in web IDE redirect to the sub directory instead of project root. !21465 (George Thomas @thegeorgeous)
- Change single-item breadcrumbs to page titles. !22155
- Improving branch filter sorting by listing exact matches first and added support for begins_with (^) and ends_with ($) matching. !22166 (Jason Rutherford)
- Remove legacy unencrypted webhook columns from the database. !22199
- Show canary status in the performance bar. !22222
- Add failure reason for execution timeout. !22224
- Rename "scheduled" label/badge of delayed jobs to "delayed". !22245
- Update the empty state on wiki-only projects to display an empty state that is more consistent with the rest of the system. !22262
- Add IID headers to E-Mail notifications. !22263
- Allow finding the common ancestor for multiple revisions through the API. !22295
- Add status to Deployment. !22380
- Add dynamic timer to delayed jobs. !22382
- No longer require a deploy to start Prometheus monitoring. !22401
- Secret Variables renamed to CI Variables in the codebase, to match UX. !22414 (Marcel Amirault @ravlen)
- Automatically navigate to last board visited. !22430
- Use merge request prefix symbol in event feed title. !22449 (George Tsiolis)
- Update Ruby version in README. !22466 (J.D. Bean)
- Reword error message for internal CI unknown pipeline status. !22474
- Bump mermaid to 8.0.0-rc.8. !22509 (@blackst0ne)
- Update Todo icons in collapsed sidebar for Issues and MRs. !22534
- Support backward compatibility when introduce new failure reason. !22566
- Add dynamic timer for delayed jobs in pipelines list. !22621
- Truncate milestone title on collapsed sidebar. !22624 (George Tsiolis)
- Standardize milestones filter in APIs to None / Any. !22637 (Heinrich Lee Yu)
- Add dynamic timer for delayed jobs in job list. !22656
- Allowing issues with single letter identifiers to be linked to external issue tracker (f.ex T-123). !22717 (Dídac Rodríguez Arbonès)
- Update project and group labels empty state. !22745 (George Tsiolis)
- Fix environment status in merge request widget. !22799
- Paginate Bitbucket Server importer projects. !22825
- Drop `allow_overflow` option in `TimeHelper.duration_in_numbers`. !52284
- Add 'only history' option to notes filter.
- Adds filtered dropdown with changed files in review.
- Expose {closed,merged}_{at,by} in merge requests API index.
- Make all legacy security reports to use raw format.
### Performance (27 changes, 6 of them are from the community)
- Add preload for routes and namespaces for issues controller. !21651
- Enhance performance of counting local LFS objects. !22143
- Use cached readme contents when available. !22325
- Experimental support for running Puma multithreaded web-server. !22372
- Enhance performance of counting local Uploads. !22522
- Reduce SQL queries needed to load open merge requests. !22709
- Significantly cut memory usage and SQL queries when reloading diffs. !22725
- Optimize merge request refresh by using the database to check commit SHAs. !22731
- Remove dind from license_management auto-devops job definition. !22732
- Add index to find stuck merge requests. !22749
- Allow Rails concurrency when running in Puma. !22751
- Improve performance of rendering large reports. !22835
- Improves performance of stuck import jobs detection. !22879
- Rewrite SnippetsFinder to improve performance by a factor of 1500.
- Enable more frozen string in lib/**/*.rb. (gfyoung)
- Enable some frozen string in lib/gitlab. (gfyoung)
- Enable even more frozen string in lib/**/*.rb. (gfyoung)
- Improve performance of tree rendering in repositories with lots of items.
- Remove gitlab-ui's tooltip from global.
- Remove gitlab-ui's progress bar from global.
- Remove gitlab-ui's pagination from global.
- Remove gitlab-ui's modal from global.
- Remove gitlab-ui's loading icon from global.
- Enable frozen string for lib/gitlab/*.rb. (gfyoung)
- Enable frozen string for lib/gitlab/ci. (gfyoung)
- Enable frozen string for remaining lib/gitlab/ci/**/*.rb. (gfyoung)
- Adds pagination to pipelines table in merge request page.
### Added (33 changes, 11 of them are from the community)
- Add endpoint to update a git submodule reference. !20949
- Add license data to projects endpoint. !21606 (J.D. Bean (@jdbean))
- Allow to configure when to retry failed CI jobs. !21758 (Markus Doits)
- Add API endpoint to list issue related merge requests. !21806 (Helmut Januschka)
- Add the Play button for delayed jobs in environment page. !22106
- Switch between tree list & file list in diffs file browser. !22191
- Re-arrange help-related user menu items into new Help menu. !22195
- Adds trace of each access check when git push times out. !22265
- Add email for milestone change. !22279
- Show post-merge pipeline in merge request page. !22292
- Add Applications API endpoints for listing and deleting entries. !22296 (Jean-Baptiste Vasseur)
- Added `Any` option to milestones filter. !22351 (Heinrich Lee Yu)
- Improve validation errors for external CI/CD configuration. !22394
- Introduce new model to persist specific cluster information. !22404
- Add background migration to populate Kubernetes namespaces. !22433
- Add support for JSON logging for audit events. !22471
- Adds option to override commit email with a noreply private email. !22560
- Add None/Any option for assignee_id in Issues and Merge Requests API. !22598 (Heinrich Lee Yu)
- Add None/Any option for assignee_id in search bar. !22599 (Heinrich Lee Yu)
- Implement parallel job keyword. !22631
- Add None / Any options to reactions filter. !22638 (Heinrich Lee Yu)
- Make index.* render like README.* when it's present in a repository. !22639 (Jakub Jirutka)
- Allow adding patches when creating a merge request via email. !22723 (Serdar Dogruyol)
- Bump Gitaly to 0.129.0. !22868
- Allow commenting on any diff line in Merge Requests. !22914
- Add revert to commits API. !22919
- Introduce Knative support. !43959 (Chris Baumbauer)
- Reimplemented image commenting in merge request diffs.
- Soft-archive old jobs.
- Renders warning info when job is archieved.
- Support licenses and performance.
- Filter notes by comments or activity for issues and merge requests.
- Bump Gitaly to 0.128.0.
### Other (54 changes, 18 of them are from the community)
- Remove .card-title from .card-header for BS4 migration. !19335 (Takuya Noguchi)
- Update group settings/edit page to new design. !21115
- Change markdown header tab anchor links to buttons. !21988 (George Tsiolis)
- Replace tooltip in markdown component with gl-tooltip. !21989 (George Tsiolis)
- Extend RBAC by having a service account restricted to project's namespace. !22011
- Update images in group docs. !22031 (Marc Schwede)
- Add gitlab:gitaly:check task for Gitaly health check. !22063
- Add new sort option "most_stars" to "Group > Children" pages. !22121 (Rene Hennig)
- Fix inaccessible dropdown for code-less projects. !22137
- Rails5: fix user edit profile clear status spec. !22169 (Jasper Maes)
- Rails 5: fix mysql milliseconds problems in scheduled build specs. !22170 (Jasper Maes)
- Focus project slug on tab navigation. !22198
- Redesign activity feed. !22217
- Update used version of Runner Helm Chart to 0.1.34. !22274
- Update environments empty state. !22297 (George Tsiolis)
- Adds model and migrations to enable group level clusters. !22307
- Use literal instead of constructor for creating regex. !22367
- Remove prometheus configuration help text. !22413 (George Tsiolis)
- Rails5: fix deployment model spec. !22428 (Jasper Maes)
- Change to top level controller for clusters so that we can use it for project clusters (now) and group clusters (later). !22438
- Remove empty spec describe blocks. !22451 (George Tsiolis)
- Change branch font type in tag creation. !22454 (George Tsiolis)
- Rails5: fix delete blob. !22456 (Jasper Maes)
- Start tracking shards and pool repositories in the database. !22482
- Allow kubeclient to call RoleBinding methods. !22524
- Introduce new kubernetes helpers. !22525
- Adds container to pager to enable scoping. !22529
- Update used version of Runner Helm Chart to 0.1.35. !22541
- Removes experimental labels from cluster views. !22550
- Combine all datetime library functions into 'datetime_utility.js'. !22570
- Upgrade Prometheus to 2.4.3 and Alertmanager to 0.15.2. !22600
- Fix stage dropdown not rendering in different languages. !22604
- Remove asset_sync gem from Gemfile and related code from codebase. !22610
- Use key-value pair arrays for API query parameter logging instead of hashes. !22623
- Replace deprecated uniq on a Relation with distinct. !22625 (Jasper Maes)
- Remove mousetrap-rails gem. !22647 (Takuya Noguchi)
- Fix IDE typos in props. !22685 (George Tsiolis)
- Add scheduled flag to job entity. !22710
- Remove `ci_enable_scheduled_build` feature flag. !22742
- Add endpoints for simulating certain failure modes in the application. !22746
- Bump KUBERNETES_VERSION for Auto DevOps to latest 1.10 series. !22757
- Fix statement timeouts in RemoveRestrictedTodos migration. !22795
- Rails5: fix mysql milliseconds issue in deployment model specs. !22850 (Jasper Maes)
- Update GitLab-Workhorse to v7.1.0. !22883
- Update JIRA service UI to accept email and API token.
- Update wiki empty state. (George Tsiolis)
- Only renders dropdown for review app changes when we have a list of files to show. Otherwise will render the regular review app button.
- Associate Rakefile with Ruby icon in diffs.
- Uses gitlab-ui components in jobs components.
- Create new group: Rename form fields and update UI.
- Transform job page into a single Vue+Vuex application.
- Updates svg dependency.
- Adds missing i18n to pipelines table.
- Disables stop environment button while the deploy is in progress.
## 11.4.5 (2018-11-04)
@ -320,6 +579,41 @@ entry.
- Check frozen string in style builds. (gfyoung)
## 11.3.9 (2018-10-31)
### Security (1 change)
- Monkey kubeclient to not follow any redirects.
## 11.3.8 (2018-10-27)
- No changes.
## 11.3.7 (2018-10-26)
### Security (6 changes)
- Escape entity title while autocomplete template rendering to prevent XSS. !2557
- Persist only SHA digest of PersonalAccessToken#token.
- Fix XSS in merge request source branch name.
- Redact personal tokens in unsubscribe links.
- Prevent SSRF attacks in HipChat integration.
- Validate Wiki attachments are valid temporary files.
## 11.3.6 (2018-10-17)
- No changes.
## 11.3.5 (2018-10-15)
### Fixed (2 changes)
- Fix loading issue on some merge request discussion. !21982
- Fix project deletion when there is a export available. !22276
## 11.3.3 (2018-10-04)
- No changes.
@ -597,6 +891,28 @@ entry.
- Creates Vue component for artifacts block on job page.
## 11.2.8 (2018-10-31)
### Security (1 change)
- Monkey kubeclient to not follow any redirects.
## 11.2.7 (2018-10-27)
- No changes.
## 11.2.6 (2018-10-26)
### Security (5 changes)
- Escape entity title while autocomplete template rendering to prevent XSS. !2558
- Fix XSS in merge request source branch name.
- Redact personal tokens in unsubscribe links.
- Persist only SHA digest of PersonalAccessToken#token.
- Prevent SSRF attacks in HipChat integration.
## 11.2.5 (2018-10-05)
### Security (3 changes)

View file

@ -64,184 +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
## Code of Conduct
As contributors and maintainers of this project, we pledge to respect all
people who contribute through reporting issues, posting feature requests,
updating documentation, submitting pull requests or patches, and other
activities.
We are committed to making participation in this project a harassment-free
experience for everyone, regardless of level of experience, gender, gender
identity and expression, sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual
language or imagery, derogatory comments or personal attacks, trolling, public
or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct. Project maintainers who do not
follow the Code of Conduct may be removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior can be
reported by emailing `contact@gitlab.com`.
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
This [documentation](https://about.gitlab.com/contributing/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&#40;Accepting%20merge%20requests&#41;)
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 GitLabs 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 codes 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.
@ -250,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.
@ -269,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.
@ -278,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.
@ -313,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.

View file

@ -1,3 +1,4 @@
danger.import_plugin('danger/plugins/helper.rb')
danger.import_dangerfile(path: 'danger/metadata')
danger.import_dangerfile(path: 'danger/changes_size')
danger.import_dangerfile(path: 'danger/changelog')

4
Dockerfile.assets Normal file
View file

@ -0,0 +1,4 @@
# Simple container to store assets for later use
FROM scratch
ADD public/assets /assets/
CMD /bin/true

View file

@ -1 +1 @@
0.125.1
0.129.0

View file

@ -1 +1 @@
1.1.1
1.3.1

View file

@ -1 +1 @@
8.3.3
8.4.1

View file

@ -1 +1 @@
7.0.1
7.1.3

28
Gemfile
View file

@ -79,13 +79,6 @@ gem 'gpgme'
gem 'gitlab_omniauth-ldap', '~> 2.0.4', require: 'omniauth-ldap'
gem 'net-ldap'
# Git Wiki
# 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 +139,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'
@ -159,6 +153,11 @@ group :unicorn do
gem 'unicorn-worker-killer', '~> 0.4.4'
end
group :puma do
gem 'puma', '~> 3.12', require: false
gem 'puma_worker_killer', require: false
end
# State machine
gem 'state_machines-activerecord', '~> 0.5.1'
@ -212,7 +211,7 @@ gem 'hipchat', '~> 1.5.0'
gem 'jira-ruby', '~> 1.4'
# Flowdock integration
gem 'gitlab-flowdock-git-hook', '~> 1.0.1'
gem 'flowdock', '~> 0.7'
# Slack integration
gem 'slack-notifier', '~> 1.5.1'
@ -245,9 +244,6 @@ gem 'rack-attack', '~> 4.4.1'
# Ace editor
gem 'ace-rails-ap', '~> 4.1.0'
# Keyboard shortcuts
gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.5'
@ -420,11 +416,10 @@ group :ed25519 do
end
# Gitaly GRPC client
gem 'gitaly-proto', '~> 0.118.1', require: 'gitaly'
gem 'grpc', '~> 1.11.0'
gem 'gitaly-proto', '~> 0.123.0', require: 'gitaly'
gem 'grpc', '~> 1.15.0'
# Locked until https://github.com/google/protobuf/issues/4210 is closed
gem 'google-protobuf', '= 3.5.1'
gem 'google-protobuf', '~> 3.6'
gem 'toml-rb', '~> 1.0.0', require: false
@ -436,6 +431,3 @@ gem 'flipper-active_support_cache_store', '~> 0.13.0'
# Structured logging
gem 'lograge', '~> 0.5'
gem 'grape_logging', '~> 1.7'
# Asset synchronization
gem 'asset_sync', '~> 2.4'

View file

@ -58,11 +58,6 @@ GEM
asciidoctor (1.5.6.2)
asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5)
asset_sync (2.4.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.4.0)
atomic (1.1.99)
attr_encrypted (3.1.0)
@ -274,32 +269,9 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
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)
gitaly-proto (0.123.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
gitlab-gollum-lib (4.2.7.5)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 3.1)
sanitize (~> 4.6.4)
stringex (~> 2.6)
gitlab-grit (2.8.2)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
mime-types (>= 1.16)
posix-spawn (~> 0.3)
gitlab-markup (1.6.4)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
@ -314,8 +286,6 @@ GEM
rubyntlm (~> 0.5)
globalid (0.4.1)
activesupport (>= 4.2.0)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gon (6.2.0)
actionpack (>= 3.0)
multi_json
@ -327,16 +297,15 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.5.1)
googleapis-common-protos-types (1.0.1)
google-protobuf (3.6.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 +329,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 +433,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)
@ -493,7 +457,6 @@ GEM
mini_mime (1.0.1)
mini_portile2 (2.3.0)
minitest (5.7.0)
mousetrap-rails (1.4.6)
msgpack (1.2.4)
multi_json (1.13.1)
multi_xml (0.6.0)
@ -575,9 +538,9 @@ 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)
parser (2.5.3.0)
ast (~> 2.4.0)
parslet (1.8.2)
peek (1.0.1)
@ -605,7 +568,6 @@ GEM
pg (0.18.4)
po_to_json (1.0.1)
json (>= 1.6.0)
posix-spawn (0.3.13)
powerpack (0.1.1)
premailer (1.10.4)
addressable
@ -629,6 +591,10 @@ GEM
pry-rails (0.3.6)
pry (>= 0.10.4)
public_suffix (3.0.3)
puma (3.12.0)
puma_worker_killer (0.1.0)
get_process_mem (~> 0.2)
puma (>= 2.7, < 4)
pyu-ruby-sasl (0.0.3.3)
rack (1.6.10)
rack-accept (0.4.5)
@ -797,7 +763,7 @@ GEM
rubyzip (1.2.2)
rufus-scheduler (3.4.0)
et-orbi (~> 1.0)
rugged (0.27.4)
rugged (0.27.5)
safe_yaml (1.0.4)
sanitize (4.6.6)
crass (~> 1.0.2)
@ -843,7 +809,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)
@ -876,7 +842,6 @@ GEM
state_machines-activerecord (0.5.1)
activerecord (>= 4.1, < 6.0)
state_machines-activemodel (>= 0.5.0)
stringex (2.8.4)
sys-filesystem (1.1.6)
ffi
sysexits (1.2.0)
@ -968,7 +933,6 @@ DEPENDENCIES
asana (~> 0.6.0)
asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0)
awesome_print
babosa (~> 1.0.2)
@ -1006,6 +970,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
@ -1013,6 +978,7 @@ DEPENDENCIES
flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
flowdock (~> 0.7)
fog-aliyun (~> 0.2.0)
fog-aws (~> 2.0.1)
fog-core (~> 1.44)
@ -1027,18 +993,15 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1)
github-linguist (~> 5.3.3)
gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
gitlab-markup (~> 1.6.4)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
google-api-client (~> 0.23)
google-protobuf (= 3.5.1)
google-protobuf (~> 3.6)
gpgme
grape (~> 1.1)
grape-entity (~> 0.7.1)
@ -1046,7 +1009,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)
@ -1075,7 +1038,6 @@ DEPENDENCIES
method_source (~> 0.8)
mini_magick
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 5.0)
@ -1109,6 +1071,8 @@ DEPENDENCIES
prometheus-client-mmap (~> 0.9.4)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
@ -1187,4 +1151,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.16.4
1.17.1

View file

@ -61,11 +61,6 @@ GEM
asciidoctor (1.5.6.2)
asciidoctor-plantuml (0.0.8)
asciidoctor (~> 1.5)
asset_sync (2.4.0)
activemodel (>= 4.1.0)
fog-core
mime-types (>= 2.99)
unf
ast (2.4.0)
atomic (1.1.99)
attr_encrypted (3.1.0)
@ -277,32 +272,9 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
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)
gitaly-proto (0.123.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
gitlab-gollum-lib (4.2.7.5)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 3.1)
sanitize (~> 4.6.4)
stringex (~> 2.6)
gitlab-grit (2.8.2)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
mime-types (>= 1.16)
posix-spawn (~> 0.3)
gitlab-markup (1.6.4)
gitlab-sidekiq-fetcher (0.3.0)
sidekiq (~> 5)
@ -317,8 +289,6 @@ GEM
rubyntlm (~> 0.5)
globalid (0.4.1)
activesupport (>= 4.2.0)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gon (6.2.0)
actionpack (>= 3.0)
multi_json
@ -330,16 +300,15 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.5.1)
googleapis-common-protos-types (1.0.1)
google-protobuf (3.6.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 +332,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 +436,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)
@ -496,7 +460,6 @@ GEM
mini_mime (1.0.1)
mini_portile2 (2.3.0)
minitest (5.7.0)
mousetrap-rails (1.4.6)
msgpack (1.2.4)
multi_json (1.13.1)
multi_xml (0.6.0)
@ -579,9 +542,9 @@ 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)
parser (2.5.1.2)
ast (~> 2.4.0)
parslet (1.8.2)
peek (1.0.1)
@ -609,7 +572,6 @@ GEM
pg (0.18.4)
po_to_json (1.0.1)
json (>= 1.6.0)
posix-spawn (0.3.13)
powerpack (0.1.1)
premailer (1.10.4)
addressable
@ -633,6 +595,10 @@ GEM
pry-rails (0.3.6)
pry (>= 0.10.4)
public_suffix (3.0.3)
puma (3.12.0)
puma_worker_killer (0.1.0)
get_process_mem (~> 0.2)
puma (>= 2.7, < 4)
pyu-ruby-sasl (0.0.3.3)
rack (2.0.5)
rack-accept (0.4.5)
@ -805,7 +771,7 @@ GEM
rubyzip (1.2.2)
rufus-scheduler (3.4.0)
et-orbi (~> 1.0)
rugged (0.27.4)
rugged (0.27.5)
safe_yaml (1.0.4)
sanitize (4.6.6)
crass (~> 1.0.2)
@ -851,7 +817,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)
@ -884,7 +850,6 @@ GEM
state_machines-activerecord (0.5.1)
activerecord (>= 4.1, < 6.0)
state_machines-activemodel (>= 0.5.0)
stringex (2.8.4)
sys-filesystem (1.1.6)
ffi
sysexits (1.2.0)
@ -977,7 +942,6 @@ DEPENDENCIES
asana (~> 0.6.0)
asciidoctor (~> 1.5.6)
asciidoctor-plantuml (= 0.0.8)
asset_sync (~> 2.4)
attr_encrypted (~> 3.1.0)
awesome_print
babosa (~> 1.0.2)
@ -1015,6 +979,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
@ -1022,6 +987,7 @@ DEPENDENCIES
flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
flowdock (~> 0.7)
fog-aliyun (~> 0.2.0)
fog-aws (~> 2.0.1)
fog-core (~> 1.44)
@ -1036,18 +1002,15 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
gitaly-proto (~> 0.118.1)
github-linguist (~> 5.3.3)
gitaly-proto (~> 0.123.0)
github-markup (~> 1.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
gitlab-markup (~> 1.6.4)
gitlab-sidekiq-fetcher
gitlab-styles (~> 2.4)
gitlab_omniauth-ldap (~> 2.0.4)
gon (~> 6.2)
google-api-client (~> 0.23)
google-protobuf (= 3.5.1)
google-protobuf (~> 3.6)
gpgme
grape (~> 1.1)
grape-entity (~> 0.7.1)
@ -1055,7 +1018,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)
@ -1084,7 +1047,6 @@ DEPENDENCIES
method_source (~> 0.8)
mini_magick
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 5.0)
@ -1118,6 +1080,8 @@ DEPENDENCIES
prometheus-client-mmap (~> 0.9.4)
pry-byebug (~> 3.4.1)
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
@ -1196,4 +1160,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
1.16.4
1.17.1

View file

@ -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

View file

@ -83,7 +83,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL/OpenSUSE
- Ruby (MRI) 2.3
- Ruby (MRI) 2.4
- Git 2.8.4+
- Redis 2.8+
- PostgreSQL (preferred) or MySQL

View file

@ -1 +1 @@
11.4.9
11.5.3

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -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

View file

@ -6,10 +6,12 @@ import Pager from './pager';
import { localTimeAgo } from './lib/utils/datetime_utility';
export default class Activities {
constructor() {
Pager.init(20, true, false, data => data, this.updateTooltips);
constructor(container = '') {
this.container = container;
$('.event-filter-link').on('click', (e) => {
Pager.init(20, true, false, data => data, this.updateTooltips, this.container);
$('.event-filter-link').on('click', e => {
e.preventDefault();
this.toggleFilter(e.currentTarget);
this.reloadActivities();
@ -22,7 +24,7 @@ export default class Activities {
reloadActivities() {
$('.content_list').html('');
Pager.init(20, true, false, data => data, this.updateTooltips);
Pager.init(20, true, false, data => data, this.updateTooltips, this.container);
}
toggleFilter(sender) {

View file

@ -1,12 +1,14 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
import Tooltip from '~/vue_shared/directives/tooltip';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
export default {
name: 'Badge',
components: {
Icon,
Tooltip,
GlLoadingIcon,
},
directives: {
Tooltip,

View file

@ -4,6 +4,7 @@ import { mapActions, mapState } from 'vuex';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
import createEmptyBadge from '../empty_badge';
import Badge from './badge.vue';
@ -14,6 +15,7 @@ export default {
components: {
Badge,
LoadingButton,
GlLoadingIcon,
},
props: {
isEditing: {

View file

@ -1,5 +1,6 @@
<script>
import { mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
import BadgeListRow from './badge_list_row.vue';
import { GROUP_BADGE } from '../constants';
@ -7,6 +8,7 @@ export default {
name: 'BadgeList',
components: {
BadgeListRow,
GlLoadingIcon,
},
computed: {
...mapState(['badges', 'isLoading', 'kind']),

View file

@ -2,6 +2,7 @@
import { mapActions, mapState } from 'vuex';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
import { PROJECT_BADGE } from '../constants';
import Badge from './badge.vue';
@ -10,6 +11,7 @@ export default {
components: {
Badge,
Icon,
GlLoadingIcon,
},
props: {
badge: {

View file

@ -51,7 +51,7 @@ export default function initCopyToClipboard() {
* the last minute to deconstruct this JSON hash and set the `text/plain` and `text/x-gfm` copy
* data types to the intended values.
*/
$(document).on('copy', 'body > textarea[readonly]', (e) => {
$(document).on('copy', 'body > textarea[readonly]', e => {
const { clipboardData } = e.originalEvent;
if (!clipboardData) return;

View file

@ -2,7 +2,9 @@ import $ from 'jquery';
$(() => {
$('body').on('click', '.js-details-target', function target() {
$(this).closest('.js-details-container').toggleClass('open');
$(this)
.closest('.js-details-container')
.toggleClass('open');
});
// Show details content. Hides link after click.
@ -13,7 +15,9 @@ $(() => {
//
$('body').on('click', '.js-details-expand', function expand(e) {
e.preventDefault();
$(this).next('.js-details-content').removeClass('hide');
$(this)
.next('.js-details-content')
.removeClass('hide');
$(this).hide();
const truncatedItem = $(this).siblings('.js-details-short');

View file

@ -1,4 +1,4 @@
/* eslint-disable object-shorthand, no-unused-vars, no-use-before-define, max-len, no-restricted-syntax, guard-for-in, no-continue */
/* eslint-disable object-shorthand, no-unused-vars, no-use-before-define, no-restricted-syntax, guard-for-in, no-continue */
import $ from 'jquery';
import _ from 'underscore';
@ -34,7 +34,7 @@ const gfmRules = {
},
},
AutolinkFilter: {
'a'(el, text) {
a(el, text) {
// Fallback on the regular MarkdownFilter's `a` handler.
if (text !== el.getAttribute('href')) return false;
@ -60,7 +60,7 @@ const gfmRules = {
},
},
ImageLazyLoadFilter: {
'img'(el, text) {
img(el, text) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
},
},
@ -71,7 +71,7 @@ const gfmRules = {
return CopyAsGFM.nodeToGFM(videoEl);
},
'video'(el) {
video(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`;
},
},
@ -118,11 +118,14 @@ const gfmRules = {
'a[name]:not([href]):empty'(el) {
return el.outerHTML;
},
'dl'(el, text) {
let lines = text.replace(/\n\n/g, '\n').trim().split('\n');
dl(el, text) {
let lines = text
.replace(/\n\n/g, '\n')
.trim()
.split('\n');
// Add two spaces to the front of subsequent list items lines,
// or leave the line entirely blank.
lines = lines.map((l) => {
lines = lines.map(l => {
const line = l.trim();
if (line.length === 0) return '';
@ -151,12 +154,15 @@ const gfmRules = {
// Prefixes lines with 4 spaces if the code contains triple backticks
if (lang === '' && text.match(/^```/gm)) {
return text.split('\n').map((l) => {
return text
.split('\n')
.map(l => {
const line = l.trim();
if (line.length === 0) return '';
return ` ${line}`;
}).join('\n');
})
.join('\n');
}
return `\`\`\`${lang}\n${text}\n\`\`\``;
@ -167,11 +173,11 @@ const gfmRules = {
},
},
MarkdownFilter: {
'br'(el) {
br(el) {
// Two spaces at the end of a line are turned into a BR
return ' ';
},
'code'(el, text) {
code(el, text) {
let backtickCount = 1;
const backtickMatch = text.match(/`+/);
if (backtickMatch) {
@ -183,27 +189,31 @@ const gfmRules = {
return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
},
'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
blockquote(el, text) {
return text
.trim()
.split('\n')
.map(s => `> ${s}`.trim())
.join('\n');
},
'img'(el) {
img(el) {
const imageSrc = el.src;
const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : el.dataset.src || '';
return `![${el.getAttribute('alt')}](${imageUrl})`;
},
'a.anchor'(el, text) {
// Don't render a Markdown link for the anchor link inside a heading
return text;
},
'a'(el, text) {
a(el, text) {
return `[${text}](${el.getAttribute('href')})`;
},
'li'(el, text) {
li(el, text) {
const lines = text.trim().split('\n');
const firstLine = `- ${lines.shift()}`;
// Add four spaces to the front of subsequent list items lines,
// or leave the line entirely blank.
const nextLines = lines.map((s) => {
const nextLines = lines.map(s => {
if (s.trim().length === 0) return '';
return ` ${s}`;
@ -211,49 +221,49 @@ const gfmRules = {
return `${firstLine}\n${nextLines.join('\n')}`;
},
'ul'(el, text) {
ul(el, text) {
return text;
},
'ol'(el, text) {
ol(el, text) {
// LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
return text.replace(/^- /mg, '1. ');
return text.replace(/^- /gm, '1. ');
},
'h1'(el, text) {
h1(el, text) {
return `# ${text.trim()}\n`;
},
'h2'(el, text) {
h2(el, text) {
return `## ${text.trim()}\n`;
},
'h3'(el, text) {
h3(el, text) {
return `### ${text.trim()}\n`;
},
'h4'(el, text) {
h4(el, text) {
return `#### ${text.trim()}\n`;
},
'h5'(el, text) {
h5(el, text) {
return `##### ${text.trim()}\n`;
},
'h6'(el, text) {
h6(el, text) {
return `###### ${text.trim()}\n`;
},
'strong'(el, text) {
strong(el, text) {
return `**${text}**`;
},
'em'(el, text) {
em(el, text) {
return `_${text}_`;
},
'del'(el, text) {
del(el, text) {
return `~~${text}~~`;
},
'hr'(el) {
hr(el) {
// extra leading \n is to ensure that there is a blank line between
// a list followed by an hr, otherwise this breaks old redcarpet rendering
return '\n-----\n';
},
'p'(el, text) {
p(el, text) {
return `${text.trim()}\n`;
},
'table'(el) {
table(el) {
const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false;
@ -263,8 +273,8 @@ const gfmRules = {
return [theadText, tbodyText].join('\n');
},
'thead'(el, text) {
const cells = _.map(el.querySelectorAll('th'), (cell) => {
thead(el, text) {
const cells = _.map(el.querySelectorAll('th'), cell => {
let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = '';
@ -296,7 +306,7 @@ const gfmRules = {
return [text, separatorRow].join('\n');
},
'tr'(el) {
tr(el) {
const cellEls = el.querySelectorAll('td, th');
if (cellEls.length === 0) return false;
@ -315,8 +325,12 @@ export class CopyAsGFM {
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
if (isIOS) return;
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('copy', '.md, .wiki', e => {
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection);
});
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', e => {
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection);
});
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
}
@ -356,7 +370,7 @@ export class CopyAsGFM {
// This will break down when the actual code block contains an uneven
// number of backticks, but this is a rare edge case.
const backtickMatch = textBefore.match(/`/g);
const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
const insideCodeBlock = backtickMatch && backtickMatch.length % 2 === 1;
if (insideCodeBlock) {
return text;
@ -393,7 +407,9 @@ export class CopyAsGFM {
let lineSelector = '.line';
if (target) {
const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
const lineClass = ['left-side', 'right-side'].filter(name =>
target.classList.contains(name),
)[0];
if (lineClass) {
lineSelector = `.line_content.${lineClass} ${lineSelector}`;
}
@ -436,7 +452,8 @@ export class CopyAsGFM {
return node.textContent;
}
const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const respectWhitespace =
respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const text = this.innerGFM(node, respectWhitespace);

View file

@ -32,7 +32,9 @@ export default function renderMath($els) {
Promise.all([
import(/* webpackChunkName: 'katex' */ 'katex'),
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
]).then(([katex]) => {
])
.then(([katex]) => {
renderWithKaTeX($els, katex);
}).catch(() => flash(__('An error occurred while rendering KaTeX')));
})
.catch(() => flash(__('An error occurred while rendering KaTeX')));
}

View file

@ -17,7 +17,8 @@ import flash from '~/flash';
export default function renderMermaid($els) {
if (!$els.length) return;
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
import(/* webpackChunkName: 'mermaid' */ 'mermaid')
.then(mermaid => {
mermaid.initialize({
// mermaid core options
mermaid: {
@ -36,7 +37,7 @@ export default function renderMermaid($els) {
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
mermaid.init(undefined, el, (id) => {
mermaid.init(undefined, el, id => {
const svg = document.getElementById(id);
svg.classList.add('mermaid');
@ -54,7 +55,8 @@ export default function renderMermaid($els) {
svg.appendChild(sourceEl);
});
});
}).catch((err) => {
})
.catch(err => {
flash(`Can't load mermaid module: ${err}`);
});
}

View file

@ -44,7 +44,10 @@ MarkdownPreview.prototype.showPreview = function ($form) {
this.hideReferencedUsers($form);
} else {
preview.addClass('md-preview-loading').text('Loading...');
this.fetchMarkdownPreview(mdText, url, (function (response) {
this.fetchMarkdownPreview(
mdText,
url,
function(response) {
var body;
if (response.body.length > 0) {
({ body } = response);
@ -59,7 +62,8 @@ MarkdownPreview.prototype.showPreview = function ($form) {
if (response.references.commands) {
this.renderReferencedCommands(response.references.commands, $form);
}
}).bind(this));
}.bind(this),
);
}
};
@ -68,7 +72,9 @@ MarkdownPreview.prototype.versionedPreviewPath = function (markdownPreviewPath,
return markdownPreviewPath;
}
return `${markdownPreviewPath}${markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'}markdown_version=${markdownVersion}`;
return `${markdownPreviewPath}${
markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
}markdown_version=${markdownVersion}`;
};
MarkdownPreview.prototype.fetchMarkdownPreview = function(text, url, success) {
@ -79,7 +85,8 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response);
return;
}
axios.post(url, {
axios
.post(url, {
text,
})
.then(({ data }) => {
@ -148,8 +155,14 @@ $(document).on('markdown-preview:show', function (e, $form) {
lastTextareaHeight = lastTextareaPreviewed.height();
// toggle tabs
$form.find(writeButtonSelector).parent().removeClass('active');
$form.find(previewButtonSelector).parent().addClass('active');
$form
.find(writeButtonSelector)
.parent()
.removeClass('active');
$form
.find(previewButtonSelector)
.parent()
.addClass('active');
// toggle content
$form.find('.md-write-holder').hide();
@ -169,8 +182,14 @@ $(document).on('markdown-preview:hide', function (e, $form) {
}
// toggle tabs
$form.find(writeButtonSelector).parent().addClass('active');
$form.find(previewButtonSelector).parent().removeClass('active');
$form
.find(writeButtonSelector)
.parent()
.addClass('active');
$form
.find(previewButtonSelector)
.parent()
.removeClass('active');
// toggle content
$form.find('.md-write-holder').show();

View file

@ -28,7 +28,7 @@ function keyCodeIs(e, keyCode) {
return e.keyCode === keyCode;
}
$(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
$(document).on('keydown.quick_submit', '.js-quick-submit', e => {
// Enter
if (!keyCodeIs(e, 13)) {
return;
@ -55,16 +55,17 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
// If the user tabs to a submit button on a `js-quick-submit` form, display a
// tooltip to let them know they could've used the hotkey
$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function displayTooltip(e) {
$(document).on(
'keyup.quick_submit',
'.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]',
function displayTooltip(e) {
// Tab
if (!keyCodeIs(e, 9)) {
return;
}
const $this = $(this);
const title = isMac() ?
'You can also press &#8984;-Enter' :
'You can also press Ctrl-Enter';
const title = isMac() ? 'You can also press &#8984;-Enter' : 'You can also press Ctrl-Enter';
$this.tooltip({
container: 'body',
@ -74,4 +75,5 @@ $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-q
trigger: 'manual',
});
$this.tooltip('show').one('blur click', () => $this.tooltip('hide'));
});
},
);

View file

@ -18,7 +18,8 @@ import '../commons/bootstrap';
$.fn.requiresInput = function requiresInput() {
const $form = $(this);
const $button = $('button[type=submit], input[type=submit]', $form);
const fieldSelector = 'input[required=required], select[required=required], textarea[required=required]';
const fieldSelector =
'input[required=required], select[required=required], textarea[required=required]';
function requireInput() {
// Collect the input values of *all* required fields

View file

@ -32,16 +32,18 @@ export default class SecretValues {
updateDom(isRevealed) {
const values = this.container.querySelectorAll(this.valueSelector);
values.forEach((value) => {
values.forEach(value => {
value.classList.toggle('hide', !isRevealed);
});
const placeholders = this.container.querySelectorAll(this.placeholderSelector);
placeholders.forEach((placeholder) => {
placeholders.forEach(placeholder => {
placeholder.classList.toggle('hide', isRevealed);
});
this.revealButton.textContent = isRevealed ? n__('Hide value', 'Hide values', values.length) : n__('Reveal value', 'Reveal values', values.length);
this.revealButton.textContent = isRevealed
? n__('Hide value', 'Hide values', values.length)
: n__('Reveal value', 'Reveal values', values.length);
this.revealButton.dataset.secretRevealStatus = isRevealed;
}
}

View file

@ -88,9 +88,11 @@ export default class Shortcuts {
return null;
}
return axios.get(gon.shortcuts_path, {
return axios
.get(gon.shortcuts_path, {
responseType: 'text',
}).then(({ data }) => {
})
.then(({ data }) => {
$.globalEval(data);
if (location && location.length > 0) {

View file

@ -18,9 +18,7 @@ $(() => {
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
$container
.find('.js-toggle-content')
.toggle(toggleState);
$container.find('.js-toggle-content').toggle(toggleState);
}
$('body').on('click', '.js-toggle-button', function toggleButton(e) {

View file

@ -18,12 +18,7 @@ export default class Renderer {
this.loader = new STLLoader();
this.fov = 45;
this.camera = new THREE.PerspectiveCamera(
this.fov,
this.width / this.height,
1,
1000,
);
this.camera = new THREE.PerspectiveCamera(this.fov, this.width / this.height, 1, 1000);
this.scene = new THREE.Scene();
@ -35,10 +30,7 @@ export default class Renderer {
this.setupLight();
// Set up OrbitControls
this.controls = new OrbitControls(
this.camera,
this.renderer.domElement,
);
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.minDistance = 5;
this.controls.maxDistance = 30;
this.controls.enableKeys = false;
@ -51,47 +43,32 @@ export default class Renderer {
antialias: true,
});
this.renderer.setClearColor(0xFFFFFF);
this.renderer.setClearColor(0xffffff);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(
this.width,
this.height,
);
this.renderer.setSize(this.width, this.height);
}
setupLight() {
// Point light illuminates the object
const pointLight = new THREE.PointLight(
0xFFFFFF,
2,
0,
);
const pointLight = new THREE.PointLight(0xffffff, 2, 0);
pointLight.castShadow = true;
this.camera.add(pointLight);
// Ambient light illuminates the scene
const ambientLight = new THREE.AmbientLight(
0xFFFFFF,
1,
);
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
this.scene.add(ambientLight);
}
setupGrid() {
this.grid = new THREE.GridHelper(
20,
20,
0x000000,
0x000000,
);
this.grid = new THREE.GridHelper(20, 20, 0x000000, 0x000000);
this.scene.add(this.grid);
}
loadFile() {
this.loader.load(this.container.dataset.endpoint, (geo) => {
this.loader.load(this.container.dataset.endpoint, geo => {
const obj = new MeshObject(geo);
this.objects.push(obj);
@ -116,30 +93,23 @@ export default class Renderer {
}
render() {
this.renderer.render(
this.scene,
this.camera,
);
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.renderWrapper);
}
changeObjectMaterials(type) {
this.objects.forEach((obj) => {
this.objects.forEach(obj => {
obj.changeMaterial(type);
});
}
setDefaultCameraPosition() {
const obj = this.objects[0];
const radius = (obj.geometry.boundingSphere.radius / 1.5);
const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2));
const radius = obj.geometry.boundingSphere.radius / 1.5;
const dist = radius / Math.sin((this.fov * (Math.PI / 180)) / 2);
this.camera.position.set(
0,
dist + 1,
dist,
);
this.camera.position.set(0, dist + 1, dist);
this.camera.lookAt(this.grid);
this.controls.update();

View file

@ -1,10 +1,6 @@
import {
Matrix4,
MeshLambertMaterial,
Mesh,
} from 'three/build/three.module';
import { Matrix4, MeshLambertMaterial, Mesh } from 'three/build/three.module';
const defaultColor = 0xE24329;
const defaultColor = 0xe24329;
const materials = {
default: new MeshLambertMaterial({
color: defaultColor,
@ -17,10 +13,7 @@ const materials = {
export default class MeshObject extends Mesh {
constructor(geo) {
super(
geo,
materials.default,
);
super(geo, materials.default);
this.geometry.computeBoundingSphere();
@ -29,13 +22,7 @@ export default class MeshObject extends Mesh {
if (this.geometry.boundingSphere.radius > 4) {
const scale = 4 / this.geometry.boundingSphere.radius;
this.geometry.applyMatrix(
new Matrix4().makeScale(
scale,
scale,
scale,
),
);
this.geometry.applyMatrix(new Matrix4().makeScale(scale, scale, scale));
this.geometry.computeBoundingSphere();
this.position.x = -this.geometry.boundingSphere.center.x;

View file

@ -42,7 +42,7 @@ class BalsamiqViewer {
this.initDatabase(loadEvent.target.response);
const previews = this.getPreviews();
previews.forEach((preview) => {
previews.forEach(preview => {
const renderedPreview = this.renderPreview(preview);
container.appendChild(renderedPreview);

View file

@ -45,7 +45,9 @@ export default class BlobFileDropzone {
this.on('addedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.addClass(HIDDEN_CLASS);
$('.dropzone-alerts').html('').hide();
$('.dropzone-alerts')
.html('')
.hide();
});
this.on('removedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
@ -67,13 +69,17 @@ export default class BlobFileDropzone {
},
// Override behavior of adding error underneath preview
error: function(file, errorMessage) {
const stripped = $('<div/>').html(errorMessage).text();
$('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show();
const stripped = $('<div/>')
.html(errorMessage)
.text();
$('.dropzone-alerts')
.html(`Error uploading file: "${stripped}"`)
.show();
this.removeFile(file);
},
});
submitButton.on('click', (e) => {
submitButton.on('click', e => {
e.preventDefault();
e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) {

View file

@ -2,13 +2,15 @@ import { getLocationHash } from '../lib/utils/url_utility';
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const updateLineNumbersOnBlobPermalinks = linksToUpdate => {
const hash = getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => {
const baseHref = permalinkButton.getAttribute('data-original-href') || (() => {
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach(permalinkButton => {
const baseHref =
permalinkButton.getAttribute('data-original-href') ||
(() => {
const href = permalinkButton.getAttribute('href');
permalinkButton.setAttribute('data-original-href', href);
return href;
@ -26,7 +28,7 @@ function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, element
}, 0);
};
blobContentHolder.addEventListener('click', (e) => {
blobContentHolder.addEventListener('click', e => {
if (e.target.matches(lineNumberSelector)) {
updateBlameAndBlobPermalinkCb();
}

View file

@ -45,15 +45,11 @@ export default class FileTemplateSelector {
}
renderLoading() {
this.$loadingIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
this.$loadingIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
renderLoaded() {
this.$loadingIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
this.$loadingIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
}
reportSelection(options) {

View file

@ -40,13 +40,14 @@ export default () => {
},
methods: {
loadFile() {
axios.get(el.dataset.endpoint)
axios
.get(el.dataset.endpoint)
.then(res => res.data)
.then((data) => {
.then(data => {
this.json = data;
this.loading = false;
})
.catch((e) => {
.catch(e => {
if (e.status !== 200) {
this.loadError = true;
}

View file

@ -13,7 +13,7 @@ export default class SketchLoader {
return this.getZipFile()
.then(data => JSZip.loadAsync(data))
.then(asyncResult => asyncResult.files['previews/preview.png'].async('uint8array'))
.then((content) => {
.then(content => {
const url = window.URL || window.webkitURL;
const blob = new Blob([new Uint8Array(content)], {
type: 'image/png',

View file

@ -3,8 +3,8 @@ import Renderer from './3d_viewer';
export default () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer'));
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
el.addEventListener('click', (e) => {
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach(el => {
el.addEventListener('click', e => {
const { target } = e;
e.preventDefault();

View file

@ -81,14 +81,10 @@ export default class TemplateSelector {
}
startLoadingSpinner() {
this.$dropdownIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
stopLoadingSpinner() {
this.$dropdownIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
this.$dropdownIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
}
}

View file

@ -22,7 +22,7 @@ export default class BlobLicenseSelector extends FileTemplateSelector {
search: {
fields: ['name'],
},
clicked: (options) => {
clicked: options => {
const { e } = options;
const el = options.$el;
const query = options.selectedObj;

View file

@ -21,5 +21,4 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector {
text: item => item.name,
});
}
}

View file

@ -22,9 +22,8 @@ export default class BlobViewer {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
if (!viewer || !viewer.dataset.richType) return;
const initViewer = promise => promise
.then(module => module.default(viewer))
.catch((error) => {
const initViewer = promise =>
promise.then(module => module.default(viewer)).catch(error => {
Flash('Error loading file viewer.');
throw error;
});
@ -79,8 +78,7 @@ export default class BlobViewer {
initBindings() {
if (this.switcherBtns.length) {
Array.from(this.switcherBtns)
.forEach((el) => {
Array.from(this.switcherBtns).forEach(el => {
el.addEventListener('click', this.switchViewHandler.bind(this));
});
}
@ -109,7 +107,10 @@ export default class BlobViewer {
this.copySourceBtn.setAttribute('title', 'Copy source to clipboard');
this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) {
this.copySourceBtn.setAttribute('title', 'Wait for the source to load to copy it to the clipboard');
this.copySourceBtn.setAttribute(
'title',
'Wait for the source to load to copy it to the clipboard',
);
this.copySourceBtn.classList.add('disabled');
} else {
this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard');
@ -147,7 +148,7 @@ export default class BlobViewer {
this.toggleCopyButtonState();
BlobViewer.loadViewer(newViewer)
.then((viewer) => {
.then(viewer => {
$(viewer).renderGFM();
this.$fileHolder.trigger('highlight:line');
@ -168,8 +169,7 @@ export default class BlobViewer {
viewer.setAttribute('data-loading', 'true');
return axios.get(url)
.then(({ data }) => {
return axios.get(url).then(({ data }) => {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');

View file

@ -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);
}

View file

@ -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));
}
}

View file

@ -1,25 +1,20 @@
/* eslint-disable comma-dangle */
import Sortable from 'sortablejs';
import Vue from 'vue';
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: {
@ -49,8 +44,8 @@ gl.issueBoards.Board = Vue.extend({
},
data() {
return {
detailIssue: Store.detail,
filter: Store.filter,
detailIssue: boardsStore.detail,
filter: boardsStore.filter,
};
},
computed: {
@ -58,44 +53,47 @@ gl.issueBoards.Board = Vue.extend({
const { issuesSize } = this.list;
return `${n__('%d issue', '%d issues', issuesSize)}`;
},
isNewIssueShown() {
return this.list.type === 'backlog' || (!this.disabled && this.list.type !== 'closed');
},
},
watch: {
filter: {
handler() {
this.list.page = 1;
this.list.getIssues(true)
.catch(() => {
this.list.getIssues(true).catch(() => {
// TODO: handle request error
});
},
deep: true,
}
},
},
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();
onEnd: e => {
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);
});
}
}
},
});
this.sortable = Sortable.create(this.$el.parentNode, this.sortableOptions);
},
created() {
if (this.list.isExpandable && AccessorUtilities.isLocalStorageAccessSafe()) {
const isCollapsed = localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false';
const isCollapsed =
localStorage.getItem(`boards.${this.boardId}.${this.list.type}.expanded`) === 'false';
this.list.isExpanded = !isCollapsed;
}
@ -109,7 +107,10 @@ gl.issueBoards.Board = Vue.extend({
this.list.isExpanded = !this.list.isExpanded;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
localStorage.setItem(`boards.${this.boardId}.${this.list.type}.expanded`, this.list.isExpanded);
localStorage.setItem(
`boards.${this.boardId}.${this.list.type}.expanded`,
this.list.isExpanded,
);
}
}
},

View file

@ -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,35 +29,34 @@ export default {
});
});
Store.state.lists = _.sortBy(Store.state.lists, 'position');
boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
// Save the labels
gl.boardService.generateDefaultLists()
gl.boardService
.generateDefaultLists()
.then(res => res.data)
.then((data) => {
data.forEach((listObj) => {
const list = Store.findList('title', listObj.title);
.then(data => {
data.forEach(listObj => {
const list = boardsStore.findList('title', listObj.title);
list.id = listObj.id;
list.label.id = listObj.label.id;
list.getIssues()
.catch(() => {
list.getIssues().catch(() => {
// TODO: handle request error
});
});
})
.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),
},
};
</script>
<template>

View file

@ -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;
}
}
},

View file

@ -1,12 +1,7 @@
/* eslint-disable comma-dangle, 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,
@ -17,9 +12,10 @@ gl.issueBoards.BoardDelete = Vue.extend({
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();
}
}
}
},
},
});

View file

@ -1,16 +1,18 @@
<script>
import Sortable from 'sortablejs';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
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',
components: {
boardCard,
boardNewIssue,
GlLoadingIcon,
},
props: {
groupId: {
@ -46,7 +48,7 @@ export default {
data() {
return {
scrollOffset: 250,
filters: Store.state.filters,
filters: boardsStore.state.filters,
showCount: false,
showIssueForm: false,
};
@ -61,11 +63,12 @@ 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(() => {
this.list.getIssues(false).catch(() => {
// TODO: handle request error
});
}
@ -83,7 +86,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 +111,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 +124,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 +138,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 +205,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();
}
},

View file

@ -1,17 +1,16 @@
<script>
import $ from 'jquery';
import { Button } from '@gitlab-org/gitlab-ui';
import { GlButton } 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',
components: {
ProjectSelect,
'gl-button': Button,
GlButton,
},
props: {
groupId: {
@ -63,13 +62,14 @@ export default {
eventHub.$emit(`scroll-board-list-${this.list.id}`);
this.cancel();
return this.list.newIssue(issue)
return this.list
.newIssue(issue)
.then(() => {
// 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

View file

@ -1,4 +1,4 @@
/* eslint-disable comma-dangle, no-new */
/* eslint-disable no-new */
import $ from 'jquery';
import Vue from 'vue';
@ -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,
@ -55,14 +51,16 @@ gl.issueBoards.BoardSidebar = Vue.extend({
return this.issue.labels && this.issue.labels.length;
},
labelDropdownTitle() {
return this.hasLabels ? sprintf(__('%{firstLabel} +%{labelCount} more'), {
return this.hasLabels
? sprintf(__('%{firstLabel} +%{labelCount} more'), {
firstLabel: this.issue.labels[0].title,
labelCount: this.issue.labels.length - 1
}) : __('Label');
labelCount: this.issue.labels.length - 1,
})
: __('Label');
},
selectedLabels() {
return this.hasLabels ? this.issue.labels.map(l => l.title).join(',') : '';
}
},
},
watch: {
detail: {
@ -75,14 +73,16 @@ gl.issueBoards.BoardSidebar = Vue.extend({
});
$('.js-issue-board-sidebar', this.$el).each((i, el) => {
$(el).data('glDropdown').clearMenu();
$(el)
.data('glDropdown')
.clearMenu();
});
}
this.issue = this.detail.issue;
this.list = this.detail.list;
},
deep: true
deep: true,
},
},
created() {
@ -117,18 +117,19 @@ 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;
})

View file

@ -1,17 +1,24 @@
<script>
import $ from 'jquery';
import { GlTooltipDirective } from '@gitlab-org/gitlab-ui';
import { sprintf, __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.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 IssueDueDate from './issue_due_date.vue';
import IssueTimeEstimate from './issue_time_estimate.vue';
import boardsStore from '../stores/boards_store';
export default {
components: {
Icon,
UserAvatarLink,
TooltipOnTruncate,
IssueDueDate,
IssueTimeEstimate,
},
directives: {
tooltip,
GlTooltip: GlTooltipDirective,
},
props: {
issue: {
@ -44,8 +51,8 @@
},
data() {
return {
limitBeforeCounter: 3,
maxRender: 4,
limitBeforeCounter: 2,
maxRender: 3,
maxCounter: 99,
};
},
@ -54,7 +61,9 @@
return this.issue.assignees.length - this.limitBeforeCounter;
},
assigneeCounterTooltip() {
return `${this.assigneeCounterLabel} more`;
const { numberOverLimit, maxCounter } = this;
const count = numberOverLimit > maxCounter ? maxCounter : numberOverLimit;
return sprintf(__('%{count} more assignees'), { count });
},
assigneeCounterLabel() {
if (this.numberOverLimit > this.maxCounter) {
@ -79,6 +88,10 @@
showLabelFooter() {
return this.issue.labels.find(l => this.showLabel(l)) !== undefined;
},
issueReferencePath() {
const { referencePath, groupId } = this.issue;
return !groupId ? referencePath.split('#')[0] : null;
},
},
methods: {
isIndexLessThanlimit(index) {
@ -95,11 +108,9 @@
return index < this.limitBeforeCounter;
},
assigneeUrl(assignee) {
if (!assignee) return '';
return `${this.rootPath}${assignee.username}`;
},
assigneeUrlTitle(assignee) {
return `Assigned to ${assignee.name}`;
},
avatarUrlTitle(assignee) {
return `Avatar for ${assignee.name}`;
},
@ -107,24 +118,34 @@
if (!label.id) return false;
return true;
},
filterByLabel(label, e) {
filterByLabel(label) {
if (!this.updateFilters) return;
const labelTitle = encodeURIComponent(label.title);
const filter = `label_name[]=${labelTitle}`;
this.applyFilter(filter);
},
filterByWeight(weight) {
if (!this.updateFilters) return;
const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
const labelTitle = encodeURIComponent(label.title);
const param = `label_name[]=${labelTitle}`;
const labelIndex = filterPath.indexOf(param);
$(e.currentTarget).tooltip('hide');
const issueWeight = encodeURIComponent(weight);
const filter = `weight=${issueWeight}`;
if (labelIndex === -1) {
filterPath.push(param);
this.applyFilter(filter);
},
applyFilter(filter) {
const filterPath = boardsStore.filter.path.split('&');
const filterIndex = filterPath.indexOf(filter);
if (filterIndex === -1) {
filterPath.push(filter);
} else {
filterPath.splice(labelIndex, 1);
filterPath.splice(filterIndex, 1);
}
gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
boardsStore.filter.path = filterPath.join('&');
Store.updateFiltersUrl();
boardsStore.updateFiltersUrl();
eventHub.$emit('updateTokens');
},
@ -140,24 +161,62 @@
<template>
<div>
<div class="board-card-header">
<h4 class="board-card-title">
<i
<h4 class="board-card-title append-bottom-0 prepend-top-0">
<icon
v-if="issue.confidential"
class="fa fa-eye-slash confidential-icon"
aria-hidden="true"
></i>
<a
v-gl-tooltip
name="eye-slash"
:title="__('Confidential')"
class="confidential-icon append-right-4"
:aria-label="__('Confidential')"
/><a
:href="issue.path"
:title="issue.title"
class="js-no-trigger"
@mousemove.stop>{{ issue.title }}</a>
<span
v-if="issueId"
class="board-card-number append-right-5"
>
{{ issue.referencePath }}
</span>
</h4>
</div>
<div
v-if="showLabelFooter"
class="board-card-labels prepend-top-4 d-flex flex-wrap"
>
<button
v-for="label in issue.labels"
v-if="showLabel(label)"
:key="label.id"
v-gl-tooltip
:style="labelStyle(label)"
:title="label.description"
class="badge color-label append-right-4 prepend-top-4"
type="button"
@click="filterByLabel(label)"
>
{{ label.title }}
</button>
</div>
<div class="board-card-footer d-flex justify-content-between align-items-end">
<div class="d-flex align-items-start flex-wrap-reverse board-card-number-container js-board-card-number-container">
<span
v-if="issue.referencePath"
class="board-card-number d-flex append-right-8 prepend-top-8"
>
<tooltip-on-truncate
v-if="issueReferencePath"
:title="issueReferencePath"
placement="bottom"
class="board-issue-path block-truncated bold"
>{{ issueReferencePath }}</tooltip-on-truncate>#{{ issue.iid }}
</span>
<span class="board-info-items prepend-top-8 d-inline-block">
<issue-due-date
v-if="issue.dueDate"
:date="issue.dueDate"
/><issue-time-estimate
v-if="issue.timeEstimate"
:estimate="issue.timeEstimate"
/>
</span>
</div>
<div class="board-card-assignee">
<user-avatar-link
v-for="(assignee, index) in issue.assignees"
@ -166,38 +225,26 @@
:link-href="assigneeUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
:img-src="assignee.avatar"
:tooltip-text="assigneeUrlTitle(assignee)"
:img-size="24"
class="js-no-trigger"
tooltip-placement="bottom"
/>
>
<span class="js-assignee-tooltip">
<span class="bold d-block">Assignee</span>
{{ assignee.name }}
<span class="text-white-50">@{{ assignee.username }}</span>
</span>
</user-avatar-link>
<span
v-if="shouldRenderCounter"
v-tooltip
v-gl-tooltip
:title="assigneeCounterTooltip"
class="avatar-counter"
data-placement="bottom"
>
{{ assigneeCounterLabel }}
</span>
</div>
</div>
<div
v-if="showLabelFooter"
class="board-card-footer"
>
<button
v-for="label in issue.labels"
v-if="showLabel(label)"
:key="label.id"
v-tooltip
:style="labelStyle(label)"
:title="label.description"
class="badge color-label"
type="button"
data-container="body"
@click="filterByLabel(label, $event)"
>
{{ label.title }}
</button>
</div>
</div>
</template>

View file

@ -0,0 +1,90 @@
<script>
import dateFormat from 'dateformat';
import { GlTooltip } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
import { __ } from '~/locale';
import { getDayDifference, getTimeago, dateInWords } from '~/lib/utils/datetime_utility';
export default {
components: {
Icon,
GlTooltip,
},
props: {
date: {
type: String,
required: true,
},
},
computed: {
title() {
const timeago = getTimeago();
const { timeDifference, standardDateFormat } = this;
const formatedDate = standardDateFormat;
if (timeDifference >= -1 && timeDifference < 7) {
return `${timeago.format(this.issueDueDate)} (${formatedDate})`;
}
return timeago.format(this.issueDueDate);
},
body() {
const { timeDifference, issueDueDate, standardDateFormat } = this;
if (timeDifference === 0) {
return __('Today');
} else if (timeDifference === 1) {
return __('Tomorrow');
} else if (timeDifference === -1) {
return __('Yesterday');
} else if (timeDifference > 0 && timeDifference < 7) {
return dateFormat(issueDueDate, 'dddd', true);
}
return standardDateFormat;
},
issueDueDate() {
return new Date(this.date);
},
timeDifference() {
const today = new Date();
return getDayDifference(today, this.issueDueDate);
},
isPastDue() {
if (this.timeDifference >= 0) return false;
return true;
},
standardDateFormat() {
const today = new Date();
const isDueInCurrentYear = today.getFullYear() === this.issueDueDate.getFullYear();
return dateInWords(this.issueDueDate, true, isDueInCurrentYear);
},
},
};
</script>
<template>
<span>
<span
ref="issueDueDate"
class="board-card-info card-number"
>
<icon
:class="{'text-danger': isPastDue, 'board-card-info-icon': true}"
name="calendar"
/><time
:class="{'text-danger': isPastDue}"
datetime="date"
class="board-card-info-text">{{ body }}</time>
</span>
<gl-tooltip
:target="() => $refs.issueDueDate"
placement="bottom"
>
<span class="bold">{{ __('Due date') }}</span>
<br />
<span :class="{'text-danger-muted': isPastDue}">{{ title }}</span>
</gl-tooltip>
</span>
</template>

View file

@ -0,0 +1,48 @@
<script>
import { GlTooltip } from '@gitlab-org/gitlab-ui';
import Icon from '~/vue_shared/components/icon.vue';
import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
export default {
components: {
Icon,
GlTooltip,
},
props: {
estimate: {
type: Number,
required: true,
},
},
computed: {
title() {
return stringifyTime(parseSeconds(this.estimate), true);
},
timeEstimate() {
return stringifyTime(parseSeconds(this.estimate));
},
},
};
</script>
<template>
<span>
<span
ref="issueTimeEstimate"
class="board-card-info card-number"
>
<icon
name="hourglass"
css-classes="board-card-info-icon"
/><time class="board-card-info-text">{{ timeEstimate }}</time>
</span>
<gl-tooltip
:target="() => $refs.issueTimeEstimate"
placement="bottom"
class="js-issue-time-estimate"
>
<span class="bold d-block">{{ __('Time estimate') }}</span>
{{ title }}
</gl-tooltip>
</span>
</template>

View file

@ -20,7 +20,7 @@ export default {
computed: {
contents() {
const obj = {
title: 'You haven\'t added any issues to your project yet',
title: "You haven't added any issues to your project yet",
content: `
An issue can be a bug, a todo or a feature request that needs to be
discussed in a project. Besides, issues are searchable and filterable.
@ -28,7 +28,7 @@ export default {
};
if (this.activeTab === 'selected') {
obj.title = 'You haven\'t selected any issues yet';
obj.title = "You haven't selected any issues yet";
obj.content = `
Go back to <strong>Open issues</strong> and select some issues
to add to your board.

View file

@ -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: {
@ -41,19 +42,17 @@ export default {
const req = this.buildUpdateRequest(list);
// Post the data to the backend
gl.boardService
.bulkUpdate(issueIds, req)
.catch(() => {
gl.boardService.bulkUpdate(issueIds, req).catch(() => {
Flash(__('Failed to update issues, please try again.'));
selectedIssues.forEach((issue) => {
selectedIssues.forEach(issue => {
list.removeIssue(issue);
list.issuesSize -= 1;
});
});
// Add the issues on the frontend
selectedIssues.forEach((issue) => {
selectedIssues.forEach(issue => {
list.addIssue(issue);
list.issuesSize += 1;
});

View file

@ -6,6 +6,7 @@
import ModalFooter from './footer.vue';
import EmptyState from './empty_state.vue';
import ModalStore from '../../stores/modal_store';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
export default {
components: {
@ -13,6 +14,7 @@
ModalHeader,
ModalList,
ModalFooter,
GlLoadingIcon,
},
props: {
newIssuePath: {
@ -107,7 +109,8 @@
loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false;
return gl.boardService.getBacklog({
return gl.boardService
.getBacklog({
...urlParamsToObject(this.filter.path),
page: this.page,
per: this.perPage,

View file

@ -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>

View file

@ -1,15 +1,18 @@
<script>
import { Link } from '@gitlab-org/gitlab-ui';
import { GlLink } 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,
GlLink,
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>

View file

@ -4,16 +4,14 @@ import $ from 'jquery';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
import CreateLabelDropdown from '../../create_label';
import boardsStore from '../stores/boards_store';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
const Store = gl.issueBoards.BoardsStore;
$(document).off('created.label').on('created.label', (e, label) => {
Store.new({
$(document)
.off('created.label')
.on('created.label', (e, label) => {
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,23 +21,26 @@ $(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'));
new CreateLabelDropdown(
$this.closest('.dropdown').find('.dropdown-new-label'),
$this.data('namespacePath'),
$this.data('projectPath'),
);
$this.glDropdown({
data(term, callback) {
axios.get($this.attr('data-list-labels-path'))
.then(({ data }) => {
axios.get($this.attr('data-list-labels-path')).then(({ data }) => {
callback(data);
});
},
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}` : ''),
class: active ? `is-active js-board-list-${active.id}` : '',
text: label.title,
href: '#',
});
@ -62,10 +63,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 +75,9 @@ gl.issueBoards.newListDropdownInit = () => {
},
});
Store.state.lists = _.sortBy(Store.state.lists, 'position');
boardsStore.state.lists = _.sortBy(boardsStore.state.lists, 'position');
}
},
});
});
};
}

View file

@ -1,11 +1,17 @@
<script>
import $ from 'jquery';
import _ from 'underscore';
import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon } from '@gitlab-org/gitlab-ui';
import eventHub from '../eventhub';
import Api from '../../api';
export default {
name: 'BoardProjectSelect',
components: {
Icon,
GlLoadingIcon,
},
props: {
groupId: {
type: Number,
@ -42,7 +48,7 @@ export default {
selectable: true,
data: (term, callback) => {
this.loading = true;
return Api.groupProjects(this.groupId, term, {}, projects => {
return Api.groupProjects(this.groupId, term, { with_issues_enabled: true }, projects => {
this.loading = false;
callback(projects);
});
@ -50,7 +56,9 @@ export default {
renderRow(project) {
return `
<li>
<a href='#' class='dropdown-menu-link' data-project-id="${project.id}" data-project-name="${project.name}">
<a href='#' class='dropdown-menu-link' data-project-id="${
project.id
}" data-project-name="${project.name}">
${_.escape(project.name)}
</a>
</li>
@ -78,11 +86,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 +98,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 +111,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">

View file

@ -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.
@ -57,9 +56,7 @@
buildPatchRequest(issue, lists) {
const listLabelIds = lists.map(list => list.label.id);
const labelIds = issue.labels
.map(label => label.id)
.filter(id => !listLabelIds.includes(id));
const labelIds = issue.labels.map(label => label.id).filter(id => !listLabelIds.includes(id));
return {
label_ids: labelIds,

View file

@ -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();
}
}
@ -31,7 +32,7 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
const tokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token');
// Remove all the tokens as they will be replaced by the search manager
[].forEach.call(tokens, (el) => {
[].forEach.call(tokens, el => {
el.parentNode.removeChild(el);
});
@ -49,7 +50,10 @@ export default class FilteredSearchBoards extends FilteredSearchManager {
canEdit(tokenName, tokenValue) {
if (this.cantEdit.includes(tokenName)) return false;
return this.cantEditWithValue.findIndex(token => token.name === tokenName &&
token.value === tokenValue) === -1;
return (
this.cantEditWithValue.findIndex(
token => token.name === tokenName && token.value === tokenValue,
) === -1
);
}
}

View file

@ -14,54 +14,48 @@ 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) => {
const isNavTypeBackForward = window.performance &&
window.performance.navigation.type === NavigationType.TYPE_BACK_FORWARD;
window.addEventListener('pageshow', event => {
const isNavTypeBackForward =
window.performance && window.performance.navigation.type === NavigationType.TYPE_BACK_FORWARD;
if (event.persisted || isNavTypeBackForward) {
window.location.reload();
}
});
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'),
};
},

View file

@ -3,32 +3,33 @@
import $ from 'jquery';
import sortableConfig from '../../sortable/sortable_config';
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.onStart = () => {
$('.has-tooltip').tooltip('hide')
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]; });
Object.keys(obj).forEach(key => {
defaultSortOptions[key] = obj[key];
});
return defaultSortOptions;
};
}

View file

@ -1,4 +1,4 @@
/* eslint-disable no-unused-vars, comma-dangle */
/* eslint-disable no-unused-vars */
/* global ListLabel */
/* global ListMilestone */
/* global ListAssignee */
@ -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) {
@ -29,6 +30,8 @@ class ListIssue {
this.toggleSubscriptionEndpoint = obj.toggle_subscription_endpoint;
this.milestone_id = obj.milestone_id;
this.project_id = obj.project_id;
this.timeEstimate = obj.time_estimate;
this.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
if (obj.project) {
this.project = new IssueProject(obj.project);
@ -38,7 +41,7 @@ class ListIssue {
this.milestone = new ListMilestone(obj.milestone);
}
obj.labels.forEach((label) => {
obj.labels.forEach(label => {
this.labels.push(new ListLabel(label));
});
@ -86,7 +89,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) {
@ -106,9 +109,9 @@ class ListIssue {
issue: {
milestone_id: this.milestone ? this.milestone.id : null,
due_date: this.dueDate,
assignee_ids: this.assignees.length > 0 ? this.assignees.map((u) => u.id) : [0],
label_ids: this.labels.map((label) => label.id)
}
assignee_ids: this.assignees.length > 0 ? this.assignees.map(u => u.id) : [0],
label_ids: this.labels.map(label => label.id),
},
};
if (!data.issue.label_ids.length) {

View file

@ -1,10 +1,11 @@
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len */
/* eslint-disable no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign */
/* global ListIssue */
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,
};

View file

@ -19,7 +19,9 @@ export default class BoardService {
}
static generateIssuePath(boardId, id) {
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${id ? `/${id}` : ''}`;
return `${gon.relative_url_root}/-/boards/${boardId ? `${boardId}` : ''}/issues${
id ? `/${id}` : ''
}`;
}
all() {
@ -54,7 +56,9 @@ export default class BoardService {
getIssuesForList(id, filter = {}) {
const data = { id };
Object.keys(filter).forEach((key) => { data[key] = filter[key]; });
Object.keys(filter).forEach(key => {
data[key] = filter[key];
});
return axios.get(mergeUrlParams(data, this.generateIssuesPath(id)));
}
@ -75,7 +79,9 @@ export default class BoardService {
}
getBacklog(data) {
return axios.get(mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`));
return axios.get(
mergeUrlParams(data, `${gon.relative_url_root}/-/boards/${this.boardId}/issues.json`),
);
}
bulkUpdate(issueIds, extraData = {}) {

View file

@ -1,15 +1,13 @@
/* eslint-disable comma-dangle, no-shadow */
/* eslint-disable no-shadow */
/* global List */
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: '',
@ -57,7 +55,7 @@ gl.issueBoards.BoardsStore = {
},
shouldAddBlankState() {
// Decide whether to add the blank state
return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0]);
return !this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'closed')[0];
},
addBlankState() {
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
@ -66,7 +64,7 @@ gl.issueBoards.BoardsStore = {
id: 'blank',
list_type: 'blank',
title: 'Welcome to your Issue Board!',
position: 0
position: 0,
});
this.state.lists = _.sortBy(this.state.lists, 'position');
@ -76,7 +74,7 @@ gl.issueBoards.BoardsStore = {
Cookies.set('issue_board_welcome_hidden', 'true', {
expires: 365 * 10,
path: ''
path: '',
});
},
welcomeIsHidden() {
@ -104,14 +102,17 @@ gl.issueBoards.BoardsStore = {
if (!issueTo) {
// Check if target list assignee is already present in this issue
if ((listTo.type === 'assignee' && listFrom.type === 'assignee') &&
issue.findAssignee(listTo.assignee)) {
if (
listTo.type === 'assignee' &&
listFrom.type === 'assignee' &&
issue.findAssignee(listTo.assignee)
) {
const targetIssue = listTo.findIssue(issue.id);
targetIssue.removeAssignee(listFrom.assignee);
} else if (listTo.type === 'milestone') {
const currentMilestone = issue.milestone;
const currentLists = this.state.lists
.filter(list => (list.type === 'milestone' && list.id !== listTo.id))
.filter(list => list.type === 'milestone' && list.id !== listTo.id)
.filter(list => list.issues.some(listIssue => issue.id === listIssue.id));
issue.removeMilestone(currentMilestone);
@ -128,7 +129,7 @@ gl.issueBoards.BoardsStore = {
}
if (listTo.type === 'closed' && listFrom.type !== 'backlog') {
issueLists.forEach((list) => {
issueLists.forEach(list => {
list.removeIssue(issue);
});
issue.removeLabels(listLabels);
@ -146,7 +147,7 @@ gl.issueBoards.BoardsStore = {
return (
(listTo.type !== 'label' && listFrom.type === 'assignee') ||
(listTo.type !== 'assignee' && listFrom.type === 'label') ||
(listFrom.type === 'backlog')
listFrom.type === 'backlog'
);
},
moveIssueInList(list, issue, oldIndex, newIndex, idArray) {
@ -156,8 +157,10 @@ gl.issueBoards.BoardsStore = {
list.moveIssue(issue, oldIndex, newIndex, beforeId, afterId);
},
findList(key, val, type = 'label') {
const filteredList = this.state.lists.filter((list) => {
const byType = type ? (list.type === type) || (list.type === 'assignee') || (list.type === 'milestone') : true;
const filteredList = this.state.lists.filter(list => {
const byType = type
? list.type === type || list.type === 'assignee' || list.type === 'milestone'
: true;
return list[key] === val && byType;
});
@ -165,5 +168,18 @@ gl.issueBoards.BoardsStore = {
},
updateFiltersUrl() {
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;

View file

@ -40,7 +40,7 @@ class ModalStore {
toggleAll() {
const select = this.selectedCount() !== this.store.issues.length;
this.store.issues.forEach((issue) => {
this.store.issues.forEach(issue => {
const issueUpdate = issue;
if (issueUpdate.selected !== select) {
@ -69,13 +69,14 @@ class ModalStore {
removeSelectedIssue(issue, forcePurge = false) {
if (this.store.activeTab === 'all' || forcePurge) {
this.store.selectedIssues = this.store.selectedIssues
.filter(fIssue => fIssue.id !== issue.id);
this.store.selectedIssues = this.store.selectedIssues.filter(
fIssue => fIssue.id !== issue.id,
);
}
}
purgeUnselectedIssues() {
this.store.selectedIssues.forEach((issue) => {
this.store.selectedIssues.forEach(issue => {
if (!issue.selected) {
this.removeSelectedIssue(issue, true);
}
@ -87,8 +88,7 @@ class ModalStore {
}
findSelectedIssue(issue) {
return this.store.selectedIssues
.filter(filteredIssue => filteredIssue.id === issue.id)[0];
return this.store.selectedIssues.filter(filteredIssue => filteredIssue.id === issue.id)[0];
}
}

View file

@ -1,6 +1,6 @@
import $ from 'jquery';
export const addTooltipToEl = (el) => {
export const addTooltipToEl = el => {
const textEl = el.querySelector('.js-breadcrumb-item-text');
if (textEl && textEl.scrollWidth > textEl.offsetWidth) {
@ -14,16 +14,17 @@ export default () => {
const breadcrumbs = document.querySelector('.js-breadcrumbs-list');
if (breadcrumbs) {
const topLevelLinks = [...breadcrumbs.children].filter(el => !el.classList.contains('dropdown'))
const topLevelLinks = [...breadcrumbs.children]
.filter(el => !el.classList.contains('dropdown'))
.map(el => el.querySelector('a'))
.filter(el => el);
const $expander = $('.js-breadcrumbs-collapsed-expander');
topLevelLinks.forEach(el => addTooltipToEl(el));
$expander.closest('.dropdown')
.on('show.bs.dropdown hide.bs.dropdown', (e) => {
$('.js-breadcrumbs-collapsed-expander', e.currentTarget).toggleClass('open')
$expander.closest('.dropdown').on('show.bs.dropdown hide.bs.dropdown', e => {
$('.js-breadcrumbs-collapsed-expander', e.currentTarget)
.toggleClass('open')
.tooltip('hide');
});
}

View file

@ -37,11 +37,15 @@ export default class BuildArtifacts {
// We want the tooltip to show if you hover anywhere on the row
// But be placed below and in the middle of the file name
$('.js-artifact-tree-row')
.on('mouseenter', (e) => {
$(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('show');
.on('mouseenter', e => {
$(e.currentTarget)
.find('.js-artifact-tree-tooltip')
.tooltip('show');
})
.on('mouseleave', (e) => {
$(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('hide');
.on('mouseleave', e => {
$(e.currentTarget)
.find('.js-artifact-tree-tooltip')
.tooltip('hide');
});
}
}

View file

@ -7,11 +7,13 @@ import statusCodes from '../lib/utils/http_status';
import VariableList from './ci_variable_list';
function generateErrorBoxContent(errors) {
const errorList = [].concat(errors).map(errorString => `
const errorList = [].concat(errors).map(
errorString => `
<li>
${_.escape(errorString)}
</li>
`);
`,
);
return `
<p>
@ -25,13 +27,7 @@ function generateErrorBoxContent(errors) {
// Used for the variable list on CI/CD projects/groups settings page
export default class AjaxVariableList {
constructor({
container,
saveButton,
errorBox,
formField = 'variables',
saveEndpoint,
}) {
constructor({ container, saveButton, errorBox, formField = 'variables', saveEndpoint }) {
this.container = container;
this.saveButton = saveButton;
this.errorBox = errorBox;
@ -51,25 +47,28 @@ export default class AjaxVariableList {
}
onSaveClicked() {
const loadingIcon = this.saveButton.querySelector('.js-secret-variables-save-loading-icon');
const loadingIcon = this.saveButton.querySelector('.js-ci-variables-save-loading-icon');
loadingIcon.classList.toggle('hide', false);
this.errorBox.classList.toggle('hide', true);
// We use this to prevent a user from changing a key before we have a chance
// to match it up in `updateRowsWithPersistedVariables`
this.variableList.toggleEnableRow(false);
return axios.patch(this.saveEndpoint, {
return axios
.patch(
this.saveEndpoint,
{
variables_attributes: this.variableList.getAllData(),
}, {
},
{
// We want to be able to process the `res.data` from a 400 error response
// and print the validation messages such as duplicate variable keys
validateStatus: status => (
status >= statusCodes.OK &&
status < statusCodes.MULTIPLE_CHOICES
) ||
validateStatus: status =>
(status >= statusCodes.OK && status < statusCodes.MULTIPLE_CHOICES) ||
status === statusCodes.BAD_REQUEST,
})
.then((res) => {
},
)
.then(res => {
loadingIcon.classList.toggle('hide', true);
this.variableList.toggleEnableRow(true);
@ -90,12 +89,15 @@ export default class AjaxVariableList {
}
updateRowsWithPersistedVariables(persistedVariables = []) {
const persistedVariableMap = [].concat(persistedVariables).reduce((variableMap, variable) => ({
const persistedVariableMap = [].concat(persistedVariables).reduce(
(variableMap, variable) => ({
...variableMap,
[variable.key]: variable,
}), {});
}),
{},
);
this.container.querySelectorAll('.js-row').forEach((row) => {
this.container.querySelectorAll('.js-row').forEach(row => {
// If we submitted a row that was destroyed, remove it so we don't try
// to destroy it again which would cause a BE error
const destroyInput = row.querySelector('.js-ci-variable-input-destroy');

View file

@ -16,10 +16,7 @@ function createEnvironmentItem(value) {
}
export default class VariableList {
constructor({
container,
formField,
}) {
constructor({ container, formField }) {
this.$container = $(container);
this.formField = formField;
this.environmentDropdownMap = new WeakMap();
@ -71,7 +68,7 @@ export default class VariableList {
this.initRow(rowEl);
});
this.$container.on('click', '.js-row-remove-button', (e) => {
this.$container.on('click', '.js-row-remove-button', e => {
e.preventDefault();
this.removeRow($(e.currentTarget).closest('.js-row'));
});
@ -81,7 +78,7 @@ export default class VariableList {
.join(',');
// Remove any empty rows except the last row
this.$container.on('blur', inputSelector, (e) => {
this.$container.on('blur', inputSelector, e => {
const $row = $(e.currentTarget).closest('.js-row');
if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) {
@ -136,7 +133,7 @@ export default class VariableList {
$rowClone.removeAttr('data-is-persisted');
// Reset the inputs to their defaults
Object.keys(this.inputMap).forEach((name) => {
Object.keys(this.inputMap).forEach(name => {
const entry = this.inputMap[name];
$rowClone.find(entry.selector).val(entry.default);
});
@ -171,7 +168,7 @@ export default class VariableList {
}
checkIfRowTouched($row) {
return Object.keys(this.inputMap).some((name) => {
return Object.keys(this.inputMap).some(name => {
const entry = this.inputMap[name];
const $el = $row.find(entry.selector);
return $el.length && $el.val() !== entry.default;
@ -190,11 +187,14 @@ export default class VariableList {
getAllData() {
// Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems.
const validRows = this.$container.find('.js-row').toArray().slice(0, -1);
const validRows = this.$container
.find('.js-row')
.toArray()
.slice(0, -1);
return validRows.map((rowEl) => {
return validRows.map(rowEl => {
const resultant = {};
Object.keys(this.inputMap).forEach((name) => {
Object.keys(this.inputMap).forEach(name => {
const entry = this.inputMap[name];
const $input = $(rowEl).find(entry.selector);
if ($input.length) {
@ -207,11 +207,16 @@ export default class VariableList {
}
getEnvironmentValues() {
const valueMap = this.$container.find(this.inputMap.environment_scope.selector).toArray()
.reduce((prevValueMap, envInput) => ({
const valueMap = this.$container
.find(this.inputMap.environment_scope.selector)
.toArray()
.reduce(
(prevValueMap, envInput) => ({
...prevValueMap,
[envInput.value]: envInput.value,
}), {});
}),
{},
);
return Object.keys(valueMap).map(createEnvironmentItem);
}

View file

@ -2,10 +2,7 @@ import $ from 'jquery';
import VariableList from './ci_variable_list';
// Used for the variable list on scheduled pipeline edit page
export default function setupNativeFormVariableList({
container,
formField = 'variables',
}) {
export default function setupNativeFormVariableList({ container, formField = 'variables' }) {
const $container = $(container);
const variableList = new VariableList({

View file

@ -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';
@ -28,6 +28,7 @@ export default class Clusters {
installIngressPath,
installRunnerPath,
installJupyterPath,
installKnativePath,
installPrometheusPath,
managePrometheusPath,
clusterStatus,
@ -49,6 +50,7 @@ export default class Clusters {
installRunnerEndpoint: installRunnerPath,
installPrometheusEndpoint: installPrometheusPath,
installJupyterEndpoint: installJupyterPath,
installKnativeEndpoint: installKnativePath,
});
this.installApplication = this.installApplication.bind(this);
@ -62,7 +64,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 +107,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);

View file

@ -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) {

View file

@ -74,7 +74,9 @@
},
isInstalled() {
return (
this.status === APPLICATION_STATUS.INSTALLED || this.status === APPLICATION_STATUS.UPDATED
this.status === APPLICATION_STATUS.INSTALLED ||
this.status === APPLICATION_STATUS.UPDATED ||
this.status === APPLICATION_STATUS.UPDATING
);
},
hasLogo() {
@ -88,19 +90,24 @@
return `js-cluster-application-row-${this.id}`;
},
installButtonLoading() {
return !this.status ||
return (
!this.status ||
this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_STATUS.INSTALLING ||
this.requestStatus === REQUEST_LOADING;
this.requestStatus === REQUEST_LOADING
);
},
installButtonDisabled() {
// Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but
// we already made a request to install and are just waiting for the real-time
// to sync up.
return ((this.status !== APPLICATION_STATUS.INSTALLABLE
&& this.status !== APPLICATION_STATUS.ERROR) ||
return (
((this.status !== APPLICATION_STATUS.INSTALLABLE &&
this.status !== APPLICATION_STATUS.ERROR) ||
this.requestStatus === REQUEST_LOADING ||
this.requestStatus === REQUEST_SUCCESS) && this.isKnownStatus;
this.requestStatus === REQUEST_SUCCESS) &&
this.isKnownStatus
);
},
installButtonLabel() {
let label;
@ -111,11 +118,16 @@
this.isUnknownStatus
) {
label = s__('ClusterIntegration|Install');
} else if (this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_STATUS.INSTALLING) {
} else if (
this.status === APPLICATION_STATUS.SCHEDULED ||
this.status === APPLICATION_STATUS.INSTALLING
) {
label = s__('ClusterIntegration|Installing');
} else if (this.status === APPLICATION_STATUS.INSTALLED ||
this.status === APPLICATION_STATUS.UPDATED) {
} else if (
this.status === APPLICATION_STATUS.INSTALLED ||
this.status === APPLICATION_STATUS.UPDATED ||
this.status === APPLICATION_STATUS.UPDATING
) {
label = s__('ClusterIntegration|Installed');
}
@ -128,15 +140,12 @@
return s__('ClusterIntegration|Manage');
},
hasError() {
return this.status === APPLICATION_STATUS.ERROR ||
this.requestStatus === REQUEST_FAILURE;
return this.status === APPLICATION_STATUS.ERROR || this.requestStatus === REQUEST_FAILURE;
},
generalErrorDescription() {
return sprintf(
s__('ClusterIntegration|Something went wrong while installing %{title}'), {
return sprintf(s__('ClusterIntegration|Something went wrong while installing %{title}'), {
title: this.title,
},
);
});
},
},
methods: {

View file

@ -1,12 +1,13 @@
<script>
import _ from 'underscore';
import helmInstallIllustration from '@gitlab-org/gitlab-svgs/illustrations/kubernetes-installation.svg';
import helmInstallIllustration from '@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';
import jeagerLogo from 'images/cluster_app_logos/jeager.png';
import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png';
import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png';
import knativeLogo from 'images/cluster_app_logos/knative.png';
import meltanoLogo from 'images/cluster_app_logos/meltano.png';
import prometheusLogo from 'images/cluster_app_logos/prometheus.png';
import { s__, sprintf } from '../../locale';
@ -53,6 +54,7 @@ export default {
jeagerLogo,
jupyterhubLogo,
kubernetesLogo,
knativeLogo,
meltanoLogo,
prometheusLogo,
}),
@ -136,6 +138,9 @@ export default {
jupyterHostname() {
return this.applications.jupyter.hostname;
},
knativeInstalled() {
return this.applications.knative.status === APPLICATION_STATUS.INSTALLED;
},
},
created() {
this.helmInstallIllustration = helmInstallIllustration;
@ -321,7 +326,6 @@ export default {
:request-reason="applications.jupyter.requestReason"
:install-application-request-params="{ hostname: applications.jupyter.hostname }"
:disabled="!helmInstalled"
class="hide-bottom-border rounded-bottom"
title-link="https://jupyterhub.readthedocs.io/en/stable/"
>
<div slot="description">
@ -371,6 +375,58 @@ export default {
</template>
</div>
</application-row>
<application-row
id="knative"
:logo-url="knativeLogo"
:title="applications.knative.title"
:status="applications.knative.status"
:status-reason="applications.knative.statusReason"
:request-status="applications.knative.requestStatus"
:request-reason="applications.knative.requestReason"
:install-application-request-params="{ hostname: applications.knative.hostname}"
:disabled="!helmInstalled"
class="hide-bottom-border rounded-bottom"
title-link="https://github.com/knative/docs"
>
<div slot="description">
<p>
{{ s__(`ClusterIntegration|A Knative build extends Kubernetes
and utilizes existing Kubernetes primitives to provide you with
the ability to run on-cluster container builds from source.
For example, you can write a build that uses Kubernetes-native
resources to obtain your source code from a repository,
build it into container a image, and then run that image.`) }}
</p>
<template v-if="knativeInstalled">
<div class="form-group">
<label for="knative-domainname">
{{ s__('ClusterIntegration|Knative Domain Name:') }}
</label>
<input
id="knative-domainname"
v-model="applications.knative.hostname"
type="text"
class="form-control js-domainname"
readonly
/>
</div>
</template>
<template v-else>
<div class="form-group">
<label for="knative-domainname">
{{ s__('ClusterIntegration|Knative Domain Name:') }}
</label>
<input
id="knative-domainname"
v-model="applications.knative.hostname"
type="text"
class="form-control js-domainname"
/>
</div>
</template>
</div>
</application-row>
</div>
</section>
</template>

View file

@ -6,6 +6,7 @@ export const APPLICATION_STATUS = {
INSTALLING: 'installing',
INSTALLED: 'installed',
UPDATED: 'updated',
UPDATING: 'updating',
ERROR: 'errored',
};
@ -15,3 +16,4 @@ export const REQUEST_SUCCESS = 'request-success';
export const REQUEST_FAILURE = 'request-failure';
export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';

View file

@ -9,6 +9,7 @@ export default class ClusterService {
runner: this.options.installRunnerEndpoint,
prometheus: this.options.installPrometheusEndpoint,
jupyter: this.options.installJupyterEndpoint,
knative: this.options.installKnativeEndpoint,
};
}

View file

@ -1,5 +1,5 @@
import { s__ } from '../../locale';
import { INGRESS, JUPYTER } from '../constants';
import { INGRESS, JUPYTER, KNATIVE } from '../constants';
export default class ClusterStore {
constructor() {
@ -46,6 +46,14 @@ export default class ClusterStore {
requestReason: null,
hostname: null,
},
knative: {
title: s__('ClusterIntegration|Knative'),
status: null,
statusReason: null,
requestStatus: null,
requestReason: null,
hostname: null,
},
},
};
}
@ -76,12 +84,8 @@ export default class ClusterStore {
this.state.status = serverState.status;
this.state.statusReason = serverState.status_reason;
serverState.applications.forEach((serverAppEntry) => {
const {
name: appId,
status,
status_reason: statusReason,
} = serverAppEntry;
serverState.applications.forEach(serverAppEntry => {
const { name: appId, status, status_reason: statusReason } = serverAppEntry;
this.state.applications[appId] = {
...(this.state.applications[appId] || {}),
@ -97,6 +101,9 @@ export default class ClusterStore {
(this.state.applications.ingress.externalIp
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
: '');
} else if (appId === KNATIVE) {
this.state.applications.knative.hostname =
serverAppEntry.hostname || this.state.applications.knative.hostname;
}
});
}

View file

@ -24,36 +24,44 @@ class CommentTypeToggle {
setConfig() {
const config = {
InputSetter: [{
InputSetter: [
{
input: this.noteTypeInput,
valueAttribute: 'data-value',
},
{
input: this.submitButton,
valueAttribute: 'data-submit-text',
}],
},
],
};
if (this.closeButton) {
config.InputSetter.push({
config.InputSetter.push(
{
input: this.closeButton,
valueAttribute: 'data-close-text',
}, {
},
{
input: this.closeButton,
valueAttribute: 'data-close-text',
inputAttribute: 'data-alternative-text',
});
},
);
}
if (this.reopenButton) {
config.InputSetter.push({
config.InputSetter.push(
{
input: this.reopenButton,
valueAttribute: 'data-reopen-text',
}, {
},
{
input: this.reopenButton,
valueAttribute: 'data-reopen-text',
inputAttribute: 'data-alternative-text',
});
},
);
}
return config;

View file

@ -1,4 +1,4 @@
/* eslint-disable func-names, wrap-iife, no-var, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, max-len */
/* eslint-disable func-names, no-var, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, one-var, no-unused-vars, no-return-assign, no-unused-expressions, no-sequences */
import $ from 'jquery';
@ -9,9 +9,14 @@ const viewModes = ['two-up', 'swipe'];
export default class ImageFile {
constructor(file) {
this.file = file;
this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
this.requestImageInfo(
$('.two-up.view .frame.deleted img', this.file),
(function(_this) {
return function(deletedWidth, deletedHeight) {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(
width,
height,
) {
_this.initViewModes();
// Load two-up view after images are loaded
@ -23,30 +28,41 @@ export default class ImageFile {
});
});
};
})(this));
})(this),
);
}
initViewModes() {
const viewMode = viewModes[0];
$('.view-modes', this.file).removeClass('hide');
$('.view-modes-menu', this.file).on('click', 'li', (function(_this) {
$('.view-modes-menu', this.file).on(
'click',
'li',
(function(_this) {
return function(event) {
if (!$(event.currentTarget).hasClass('active')) {
return _this.activateViewMode(event.currentTarget.className);
}
};
})(this));
})(this),
);
return this.activateViewMode(viewMode);
}
activateViewMode(viewMode) {
$('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active');
return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) {
$('.view-modes-menu li', this.file)
.removeClass('active')
.filter('.' + viewMode)
.addClass('active');
return $('.view:visible:not(.' + viewMode + ')', this.file).fadeOut(
200,
(function(_this) {
return function() {
$(".view." + viewMode, _this.file).fadeIn(200);
$('.view.' + viewMode, _this.file).fadeIn(200);
return _this.initView(viewMode);
};
})(this));
})(this),
);
}
initView(viewMode) {
@ -63,7 +79,10 @@ export default class ImageFile {
$body.css('user-select', 'none');
});
$body.off('mouseup').off('mousemove').on('mouseup', function() {
$body
.off('mouseup')
.off('mousemove')
.on('mouseup', function() {
dragging = false;
$body.css('user-select', '');
})
@ -81,24 +100,29 @@ export default class ImageFile {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
$('.frame', view).each((function(_this) {
$('.frame', view)
.each(
(function(_this) {
return function(index, frame) {
var height, width;
width = $(frame).width();
height = $(frame).height();
maxWidth = width > maxWidth ? width : maxWidth;
return maxHeight = height > maxHeight ? height : maxHeight;
return (maxHeight = height > maxHeight ? height : maxHeight);
};
})(this)).css({
})(this),
)
.css({
width: maxWidth,
height: maxHeight
height: maxHeight,
});
return [maxWidth, maxHeight];
}
views = {
'two-up': function() {
return $('.two-up.view .wrap', this.file).each((function(_this) {
return $('.two-up.view .wrap', this.file).each(
(function(_this) {
return function(index, wrap) {
$('img', wrap).each(function() {
var currentWidth;
@ -108,58 +132,68 @@ export default class ImageFile {
}
});
return _this.requestImageInfo($('img', wrap), function(width, height) {
$('.image-info .meta-width', wrap).text(width + "px");
$('.image-info .meta-height', wrap).text(height + "px");
$('.image-info .meta-width', wrap).text(width + 'px');
$('.image-info .meta-height', wrap).text(height + 'px');
return $('.image-info', wrap).removeClass('hide');
});
};
})(this));
})(this),
);
},
'swipe': function() {
swipe() {
var maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
return $('.swipe.view', this.file).each((function(_this) {
return $('.swipe.view', this.file).each(
(function(_this) {
return function(index, view) {
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
ref = _this.prepareFrames(view), [maxWidth, maxHeight] = ref;
(ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
$swipeFrame = $('.swipe-frame', view);
$swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view);
$swipeFrame.css({
width: maxWidth + 16,
height: maxHeight + 28
height: maxHeight + 28,
});
$swipeWrap.css({
width: maxWidth + 1,
height: maxHeight + 2
height: maxHeight + 2,
});
// Set swipeBar left position to match image frame
$swipeBar.css({
left: 1
left: 1,
});
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
_this.initDraggable($swipeBar, wrapPadding, function(e, left) {
if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) {
$swipeWrap.width((maxWidth + 1) - left);
if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) {
$swipeWrap.width(maxWidth + 1 - left);
$swipeBar.css('left', left);
}
});
};
})(this));
})(this),
);
},
'onion-skin': function() {
var dragTrackWidth, maxHeight, maxWidth;
maxWidth = 0;
maxHeight = 0;
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each((function(_this) {
return $('.onion-skin.view', this.file).each(
(function(_this) {
return function(index, view) {
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false;
ref = _this.prepareFrames(view), [maxWidth, maxHeight] = ref;
var $frame,
$track,
$dragger,
$frameAdded,
framePadding,
ref,
dragging = false;
(ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
$frame = $('.onion-skin-frame', view);
$frameAdded = $('.frame.added', view);
$track = $('.drag-track', view);
@ -167,14 +201,14 @@ export default class ImageFile {
$frame.css({
width: maxWidth + 16,
height: maxHeight + 28
height: maxHeight + 28,
});
$('.swipe-wrap', view).css({
width: maxWidth + 1,
height: maxHeight + 2
height: maxHeight + 2,
});
$dragger.css({
left: dragTrackWidth
left: dragTrackWidth,
});
$frameAdded.css('opacity', 1);
@ -189,9 +223,10 @@ export default class ImageFile {
}
});
};
})(this));
}
}
})(this),
);
},
};
requestImageInfo(img, callback) {
const domImg = img.get(0);
@ -199,11 +234,14 @@ export default class ImageFile {
if (domImg.complete) {
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
} else {
return img.on('load', (function(_this) {
return img.on(
'load',
(function(_this) {
return function() {
return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
};
})(this));
})(this),
);
}
}
}

View file

@ -20,10 +20,12 @@ export default () => {
if (pipelineTableViewEl) {
// Update MR and Commits tabs
pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => {
if (event.detail.pipelines &&
pipelineTableViewEl.addEventListener('update-pipelines-count', event => {
if (
event.detail.pipelines &&
event.detail.pipelines.count &&
event.detail.pipelines.count.all) {
event.detail.pipelines.count.all
) {
const badge = document.querySelector('.js-pipelines-mr-count');
badge.textContent = event.detail.pipelines.count.all;

Some files were not shown because too many files have changed in this diff Show more