diff options
770 files changed, 7921 insertions, 4436 deletions
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml index 5aa1a856405..beab406fab7 100644 --- a/.gitlab/ci/docs.gitlab-ci.yml +++ b/.gitlab/ci/docs.gitlab-ci.yml @@ -66,6 +66,10 @@ docs lint: - scripts/lint-changelog-yaml - mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX - cd /tmp/gitlab-docs + # Lint Markdown + # https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md + - bundle exec mdl content/$DOCS_GITLAB_REPO_SUFFIX/**/*.md --rules \ + MD032 # Build HTML from Markdown - bundle exec nanoc # Check the internal links diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 75c79bb213f..cd6953a6ac2 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -72,6 +72,7 @@ gitlab:assets:compile pull-cache: except: - master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ee + - /(^docs[\/-].*|.*-docs$)/ .compile-assets-metadata: extends: .dedicated-runner diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index b7ef4b31743..eb50f08c1a7 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -28,11 +28,15 @@ policy: pull stage: test -.dedicated-no-docs-pull-cache-job: - extends: .dedicated-pull-cache-job +.no-docs: except: - /(^docs[\/-].*|.*-docs$)/ +.dedicated-no-docs-pull-cache-job: + extends: + - .dedicated-pull-cache-job + - .no-docs + .dedicated-no-docs-and-no-qa-pull-cache-job: extends: .dedicated-pull-cache-job except: @@ -45,6 +49,12 @@ variables: SETUP_DB: "false" +# Jobs that need a dedicated runner, with no cache +.dedicated-no-docs: + extends: + - .dedicated-runner + - .no-docs + .single-script-job-dedicated-runner: extends: .dedicated-runner image: ruby:2.6-alpine diff --git a/.gitlab/ci/memory.gitlab-ci.yml b/.gitlab/ci/memory.gitlab-ci.yml index 50b843df585..2f3907a331a 100644 --- a/.gitlab/ci/memory.gitlab-ci.yml +++ b/.gitlab/ci/memory.gitlab-ci.yml @@ -5,15 +5,15 @@ memory-static: # Loads each of gems in the Gemfile and checks how much memory they consume when they are required. # 'derailed_benchmarks' internally uses 'get_process_mem' - - scripts/memory-static 'tmp/memory_static_full_report.txt' 'tmp/memory_static_metrics.txt' + - bundle exec derailed bundle:mem > tmp/memory_bundle_mem.txt + - scripts/generate-gems-size-metrics-static tmp/memory_bundle_mem.txt >> 'tmp/memory_metrics.txt' # Outputs detailed information about objects created while gems are loaded. # 'derailed_benchmarks' internally uses 'memory_profiler' - - scripts/memory-static-objects 'tmp/memory_static_objects_full_report.txt' 'tmp/memory_static_metrics.txt' + - bundle exec derailed bundle:objects > tmp/memory_bundle_objects.txt + - scripts/generate-gems-memory-metrics-static tmp/memory_bundle_objects.txt >> 'tmp/memory_metrics.txt' artifacts: paths: - - tmp/memory_static_full_report.txt - - tmp/memory_static_objects_full_report.txt - - tmp/memory_static_metrics.txt + - tmp/memory_*.txt reports: - metrics: tmp/memory_static_metrics.txt + metrics: tmp/memory_metrics.txt diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml index d0e09dbf2f8..89b5ae38072 100644 --- a/.gitlab/ci/reports.gitlab-ci.yml +++ b/.gitlab/ci/reports.gitlab-ci.yml @@ -1,98 +1,26 @@ include: - template: Code-Quality.gitlab-ci.yml + - template: Security/SAST.gitlab-ci.yml + - template: Security/Dependency-Scanning.gitlab-ci.yml code_quality: - extends: .dedicated-no-docs-no-db-pull-cache-job + extends: .dedicated-no-docs # gitlab-org runners set `privileged: false` but we need to have it set to true # since we're using Docker in Docker tags: [] before_script: [] cache: {} dependencies: [] - variables: - SETUP_DB: "false" sast: - extends: .dedicated-no-docs-no-db-pull-cache-job - image: docker:stable + extends: .dedicated-no-docs + before_script: [] + tags: [] variables: SAST_CONFIDENCE_LEVEL: 2 DOCKER_DRIVER: overlay2 - allow_failure: true - tags: [] - before_script: [] - cache: {} - dependencies: [] - services: - - docker:stable-dind - script: - - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage - function propagate_env_vars() { - CURRENT_ENV=$(printenv) - - for VAR_NAME; do - echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME " - done - } - - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - - | - docker run \ - $(propagate_env_vars \ - SAST_ANALYZER_IMAGES \ - SAST_ANALYZER_IMAGE_PREFIX \ - SAST_ANALYZER_IMAGE_TAG \ - SAST_DEFAULT_ANALYZERS \ - SAST_BRAKEMAN_LEVEL \ - SAST_GOSEC_LEVEL \ - SAST_FLAWFINDER_LEVEL \ - SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \ - SAST_PULL_ANALYZER_IMAGE_TIMEOUT \ - SAST_RUN_ANALYZER_TIMEOUT \ - ) \ - --volume "$PWD:/code" \ - --volume /var/run/docker.sock:/var/run/docker.sock \ - "registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code - artifacts: - reports: - sast: gl-sast-report.json dependency_scanning: - extends: .dedicated-no-docs-no-db-pull-cache-job - image: docker:stable - variables: - DOCKER_DRIVER: overlay2 - allow_failure: true - tags: [] before_script: [] - cache: {} - dependencies: [] - services: - - docker:stable-dind - script: - - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - - | # this is required to avoid undesirable reset of Docker image ENV variables being set on build stage - function propagate_env_vars() { - CURRENT_ENV=$(printenv) - - for VAR_NAME; do - echo $CURRENT_ENV | grep "${VAR_NAME}=" > /dev/null && echo "--env $VAR_NAME " - done - } - - | - docker run \ - $(propagate_env_vars \ - DS_ANALYZER_IMAGES \ - DS_ANALYZER_IMAGE_PREFIX \ - DS_ANALYZER_IMAGE_TAG \ - DS_DEFAULT_ANALYZERS \ - DEP_SCAN_DISABLE_REMOTE_CHECKS \ - DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \ - DS_PULL_ANALYZER_IMAGE_TIMEOUT \ - DS_RUN_ANALYZER_TIMEOUT \ - ) \ - --volume "$PWD:/code" \ - --volume /var/run/docker.sock:/var/run/docker.sock \ - "registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code - artifacts: - reports: - dependency_scanning: gl-dependency-scanning-report.json + tags: [] + extends: .dedicated-no-docs diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 9b764028be9..933af90c85a 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -77,6 +77,7 @@ schedule:review-build-cng: .review-deploy-base: &review-deploy-base <<: *review-base allow_failure: true + retry: 2 stage: review variables: HOST_SUFFIX: "${CI_ENVIRONMENT_SLUG}" @@ -95,10 +96,16 @@ schedule:review-build-cng: - install_api_client_dependencies_with_apk - source scripts/review_apps/review-apps.sh script: - - perform_review_app_deployment + - check_kube_domain + - ensure_namespace + - install_tiller + - install_external_dns + - download_chart + - deploy || display_deployment_debug + - wait_for_review_app_to_be_accessible + - add_license artifacts: - paths: - - review_app_url.txt + paths: [review_app_url.txt] expire_in: 2 days when: always @@ -108,8 +115,6 @@ review-deploy: schedule:review-deploy: <<: *review-deploy-base <<: *review-schedules-only - script: - - perform_review_app_deployment review-stop: <<: *review-base @@ -124,11 +129,11 @@ review-stop: script: - source scripts/review_apps/review-apps.sh - delete - - cleanup .review-qa-base: &review-qa-base <<: *review-docker allow_failure: true + retry: 2 stage: qa variables: <<: *review-docker-variables diff --git a/CHANGELOG.md b/CHANGELOG.md index 623e5d0acb9..8d4509e370d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,346 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 12.0.2 (2019-06-25) + +### Fixed (7 changes, 1 of them is from the community) + +- Fix missing API notification flags for Microsoft Teams. !29824 (Seiji Suenaga) +- Fixed 'diff version changes' link not working. !29825 +- Fix label serialization in issue and note hooks. !29850 +- Include the GitLab version in the cache key for Gitlab::JsonCache. !29938 +- Prevent EE backport migrations from running if CE is not migrated. !30002 +- Silence backup warnings when CRON=1 in use. !30033 +- Fix comment emails not respecting group-level notification email. + +### Performance (1 change) + +- Omit issues links in merge request entity API response. !29917 + + +## 12.0.1 (2019-06-24) + +- No changes. + +## 12.0.0 (2019-06-22) + +### Security (10 changes) + +- Hide confidential issue title on unsubscribe for anonymous users. +- Fix url redaction for issue links. +- Fix confidential issue label disclosure on milestone view. +- Filter relative links in wiki for XSS. +- Prevent XSS injection in note imports. +- Resolve: Milestones leaked via search API. +- Prevent bypass of restriction disabling web password sign in. +- Add extra fields for handling basic auth on import by url page. +- Protect Gitlab::HTTP against DNS rebinding attack. +- Prevent invalid branch for merge request. + +### Removed (5 changes, 1 of them is from the community) + +- Remove ability for group clusters to be automatically configured on creation. !27245 +- Removes support for AUTO_DEVOPS_DOMAIN. !28460 +- Remove the circuit breaker API. !28669 +- Make Kubernetes service templates readonly. !29044 +- Remove Content-Type override for Mattermost OAuth login. (Harrison Healey) + +### Fixed (116 changes, 28 of them are from the community) + +- Fix col-sm-* in forms to keep layout. !24885 (Takuya Noguchi) +- Avoid 500 when rendering users ATOM data. !25408 +- Fix flyout nav on small viewports. !25998 +- Fix proxy support in Container Scanning. !27246 +- preventing blocked users and their PipelineSchdules from creating new Pipelines. !27318 +- Fix yaml linting for GitLab CI inside project (.gitlab/ci) *.yml files and CI template files. !27576 (Will Hall) +- Fix yaml linting for project root *.yml files. !27579 (Will Hall) +- Added a content field to atom feed. !27652 +- Bring secondary button styles up to design standard. !27920 +- Use FindOrCreateService to create labels and check for existing ones. !27987 (Matt Duren) +- Fix "too many loops" error by handling gracefully cron schedules for non existent days. !28002 +- Handle errors in successful notes reply. !28082 +- Fix 500 error when accessing charts with an anonymous user. !28091 (Diego Silva) +- Allow user to set primary email first when 2FA is required. !28097 (Kartikey Tanna) +- Auto-DevOps: allow to disable rollout status check. !28130 (Sergej Nikolaev <kinolaev@gmail.com>) +- Resolved JIRA service: NoMethodError: undefined method 'find' for nil:NilClass. !28206 +- Supports Matomo/Piwik string website ID ("Protect Track ID" plugin). !28214 (DUVERGIER Claude) +- Fix loading.. dropdown at search field. !28275 (Pavel Chausov) +- Remove unintended error message shown when moving issues. !28317 +- Properly clear the merge error upon rebase failure. !28319 +- Upgrade dependencies for node 12 compatibility. !28323 +- Fix. `db:migrate` is failed on MySQL 8. !28351 (sue445) +- Fix an error in projects admin when statistics are missing. !28355 +- Fix emojis URLs. !28371 +- Prevent common name collisions when requesting multiple Let's Encrypt certificates concurrently. !28373 +- Fix issue that causes "Save changes" button in project settings pages to be enabled/disabled incorrectly when changes are made to the form. !28377 +- Fix diff notes and discussion notes being exported as regular notes. !28401 +- Fix padding in MR widget. !28472 +- Updates loading icon in commits page. !28475 +- Fix border radius of discussions. !28490 +- Update broadcast message action icons. !28496 (Jarek Ostrowski @jareko) +- Update icon color to match design system, pass accessibility. !28498 (Jarek Ostrowski @jareko) +- Show data on Cycle Analytics page when value is less than a second. !28507 +- Fix dropdown position when loading remote data. !28526 +- Delete unauthorized Todos when project is made private. !28560 +- Change links in system notes to use relative paths. !28588 (Luke Picciau) +- Update favicon from next. !28601 (Jarek Ostrowski @jareko) +- Open visibility help link in a new tab. !28603 (George Tsiolis) +- Fix issue importing members with owner access. !28636 +- Fix the height of the page headers on issues/merge request/snippets pages. !28650 (Erik van der Gaag) +- Always show "Pipelines must succeed" checkbox. !28651 +- Resolve moving an issue results in broken image links in comments. !28654 +- Fix milestone references containing &, <, or >. !28667 +- Add hover and focus to Attach a file. !28682 +- Correctly word-wrapping project descriptions with very long words. !28695 (Erik van der Gaag) +- Prevent icons from shrinking in User popover when contents exceed container. !28696 +- Allow removal of empty lines via suggestions. !28703 +- Throw an error when formatDate's input is invalid. !28713 +- Fix order dependency with user params during imports. !28719 +- Fix search dropdown not closing on blur if empty. !28730 +- Fixed ignored postgres version that occurs after the first autodevops deploy when specifying custom $POSTGRES_VERSION. !28735 (Brandon Dimcheff) +- Limit milestone dates to before year 9999. !28742 (Luke Picciau) +- Set project default visibility to max allowed. !28754 +- Cancel auto merge when merge request is closed. !28782 +- Fixes Ref link being displayed as raw HTML in the Pipelines page. !28823 +- Fix job name in graph dropdown overflowing. !28824 +- Add style to disable webkit icons for search inputs. !28833 (Jarek Ostrowski @jareko) +- Fix email notifications for user excluded actions. !28835 +- Resolve Tooltip Consistency. !28839 +- Fix Merge Request merge checkbox alignment on mobile view. !28845 +- Add referenced-commands in no overflow list. !28858 +- Fix participants list wrapping. !28873 +- Excludes MR author from Review roulette. !28886 (Jacopo Beschi @jacopo-beschi) +- Give labels consistent weight. !28895 +- Added padding to time window dropdown in monitor dashboard. !28897 +- Move text under p tag. !28901 +- Resolve Position is off when visiting files with anchors. !28913 +- Fix whitespace changes visibility when the related file was initially collapsed. !28950 (Ondřej Budai) +- Fix emoji picker visibility issue. !28984 +- Resolve Merge request discussion text jumps when resolved. !28995 +- Allow lowercase prefix for Youtrack issue ids. !29057 (Matthias Baur) +- Add support to view entirety of long branch name in dropdown instead of it being cut off. !29069 +- Fix inconsistent option dropdown button height to match adjacent button. !29096 +- Improve new user email markup unconsistency between text and html parts. !29111 (Haunui Saint-sevin) +- Eliminate color inconsistencies in metric graphs. !29127 +- Avoid setting Gitlab::Session on sessionless requests and Git HTTP. !29146 +- Use the selected time window for metrics dashboard. !29152 +- Remove build policies from serverless app template. !29253 +- Fix serverless apps deployments by bumping 'tm' version. !29254 +- Include the port in the URLs of the API Link headers. !29267 +- Fix Fogbugz Importer not working. !29383 +- Fix GPG signature verification with recent GnuPG versions. !29388 (David Palubin) +- Cancel Auto Merge when target branch is changed. !29416 +- Fix nil coercion updating storage size on project statistics. !29425 +- Ignore legacy artifact columns in Project Import/Export. !29427 +- Avoid DB timeouts when scheduling migrations. !29437 +- Handle encoding errors for MergeToRefService. !29440 +- Fix UTF-8 conversion issues when resolving conflicts. !29453 +- Enlarge metrics time-window dropdown links. !29458 +- Remove unnecessary decimals on Metrics chart axis. !29468 +- Fix scrolling to top on assignee change. !29500 +- Allow command/control click to open link in new tab on Merge Request tabs. !29506 +- Omit blocked admins from repository check e-mails. !29507 +- Fix diverged branch locals. !29508 +- Process up to 100 commit messages for references when pushing to a new default branch. !29511 (Fabio Papa) +- Allow developer role to delete docker tags via container registry API. !29512 +- Fix "Resolve conflicts" button not appearing for some users. !29535 +- Fix: propagate all documented ENV vars to CI when using SAST. !29564 +- AutoDevops function ensure_namespace() now explicitly tests the namespace. !29567 (Jack Lei) +- Fix sidebar flyout navigation. !29571 +- Fix missing deployment rockets in monitor dashboard. !29574 +- Fix inability to set visibility_level on project via API. !29578 +- Ensure a Kubernetes namespace is not used for deployments if there is no service account token associated with it. !29643 +- Refresh service_account_token for kubernetes_namespaces. !29657 +- Expose all current events properly on services API. !29736 (Zsolt Kovari) +- Move Dropdown to Stick to MR View App Button. !29767 +- Fix IDE commit using latest ref in branch and overriding contents. !29769 +- Revert concurrent pipeline creation for pipeline schedules. !29794 +- Fix layout of group milestone header. +- Fix remote mirrors not updating after tag push. +- Fix padding of unclickable pipeline dropdown items to match links. +- Change resolve button text to mark comment as resolved. +- Align system note within discussion with other notes. +- Fix border radii on diff files and repo files. +- Fixed show whitespace button not refetching diff content. +- Fix pipeline schedules when owner is nil. + +### Changed (35 changes, 13 of them are from the community) + +- Include information if issue was clossed via merge request or commit. !15610 (Michał Zając) +- Removes duplicated members from api/projects/:id/members/all. !24005 (Jacopo Beschi @jacopo-beschi) +- Apply the group setting "require 2FA" across all subgroup members as well when changing the group setting. !24965 (rroger) +- Enable function features for external Knative installations. !27173 +- Remove dind from DAST template. !28083 +- Update registration form to indicate invalid name or username length on input. !28095 (Jiaan Louw) +- Default masked to false for new variables. !28186 +- Better isolated `Docker.gitlab-ci.yml` to avoid interference with other job configurations. !28213 (lrkwz) +- Remove the mr_push_options feature flag. !28278 +- Replace Oxygen-Sans font with Noto Sans. !28322 +- Update new smiley icons, find n replace old names with new ones. !28338 (Jarek Ostrowski) +- Adds a text label to color pickers to improve accessibility. !28343 (Chris Toynbee) +- Prioritize login form on mobile breakpoint. !28360 +- Move some project routes under /-/ scope. !28435 +- I18n for issue closure reason in emails. !28489 (Michał Zając) +- Geo: Remove Gitlab::LfsToken::LegacyRedisDeviseToken implementation and usage. !28546 +- Add check circle filled icon for resolved comments. !28663 +- Update project security dashboard documentation. !28681 +- Remove `docker pull` prefix when copying a tag from the registry. !28757 (Benedikt Franke) +- Adjust milestone completion rate to be based on issues count. !28777 +- Enhance line-height of Activity feed UI. !28856 (Jacopo Beschi @jacopo-beschi) +- Upgrade to Gitaly v1.43.0. !28867 +- Do not display Update app button when saving Knative domain name. !28904 +- Rebrush of flash-warning according to the new design (brighter background and darker font). !28916 (Michel Engelen) +- Added reference, web_path, and relative_position fields to GraphQL Issue. !28998 +- Change logic behind cycle analytics. !29018 +- Add documentation links for confidental and locked discussions. !29073 +- Update GITALY_SERVER_VERSION to 1.45.0. !29109 +- Allow masking if 8 or more characters in base64. !29143 (thomas-nilsson-irfu) +- Replaces sidekiq mtail metrics with ruby instrumentation metrics. !29215 +- Allow references to labels and milestones to contain emoji. !29284 +- changed the styles on `Add List` dropdown to look more like the EE vesion. !29338 (Michel Engelen) +- Hashed Storage is enabled by default on new installations. !29586 +- Upgrade to Gitaly v1.47.0. !29789 +- Default MR checkbox to true in most cases. + +### Performance (11 changes) + +- Improve performance of jobs controller. !28093 +- Upgrade Ruby version to 2.6.3. !28117 +- Make pipeline schedule worker resilient. !28407 +- Fix performance issue with large Markdown content in issue or merge request description. !28597 +- Improve clone performance by using delta islands. !28871 +- Reduce Gitaly calls to improve performance when rendering suggestions. !29027 +- Use Redis for CacheMarkDownField on non AR models. !29054 +- Add index on public_email for users. !29430 +- Speed up commit loads by disabling BatchLoader replace_methods. !29633 +- Add index on invite_email for members. !29768 +- Improve performance of users autocomplete when there are lots of results. + +### Added (47 changes, 12 of them are from the community) + +- Added option to filter jobs by age in the /job/request API endpoint. !1340 (Dmitry Chepurovskiy) +- Add ability to define notification email addresses for groups you belong to. !25299 +- Add wiki size to project statistics. !25321 (Peter Marko) +- 58404 - setup max depth for GraphQL. !25737 (Ken Ding) +- Add auto SSL toggle option to Pages domain settings page. !26438 +- Empty project state for Web IDE. !26556 +- Add support for multiple job parents in GitLab CI YAML. !26801 (Wolphin (Nikita)) +- Pass user's identity and token from JupyterHub to user's Jupyter environment. !27314 (Amit Rathi) +- Add issues_statistics api endpoints and extend issues search api. !27366 +- Validate Kubernetes credentials at cluster creation. !27403 +- Update the merge request widget's "Merge" button to support merge trains. !27594 +- Style the toast component according to design specs. !27734 +- Add API support for committing changes to different projects in same fork network. !27915 +- Add support for && and || to CI Pipeline Expressions. Change CI variable expression matching for Lexeme::Pattern to eagerly return tokens. !27925 (Martin Manelli) +- Added ref querystring parameter to project search API to allow searching on branches/tags other than the default. !28069 (Lee Tickett) +- Add notify_only_default_branch option to PipelinesEmailService. !28271 (Peter Marko) +- Support multiplex GraphQL queries. !28273 +- Add Namespace and ProjectStatistics to GraphQL API. !28277 +- Display classname JUnit attribute in report modal. !28376 +- API: Allow to get and set "masked" attribute for variables. !28381 (Mathieu Parent) +- Add allow_failure attribute to Job API. !28406 +- Add support for AsciiDoc include directive. !28417 (Jakub Jirutka & Guillaume Grossetie) +- Migrate Kubernetes service integration templates to clusters. !28534 +- Allow issue list to be sorted by relative order. !28566 +- Implement borderless discussion design with new reply field. !28580 +- Add expand/collapse to error tracking settings. !28619 +- Adds collapsible sections for job log. !28642 +- Add LFS oid to GraphQL blob type. !28666 +- Allow users to specify a time range on metrics dashboard. !28670 +- Add a New Copy Button That Works in Modals. !28676 +- Add Kubernetes logs to Admin Logs UI. !28685 +- Set up git client in Jupyter installtion. !28783 (Amit Rathi) +- Add task count and completed count to responses of Issue and MR. !28859 +- Add project level git depth CI/CD setting. !28919 +- Use global IDs when exposing GraphQL resources. !29080 +- Expose wiki_size on GraphQL API. !29123 +- Expose notes and discussions in GraphQL. !29212 +- Use to 'gitlabktl' build serverless applications. !29258 +- Adds pagination component for graphql api. !29277 +- Allow switching clusters between managed and unmanaged. !29322 +- Get and edit ci_default_git_depth via project API. !29353 +- Link to an external dashboard from metrics dashboard. !29369 +- Add labels to note event payload. !29384 (Sujay Patel) +- Add Join meeting button to issues with Zoom links. !29454 +- Make task completion status available via GraphQL. +- Add backtraces to Peek performance bar for SQL calls. +- Added diff suggestion feature discovery popover. + +### Other (62 changes, 14 of them are from the community) + +- Unified EE/CS differences in repository/show.html. !13562 +- Remove legacy artifact related code. !26475 +- Backport the EE schema and migrations to CE. !26940 (Yorick Peterse) +- Add dedicated logging for GraphQL queries. !27885 +- i18n: externalize strings from user profile settings. !28088 (Antony Liu) +- Omit max-count for diverging_commit_counts behind feature flag. !28157 +- Fix alignment of resend button in members page. !28202 +- Update indirect dependency fsevents from 1.2.4 to 1.2.9. !28220 (Takuya Noguchi) +- Update get_process_mem to 0.2.3. !28248 +- Add Pool repository to the usage ping. !28267 +- Forbid NULL in project_statistics.packages_size. !28400 +- Update Gitaly to v1.42.1. !28425 +- Upgrade babel to 7.4.4. !28437 (Takuya Noguchi) +- Externalize profiles preferences. !28470 (George Tsiolis) +- Update GitLab Runner Helm Chart to 0.5.0. !28497 +- Change collapse icon size to size of profile picture. !28512 +- Resolve Snippet icon button is misaligned. !28522 +- Bumps Kubernetes in Auto DevOps to 1.11.10. !28525 +- Bump Helm version in Auto-DevOps.gitlab-ci.yml to 2.14.0. !28527 +- Migrate the monitoring dashboard store to vuex. !28555 +- Give New Snippet button green outline. !28559 +- Removes project_auto_devops#domain column. !28574 +- Externalize strings of email page in user profile. !28587 (antony liu) +- Externalize strings of active sessions page in user profile. !28590 (antony liu) +- Refactor and abstract Auto Merge Processes. !28595 +- Add section to dev docs on accessing chatops. !28623 +- Externalize strings of chat page in user profile. !28632 +- Externalize strings of PGP Keys and SSH Keys page in user profile. !28653 (Antony Liu) +- Added the `.extended-height` class to the labels-dropdown. !28659 (Michel Engelen) +- Moved EE/CE code differences for `app/assets/javascripts/gl_dropdown.js` into CE. !28711 (Michel Engelen) +- Update GitLab Runner Helm Chart to 0.5.1. !28720 +- Remove support for using Geo with an installation from source. !28737 +- API: change masked attribute type to Boolean. !28758 +- API: change protected attribute type to Boolean. !28766 +- Add a column header to admin/jobs page. !28837 +- Reset merge status from mergeable MRs. !28843 +- Show tooltip on truncated commit title. !28865 (Timofey Trofimov) +- Added conditional rendering to `app/views/search/_form.html.haml` for CE/EE code base consistency. !28883 (Michel Engelen) +- Change "Report abuse to GitLab" to more generic wording. !28884 (Marc Schwede) +- Update GitLab Pages to v1.6.0. !29048 +- Update GitLab Runner Helm Chart to 0.5.2. !29050 +- User link styling for commits. !29150 +- Fix null source_project_id in pool_repositories. !29157 +- Add deletion protection setting column to application_settings table. !29268 +- Added code differnces from EE in file 'app/assets/javascripts/pages/projects/project.js' to CE. !29271 (Michel Engelen) +- Update to GitLab Shell v9.3.0. !29283 +- Document when milestones and labels links are missing. !29355 +- Make margin between buttons consistent. !29378 +- Changed the 'Created' label to 'Last Updated' on the container registry table to more accurately reflect what the date represents. !29464 +- Update GitLab Pages to v1.6.1. !29559 +- Indent collapsible sections. !29804 +- Group download buttons into a .btn-group. +- Change default color of award emoji button. +- Use blue for activity stream links; use monospace font for commit sha. +- Remove fixed height from MR diff headers. +- Moves the table pagination shared component. +- Add warning that gitlab-secrets isn't included in backup. +- Update merge request tabs so they no longer scroll. +- Reduce height of issue board input to align with buttons. +- Increase height of move issue dropdown. +- Use grid and correct border radius for status badge. +- Moves snowplow to CE repo. + + +## 11.11.4 (2019-06-26) + +### Fixed (3 changes) + +- Fix Fogbugz Importer not working. !29383 +- Fix scrolling to top on assignee change. !29500 +- Fix IDE commit using latest ref in branch and overriding contents. !29769 + + ## 11.11.3 (2019-06-10) ### Fixed (5 changes) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 21998d3c2d9..7f3a46a841e 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -1.47.0 +1.49.0 @@ -300,6 +300,9 @@ gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-redis', '~> 1.2.0' +# Memory benchmarks +gem 'derailed_benchmarks', require: false + # Metrics group :metrics do gem 'method_source', '~> 0.8', require: false @@ -374,7 +377,6 @@ group :development, :test do gem 'activerecord_sane_schema_dumper', '1.0' gem 'stackprof', '~> 0.2.10', require: false - gem 'derailed_benchmarks', require: false gem 'simple_po_parser', '~> 1.1.2', require: false @@ -1 +1 @@ -12.0.0-pre +12.1.0-pre diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js index d0b7f3ff7a2..b23de36f860 100644 --- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js @@ -59,6 +59,14 @@ export default function renderMermaid($els) { mermaid.init(undefined, el, id => { const svg = document.getElementById(id); + // As of https://github.com/knsv/mermaid/commit/57b780a0d, + // Mermaid will make two init callbacks:one to initialize the + // flow charts, and another to initialize the Gannt charts. + // Guard against an error caused by double initialization. + if (svg.classList.contains('mermaid')) { + return; + } + svg.classList.add('mermaid'); // pre > code > svg diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index c587b276fa3..2ace0060c42 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -38,6 +38,7 @@ export default Vue.extend({ issue: {}, list: {}, loadingAssignees: false, + timeTrackingLimitToHours: boardsStore.timeTracking.limitToHours, }; }, computed: { diff --git a/app/assets/javascripts/boards/components/issue_time_estimate.vue b/app/assets/javascripts/boards/components/issue_time_estimate.vue index 98c1d29db16..3385aad5b11 100644 --- a/app/assets/javascripts/boards/components/issue_time_estimate.vue +++ b/app/assets/javascripts/boards/components/issue_time_estimate.vue @@ -2,6 +2,7 @@ import { GlTooltip } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility'; +import boardsStore from '../stores/boards_store'; export default { components: { @@ -14,12 +15,17 @@ export default { required: true, }, }, + data() { + return { + limitToHours: boardsStore.timeTracking.limitToHours, + }; + }, computed: { title() { - return stringifyTime(parseSeconds(this.estimate), true); + return stringifyTime(parseSeconds(this.estimate, { limitToHours: this.limitToHours }), true); }, timeEstimate() { - return stringifyTime(parseSeconds(this.estimate)); + return stringifyTime(parseSeconds(this.estimate, { limitToHours: this.limitToHours })); }, }, }; diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index f2f37d22b97..a020765f335 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -49,6 +49,7 @@ export default () => { } boardsStore.create(); + boardsStore.setTimeTrackingLimitToHours($boardApp.dataset.timeTrackingLimitToHours); issueBoardsApp = new Vue({ el: $boardApp, diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js b/app/assets/javascripts/boards/mixins/sortable_default_options.js index 983b28d2e67..636ca99952c 100644 --- a/app/assets/javascripts/boards/mixins/sortable_default_options.js +++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js @@ -1,7 +1,7 @@ /* global DocumentTouch */ import $ from 'jquery'; -import sortableConfig from '../../sortable/sortable_config'; +import sortableConfig from 'ee_else_ce/sortable/sortable_config'; export function sortableStart() { $('.has-tooltip') diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 4b3b44574a8..4ba4cde6bae 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -12,6 +12,9 @@ import eventHub from '../eventhub'; const boardsStore = { disabled: false, + timeTracking: { + limitToHours: false, + }, scopedLabels: { helpLink: '', enabled: false, @@ -222,6 +225,10 @@ const boardsStore = { setIssueDetail(issueDetail) { this.detail.issue = issueDetail; }, + + setTimeTrackingLimitToHours(limitToHours) { + this.timeTracking.limitToHours = parseBoolean(limitToHours); + }, }; BoardsStoreEE.initEESpecific(boardsStore); diff --git a/app/assets/javascripts/branches/components/divergence_graph.vue b/app/assets/javascripts/branches/components/divergence_graph.vue new file mode 100644 index 00000000000..36fff370ea1 --- /dev/null +++ b/app/assets/javascripts/branches/components/divergence_graph.vue @@ -0,0 +1,72 @@ +<script> +import { sprintf, __ } from '~/locale'; +import GraphBar from './graph_bar.vue'; +import { MAX_COMMIT_COUNT } from '../constants'; + +export default { + components: { + GraphBar, + }, + props: { + defaultBranch: { + type: String, + required: true, + }, + distance: { + type: Number, + required: false, + default: null, + }, + aheadCount: { + type: Number, + required: true, + }, + behindCount: { + type: Number, + required: true, + }, + maxCommits: { + type: Number, + required: true, + }, + }, + computed: { + title() { + if (this.distance) { + return sprintf( + __('More than %{number_commits_distance} commits different with %{default_branch}'), + { + number_commits_distance: + this.distance >= MAX_COMMIT_COUNT ? `${MAX_COMMIT_COUNT - 1}+` : this.distance, + default_branch: this.defaultBranch, + }, + ); + } + + return sprintf( + __( + '%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead', + ), + { + number_commits_behind: this.behindCount, + number_commits_ahead: this.aheadCount, + default_branch: this.defaultBranch, + }, + ); + }, + }, +}; +</script> + +<template> + <div :title="title" class="divergence-graph px-2 d-none d-md-block"> + <template v-if="distance"> + <graph-bar :count="distance" :max-commits="maxCommits" position="full" /> + </template> + <template v-else> + <graph-bar :count="behindCount" :max-commits="maxCommits" position="left" /> + <div class="graph-separator pull-left mt-1"></div> + <graph-bar :count="aheadCount" :max-commits="maxCommits" position="right" /> + </template> + </div> +</template> diff --git a/app/assets/javascripts/branches/components/graph_bar.vue b/app/assets/javascripts/branches/components/graph_bar.vue new file mode 100644 index 00000000000..83da41ca097 --- /dev/null +++ b/app/assets/javascripts/branches/components/graph_bar.vue @@ -0,0 +1,69 @@ +<script> +import { SIDES, MAX_COMMIT_COUNT } from '../constants'; + +export default { + props: { + position: { + type: String, + required: true, + }, + count: { + type: Number, + required: true, + }, + maxCommits: { + type: Number, + required: true, + }, + }, + computed: { + label() { + if (this.count >= MAX_COMMIT_COUNT) { + return `${MAX_COMMIT_COUNT - 1}+`; + } + + return this.count; + }, + barGraphWidthFactor() { + return this.maxCommits > 0 ? 100 / this.maxCommits : 0; + }, + style() { + return { + width: `${this.count * this.barGraphWidthFactor}%`, + }; + }, + isFullWidth() { + return this.position === SIDES.full; + }, + isLeftSide() { + return this.position === SIDES.left; + }, + roundedClass() { + if (this.isFullWidth) return 'rounded'; + + return `rounded-${this.position}`; + }, + textAlignmentClass() { + if (this.isFullWidth) return 'text-center'; + + return `text-${this.isLeftSide ? SIDES.right : SIDES.left}`; + }, + positionSideClass() { + return `position-${this.isLeftSide ? SIDES.right : SIDES.left}-0`; + }, + }, +}; +</script> + +<template> + <div :class="{ full: isFullWidth }" class="position-relative pull-left pt-1 graph-side h-100"> + <div + :style="style" + :class="[roundedClass, positionSideClass]" + class="position-absolute bar js-graph-bar" + ></div> + <span :class="textAlignmentClass" class="d-block pt-1 pr-1 count js-graph-count"> + {{ label }} + </span> + </div> +</template> diff --git a/app/assets/javascripts/branches/constants.js b/app/assets/javascripts/branches/constants.js new file mode 100644 index 00000000000..16949e662d2 --- /dev/null +++ b/app/assets/javascripts/branches/constants.js @@ -0,0 +1,6 @@ +export const SIDES = { + full: 'full', + left: 'left', + right: 'right', +}; +export const MAX_COMMIT_COUNT = 1000; diff --git a/app/assets/javascripts/branches/divergence_graph.js b/app/assets/javascripts/branches/divergence_graph.js new file mode 100644 index 00000000000..670e8e9eb60 --- /dev/null +++ b/app/assets/javascripts/branches/divergence_graph.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import DivergenceGraph from './components/divergence_graph.vue'; + +export default () => { + document.querySelectorAll('.js-branch-divergence-graph').forEach(el => { + const { distance, aheadCount, behindCount, defaultBranch, maxCommits } = el.dataset; + + return new Vue({ + el, + render(h) { + return h(DivergenceGraph, { + props: { + defaultBranch, + distance: distance ? parseInt(distance, 10) : null, + aheadCount: parseInt(aheadCount, 10), + behindCount: parseInt(behindCount, 10), + maxCommits: parseInt(maxCommits, 10), + }, + }); + }, + }); + }); +}; diff --git a/app/assets/javascripts/clusters/services/application_state_machine.js b/app/assets/javascripts/clusters/services/application_state_machine.js index 17ea4d77795..6e632519d8a 100644 --- a/app/assets/javascripts/clusters/services/application_state_machine.js +++ b/app/assets/javascripts/clusters/services/application_state_machine.js @@ -80,6 +80,9 @@ const applicationStateMachine = { installFailed: false, }, }, + [NOT_INSTALLABLE]: { + target: NOT_INSTALLABLE, + }, // This is possible in artificial environments for E2E testing [INSTALLED]: { target: INSTALLED, @@ -108,6 +111,9 @@ const applicationStateMachine = { updateSuccessful: false, }, }, + [NOT_INSTALLABLE]: { + target: NOT_INSTALLABLE, + }, [UNINSTALL_EVENT]: { target: UNINSTALLING, effects: { diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index d0cc4897aeb..a4394ab7e92 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -12,6 +12,7 @@ import 'core-js/es/promise/finally'; import 'core-js/es/string/code-point-at'; import 'core-js/es/string/from-code-point'; import 'core-js/es/string/includes'; +import 'core-js/es/string/starts-with'; import 'core-js/es/symbol'; import 'core-js/es/map'; import 'core-js/es/weak-map'; diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index aaa9f8b759a..58d5b658b17 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -49,6 +49,8 @@ export default { return this.author.id ? this.author.id : ''; }, authorUrl() { + // TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings + // name: 'mailto:' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives return this.author.web_url || `mailto:${this.commit.author_email}`; }, authorAvatar() { @@ -80,7 +82,7 @@ export default { v-html="commit.title_html" ></a> - <span class="commit-row-message d-block d-sm-none"> · {{ commit.short_id }} </span> + <span class="commit-row-message d-block d-sm-none">· {{ commit.short_id }}</span> <button v-if="commit.description_html" diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue index 80aec84f574..1dcdb65d5c7 100644 --- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue +++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue @@ -1,6 +1,6 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; -import { n__, __ } from '~/locale'; +import { n__, __, sprintf } from '~/locale'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; export default { @@ -54,11 +54,7 @@ export default { }, methods: { commitsText(version) { - return n__( - `${version.commits_count} commit,`, - `${version.commits_count} commits,`, - version.commits_count, - ); + return n__(`%d commit,`, `%d commits,`, version.commits_count); }, href(version) { if (this.isBase(version)) { @@ -76,7 +72,7 @@ export default { if (this.targetBranch && (this.isBase(version) || !version)) { return this.targetBranch.branchName; } - return `version ${version.version_index}`; + return sprintf(__(`version %{versionIndex}`), { versionIndex: version.version_index }); }, isActive(version) { if (!version) { @@ -125,9 +121,9 @@ export default { <div> <strong> {{ versionName(version) }} - <template v-if="isBase(version)"> - (base) - </template> + <template v-if="isBase(version)">{{ + s__('DiffsCompareBaseBranch|(base)') + }}</template> </strong> </div> <div> diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index f5876a73eff..63350fafefa 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -151,21 +151,22 @@ export default { <div v-if="forkMessageVisible" class="js-file-fork-suggestion-section file-fork-suggestion"> <span class="file-fork-suggestion-note"> - You're not allowed to <span class="js-file-fork-suggestion-section-action">edit</span> files - in this project directly. Please fork this project, make your changes there, and submit a - merge request. + {{ sprintf(__("You're not allowed to %{tag_start}edit%{tag_end} files in this project + directly. Please fork this project, make your changes there, and submit a merge request."), + { tag_start: '<span class="js-file-fork-suggestion-section-action">', tag_end: '</span>' }) + }} </span> <a :href="file.fork_path" class="js-fork-suggestion-button btn btn-grouped btn-inverted btn-success" - >Fork</a + >{{ __('Fork') }}</a > <button class="js-cancel-fork-suggestion-button btn btn-grouped" type="button" @click="hideForkMessage" > - Cancel + {{ __('Cancel') }} </button> </div> <gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" /> diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue index 7cf3d90d468..e28909b7be3 100644 --- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue +++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue @@ -74,7 +74,7 @@ export default { <button v-if="discussionsExpanded" type="button" - aria-label="Show comments" + :aria-label="__('Show comments')" class="diff-notes-collapse js-diff-comment-avatar js-diff-comment-button" @click="toggleDiscussions" > diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue index f8a637138ad..426bb63d4f7 100644 --- a/app/assets/javascripts/environments/components/container.vue +++ b/app/assets/javascripts/environments/components/container.vue @@ -57,6 +57,7 @@ export default { :user-callouts-path="userCalloutsPath" :lock-promotion-svg-path="lockPromotionSvgPath" :help-canary-deployments-path="helpCanaryDeploymentsPath" + :deploy-boards-help-path="deployBoardsHelpPath" /> <table-pagination diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index ec78240217b..81927d18f8b 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -43,6 +43,11 @@ export default { type: String, required: true, }, + deployBoardsHelpPath: { + type: String, + required: false, + default: '', + }, }, created() { @@ -112,6 +117,7 @@ export default { :user-callouts-path="userCalloutsPath" :lock-promotion-svg-path="lockPromotionSvgPath" :help-canary-deployments-path="helpCanaryDeploymentsPath" + :deploy-boards-help-path="deployBoardsHelpPath" @onChangePage="onChangePage" > <empty-state diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 55613d815ce..4464f5e5578 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -22,6 +22,11 @@ export default { required: true, default: () => [], }, + deployBoardsHelpPath: { + type: String, + required: false, + default: '', + }, canReadEnvironment: { type: Boolean, required: false, @@ -106,8 +111,10 @@ export default { <div class="deploy-board-container"> <deploy-board :deploy-board-data="model.deployBoardData" + :deploy-boards-help-path="deployBoardsHelpPath" :is-loading="model.isLoadingDeployBoard" :is-empty="model.isEmptyDeployBoard" + :has-legacy-app-label="model.hasLegacyAppLabel" :logs-path="model.logs_path" /> </div> diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js index b53d42f202b..dcdaf8731f8 100644 --- a/app/assets/javascripts/environments/index.js +++ b/app/assets/javascripts/environments/index.js @@ -20,6 +20,7 @@ export default () => endpoint: environmentsData.environmentsDataEndpoint, newEnvironmentPath: environmentsData.newEnvironmentPath, helpPagePath: environmentsData.helpPagePath, + deployBoardsHelpPath: environmentsData.deployBoardsHelpPath, cssContainerClass: environmentsData.cssClass, canCreateEnvironment: parseBoolean(environmentsData.canCreateEnvironment), canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment), @@ -31,6 +32,7 @@ export default () => endpoint: this.endpoint, newEnvironmentPath: this.newEnvironmentPath, helpPagePath: this.helpPagePath, + deployBoardsHelpPath: this.deployBoardsHelpPath, cssContainerClass: this.cssContainerClass, canCreateEnvironment: this.canCreateEnvironment, canReadEnvironment: this.canReadEnvironment, diff --git a/app/assets/javascripts/environments/mixins/container_mixin.js b/app/assets/javascripts/environments/mixins/container_mixin.js index f2907c120f8..abf7d33be91 100644 --- a/app/assets/javascripts/environments/mixins/container_mixin.js +++ b/app/assets/javascripts/environments/mixins/container_mixin.js @@ -25,5 +25,10 @@ export default { required: false, default: null, }, + deployBoardsHelpPath: { + type: String, + required: false, + default: '', + }, }, }; diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js index 891086b4142..f280f3cd26c 100644 --- a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js +++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js @@ -10,7 +10,7 @@ import { mergeUrlParams } from '../lib/utils/url_utility'; export default class AvailableDropdownMappings { constructor( container, - baseEndpoint, + runnerTagsEndpoint, labelsEndpoint, milestonesEndpoint, groupsOnly, @@ -18,7 +18,7 @@ export default class AvailableDropdownMappings { includeDescendantGroups, ) { this.container = container; - this.baseEndpoint = baseEndpoint; + this.runnerTagsEndpoint = runnerTagsEndpoint; this.labelsEndpoint = labelsEndpoint; this.milestonesEndpoint = milestonesEndpoint; this.groupsOnly = groupsOnly; @@ -149,7 +149,7 @@ export default class AvailableDropdownMappings { } getRunnerTagsEndpoint() { - return `${this.baseEndpoint}/admin/runners/tag_list.json`; + return `${this.runnerTagsEndpoint}.json`; } getMergeRequestTargetBranchesEndpoint() { diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index 1cbfd7f9bb9..835d3bf8a53 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -8,7 +8,7 @@ import FilteredSearchVisualTokens from './filtered_search_visual_tokens'; export default class FilteredSearchDropdownManager { constructor({ - baseEndpoint = '', + runnerTagsEndpoint = '', labelsEndpoint = '', milestonesEndpoint = '', tokenizer, @@ -19,7 +19,7 @@ export default class FilteredSearchDropdownManager { filteredSearchTokenKeys, }) { this.container = FilteredSearchContainer.container; - this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); + this.runnerTagsEndpoint = runnerTagsEndpoint.replace(/\/$/, ''); this.labelsEndpoint = labelsEndpoint.replace(/\/$/, ''); this.milestonesEndpoint = milestonesEndpoint.replace(/\/$/, ''); this.tokenizer = tokenizer; @@ -51,7 +51,7 @@ export default class FilteredSearchDropdownManager { const supportedTokens = this.filteredSearchTokenKeys.getKeys(); const availableMappings = new AvailableDropdownMappings( this.container, - this.baseEndpoint, + this.runnerTagsEndpoint, this.labelsEndpoint, this.milestonesEndpoint, this.groupsOnly, diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 450e0725f2e..d1f52b91d9e 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -85,7 +85,8 @@ export default class FilteredSearchManager { if (this.filteredSearchInput) { this.tokenizer = FilteredSearchTokenizer; this.dropdownManager = new FilteredSearchDropdownManager({ - baseEndpoint: this.filteredSearchInput.getAttribute('data-base-endpoint') || '', + runnerTagsEndpoint: + this.filteredSearchInput.getAttribute('data-runner-tags-endpoint') || '', labelsEndpoint: this.filteredSearchInput.getAttribute('data-labels-endpoint') || '', milestonesEndpoint: this.filteredSearchInput.getAttribute('data-milestones-endpoint') || '', tokenizer: this.tokenizer, diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 3b73dd83c9f..b308cd9c236 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -318,6 +318,7 @@ class GfmAutoComplete { } setupLabels($input) { + const instance = this; const fetchData = this.fetchData.bind(this); const LABEL_COMMAND = { LABEL: '/label', UNLABEL: '/unlabel', RELABEL: '/relabel' }; let command = ''; @@ -348,7 +349,6 @@ class GfmAutoComplete { })); }, matcher(flag, subtext) { - const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers); const subtextNodes = subtext .split(/\n+/g) .pop() @@ -366,6 +366,27 @@ class GfmAutoComplete { return null; }); + // If any label matches the inserted text after the last `~`, suggest those labels, + // even if any spaces or funky characters were typed. + // This allows matching labels like "Accepting merge requests". + const labels = instance.cachedData[flag]; + if (labels) { + if (!subtext.includes(flag)) { + // Do not match if there is no `~` before the cursor + return null; + } + const lastCandidate = subtext.split(flag).pop(); + if (labels.find(label => label.title.startsWith(lastCandidate))) { + return lastCandidate; + } + } else { + // Load all labels into the autocompleter. + // This needs to happen if e.g. editing a label in an existing comment, because normally + // label data would only be loaded only once you type `~`. + fetchData(this.$inputor, this.at); + } + + const match = GfmAutoComplete.defaultMatcher(flag, subtext, this.app.controllers); return match && match.length ? match[1] : null; }, filter(query, data, searchKey) { @@ -563,8 +584,9 @@ class GfmAutoComplete { const accentAChar = decodeURI('%C3%80'); const accentYChar = decodeURI('%C3%BF'); + // Holy regex, batman! const regexp = new RegExp( - `^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-]|[^\\x00-\\x7a])*)$`, + `^(?:\\B|[^a-zA-Z0-9_\`${atSymbolsWithoutBar}]|\\s)${resultantFlag}(?!${atSymbolsWithBar})((?:[A-Za-z${accentAChar}-${accentYChar}0-9_'.+-:]|[^\\x00-\\x7a])*)$`, 'gi', ); diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index d5130cd331d..9909f437fc8 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -1,12 +1,15 @@ <script> +import { GlLoadingIcon } from '@gitlab/ui'; import { visitUrl } from '../../lib/utils/url_utility'; import tooltip from '../../vue_shared/directives/tooltip'; import identicon from '../../vue_shared/components/identicon.vue'; import eventHub from '../event_hub'; +import { VISIBILITY_TYPE_ICON, GROUP_VISIBILITY_TYPE } from '../constants'; import itemCaret from './item_caret.vue'; import itemTypeIcon from './item_type_icon.vue'; import itemStats from './item_stats.vue'; +import itemStatsValue from './item_stats_value.vue'; import itemActions from './item_actions.vue'; export default { @@ -14,10 +17,12 @@ export default { tooltip, }, components: { + GlLoadingIcon, identicon, itemCaret, itemTypeIcon, itemStats, + itemStatsValue, itemActions, }, props: { @@ -57,6 +62,12 @@ export default { isGroup() { return this.group.type === 'group'; }, + visibilityIcon() { + return VISIBILITY_TYPE_ICON[this.group.visibility]; + }, + visibilityTooltip() { + return GROUP_VISIBILITY_TYPE[this.group.visibility]; + }, }, methods: { onClickRowGroup(e) { @@ -80,43 +91,61 @@ export default { <li :id="groupDomId" :class="rowClass" class="group-row" @click.stop="onClickRowGroup"> <div :class="{ 'project-row-contents': !isGroup }" - class="group-row-contents d-flex justify-content-end align-items-center" + class="group-row-contents d-flex align-items-center" > <div class="folder-toggle-wrap append-right-4 d-flex align-items-center"> <item-caret :is-group-open="group.isOpen" /> <item-type-icon :item-type="group.type" :is-group-open="group.isOpen" /> </div> + <gl-loading-icon + v-if="group.isChildrenLoading" + size="md" + class="d-none d-sm-inline-flex flex-shrink-0 append-right-10" + /> <div - :class="{ 'content-loading': group.isChildrenLoading }" - class="avatar-container rect-avatar s24 d-none d-sm-flex" + :class="{ 'd-sm-flex': !group.isChildrenLoading }" + class="avatar-container rect-avatar s32 d-none flex-grow-0 flex-shrink-0 " > <a :href="group.relativePath" class="no-expand"> - <img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s24" /> - <identicon v-else :entity-id="group.id" :entity-name="group.name" size-class="s24" /> + <img v-if="hasAvatar" :src="group.avatarUrl" class="avatar s32" /> + <identicon v-else :entity-id="group.id" :entity-name="group.name" size-class="s32" /> </a> </div> - <div class="group-text flex-grow"> - <div class="title namespace-title append-right-8"> - <a - v-tooltip - :href="group.relativePath" - :title="group.fullName" - class="no-expand" - data-placement="bottom" - >{{ - // ending bracket must be by closing tag to prevent - // link hover text-decoration from over-extending - group.name - }}</a - > - <span v-if="group.permission" class="user-access-role"> {{ group.permission }} </span> + <div class="group-text-container d-flex flex-fill align-items-center"> + <div class="group-text flex-grow-1 flex-shrink-1"> + <div class="d-flex align-items-center flex-wrap title namespace-title append-right-8"> + <a + v-tooltip + :href="group.relativePath" + :title="group.fullName" + class="no-expand prepend-top-8 append-right-8" + data-placement="bottom" + >{{ + // ending bracket must be by closing tag to prevent + // link hover text-decoration from over-extending + group.name + }}</a + > + <item-stats-value + :icon-name="visibilityIcon" + :title="visibilityTooltip" + css-class="item-visibility d-inline-flex align-items-center prepend-top-8 append-right-4" + /> + <span v-if="group.permission" class="user-access-role prepend-top-8"> + {{ group.permission }} + </span> + </div> + <div v-if="group.description" class="description"> + <span v-html="group.description"> </span> + </div> </div> - <div v-if="group.description" class="description"> - <span v-html="group.description"> </span> + <div + class="metadata align-items-md-center d-flex flex-grow-1 flex-shrink-0 flex-wrap justify-content-md-between" + > + <item-actions v-if="isGroup" :group="group" :parent-group="parentGroup" /> + <item-stats :item="group" class="group-stats prepend-top-2 d-none d-md-flex" /> </div> </div> - <item-stats :item="group" class="group-stats prepend-top-2" /> - <item-actions v-if="isGroup" :group="group" :parent-group="parentGroup" /> </div> <group-folder v-if="group.isOpen && hasChildren" diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue index a7995865c77..cafd22731b1 100644 --- a/app/assets/javascripts/groups/components/item_actions.vue +++ b/app/assets/javascripts/groups/components/item_actions.vue @@ -44,31 +44,31 @@ export default { </script> <template> - <div class="controls"> + <div class="controls d-flex justify-content-end"> <a - v-if="group.canEdit" + v-if="group.canLeave" v-tooltip - :href="group.editPath" - :title="editBtnTitle" - :aria-label="editBtnTitle" + :href="group.leavePath" + :title="leaveBtnTitle" + :aria-label="leaveBtnTitle" data-container="body" data-placement="bottom" - class="edit-group btn no-expand" + class="leave-group btn btn-xs no-expand" + @click.prevent="onLeaveGroup" > - <icon name="settings" /> + <icon name="leave" css-classes="position-top-0" /> </a> <a - v-if="group.canLeave" + v-if="group.canEdit" v-tooltip - :href="group.leavePath" - :title="leaveBtnTitle" - :aria-label="leaveBtnTitle" + :href="group.editPath" + :title="editBtnTitle" + :aria-label="editBtnTitle" data-container="body" data-placement="bottom" - class="leave-group btn no-expand" - @click.prevent="onLeaveGroup" + class="edit-group btn btn-xs no-expand" > - <icon name="leave" /> + <icon name="settings" css-classes="position-top-0" /> </a> </div> </template> diff --git a/app/assets/javascripts/groups/components/item_caret.vue b/app/assets/javascripts/groups/components/item_caret.vue index 43b9607ea8e..18ea4819878 100644 --- a/app/assets/javascripts/groups/components/item_caret.vue +++ b/app/assets/javascripts/groups/components/item_caret.vue @@ -21,5 +21,5 @@ export default { </script> <template> - <span class="folder-caret"> <icon :size="12" :name="iconClass" /> </span> + <span class="folder-caret append-right-4"> <icon :size="10" :name="iconClass" /> </span> </template> diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue index bc6851ea2bf..734a9a89c72 100644 --- a/app/assets/javascripts/groups/components/item_stats.vue +++ b/app/assets/javascripts/groups/components/item_stats.vue @@ -48,7 +48,7 @@ export default { :title="__('Subgroups')" :value="item.subgroupCount" css-class="number-subgroups" - icon-name="folder" + icon-name="folder-o" /> <item-stats-value v-if="isGroup" @@ -70,12 +70,6 @@ export default { css-class="project-stars" icon-name="star" /> - <item-stats-value - :icon-name="visibilityIcon" - :title="visibilityTooltip" - css-class="item-visibility" - tooltip-placement="left" - /> <div v-if="isProject" class="last-updated"> <time-ago-tooltip :time="item.updatedAt" tooltip-placement="bottom" /> </div> diff --git a/app/assets/javascripts/groups/components/item_type_icon.vue b/app/assets/javascripts/groups/components/item_type_icon.vue index e1ebd03cb5f..ae69fbd7bde 100644 --- a/app/assets/javascripts/groups/components/item_type_icon.vue +++ b/app/assets/javascripts/groups/components/item_type_icon.vue @@ -20,7 +20,7 @@ export default { computed: { iconClass() { if (this.itemType === ITEM_TYPE.GROUP) { - return this.isGroupOpen ? 'folder-open' : 'folder'; + return this.isGroupOpen ? 'folder-open' : 'folder-o'; } return 'bookmark'; }, diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue index 95782b2c88a..1af86a94482 100644 --- a/app/assets/javascripts/ide/components/ide_tree_list.vue +++ b/app/assets/javascripts/ide/components/ide_tree_list.vue @@ -30,6 +30,9 @@ export default { showLoading() { return !this.currentTree || this.currentTree.loading; }, + actualTreeList() { + return this.currentTree.tree.filter(entry => !entry.moved); + }, }, mounted() { this.updateViewer(this.viewerType); @@ -54,9 +57,9 @@ export default { <slot name="header"></slot> </header> <div class="ide-tree-body h-100"> - <template v-if="currentTree.tree.length"> + <template v-if="actualTreeList.length"> <file-row - v-for="file in currentTree.tree" + v-for="file in actualTreeList" :key="file.key" :file="file" :level="0" diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index 5429b834708..507dc363529 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -8,6 +8,7 @@ import * as types from './mutation_types'; import { decorateFiles } from '../lib/files'; import { stageKeys } from '../constants'; import service from '../services'; +import router from '../ide_router'; export const redirectToUrl = (self, url) => visitUrl(url); @@ -208,10 +209,6 @@ export const deleteEntry = ({ commit, dispatch, state }, path) => { commit(types.DELETE_ENTRY, path); - if (entry.parentPath && state.entries[entry.parentPath].tree.length === 0) { - dispatch('deleteEntry', entry.parentPath); - } - dispatch('triggerFilesChange'); }; @@ -238,10 +235,15 @@ export const renameEntry = ( parentPath: newParentPath, }); }); - } + } else { + const newPath = parentPath ? `${parentPath}/${name}` : name; + const newEntry = state.entries[newPath]; + commit(types.TOGGLE_FILE_CHANGED, { file: newEntry, changed: true }); - if (!entryPath && !entry.tempFile) { - dispatch('deleteEntry', path); + if (entry.opened) { + router.push(`/project${newEntry.url}`); + commit(types.TOGGLE_FILE_OPEN, entry.path); + } } dispatch('triggerFilesChange'); diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js index dc40a1fa6a2..7627b6e03af 100644 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ b/app/assets/javascripts/ide/stores/actions/file.js @@ -73,7 +73,9 @@ export const getFileData = ( .getFileData(joinPaths(gon.relative_url_root || '', url.replace('/-/', '/'))) .then(({ data, headers }) => { const normalizedHeaders = normalizeHeaders(headers); - setPageTitle(decodeURI(normalizedHeaders['PAGE-TITLE'])); + let title = normalizedHeaders['PAGE-TITLE']; + title = file.prevPath ? title.replace(file.prevPath, file.path) : title; + setPageTitle(decodeURI(title)); if (data) commit(types.SET_FILE_DATA, { data, file }); if (openFile) commit(types.TOGGLE_FILE_OPEN, path); diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index ae42b87c9a7..ec4c2fdcde2 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -216,15 +216,16 @@ export default { Vue.set(state.entries, newPath, { ...oldEntry, id: newPath, - key: `${newPath}-${oldEntry.type}-${oldEntry.id}`, + key: `${newPath}-${oldEntry.type}-${oldEntry.path}`, path: newPath, name: entryPath ? oldEntry.name : name, tempFile: true, prevPath: oldEntry.tempFile ? null : oldEntry.path, url: oldEntry.url.replace(new RegExp(`${oldEntry.path}/?$`), newPath), tree: [], - parentPath, raw: '', + opened: false, + parentPath, }); oldEntry.moved = true; @@ -241,10 +242,6 @@ export default { state.changedFiles = state.changedFiles.concat(newEntry); } - if (state.entries[newPath].opened) { - state.openFiles.push(state.entries[newPath]); - } - if (oldEntry.tempFile) { const filterMethod = f => f.path !== oldEntry.path; diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js index 4e7a8765abe..fb132c1afc1 100644 --- a/app/assets/javascripts/ide/stores/utils.js +++ b/app/assets/javascripts/ide/stores/utils.js @@ -147,9 +147,9 @@ export const createCommitPayload = ({ commit_message: state.commitMessage || getters.preBuiltCommitMessage, actions: getCommitFiles(rootState.stagedFiles).map(f => ({ action: commitActionForFile(f), - file_path: f.path, + file_path: f.moved ? f.movedPath : f.path, previous_path: f.prevPath === '' ? undefined : f.prevPath, - content: f.content || undefined, + content: f.prevPath ? null : f.content || undefined, encoding: f.base64 ? 'base64' : 'text', last_commit_id: newBranch || f.deleted || f.prevPath ? undefined : f.lastCommitSha, })), diff --git a/app/assets/javascripts/ide/utils.js b/app/assets/javascripts/ide/utils.js index d895eca7af0..ae579fef25f 100644 --- a/app/assets/javascripts/ide/utils.js +++ b/app/assets/javascripts/ide/utils.js @@ -3,7 +3,7 @@ import { commitItemIconMap } from './constants'; export const getCommitIconMap = file => { if (file.deleted) { return commitItemIconMap.deleted; - } else if (file.tempFile) { + } else if (file.tempFile && !file.prevPath) { return commitItemIconMap.addition; } diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index 42a3de62772..b2f9296c68b 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -73,7 +73,9 @@ export default { Save changes <i v-if="formState.updateLoading" class="fa fa-spinner fa-spin" aria-hidden="true"> </i> </button> - <button class="btn btn-default float-right" type="button" @click="closeForm">Cancel</button> + <button class="btn btn-default float-right" type="button" @click="closeForm"> + {{ __('Cancel') }} + </button> <button v-if="shouldShowDeleteButton" :class="{ disabled: deleteLoading }" diff --git a/app/assets/javascripts/issue_show/components/fields/description.vue b/app/assets/javascripts/issue_show/components/fields/description.vue index d27dd873125..447d7bf21a5 100644 --- a/app/assets/javascripts/issue_show/components/fields/description.vue +++ b/app/assets/javascripts/issue_show/components/fields/description.vue @@ -39,7 +39,7 @@ export default { <template> <div class="common-note-form"> - <label class="sr-only" for="issue-description"> Description </label> + <label class="sr-only" for="issue-description">{{ __('Description') }}</label> <markdown-field :markdown-preview-path="markdownPreviewPath" :markdown-docs-path="markdownDocsPath" @@ -55,8 +55,8 @@ export default { qa-description-textarea" dir="auto" data-supports-quick-actions="false" - aria-label="Description" - placeholder="Write a comment or drag your files here…" + :aria-label="__('Description')" + :placeholder="__('Write a comment or drag your files here…')" @keydown.meta.enter="updateIssuable" @keydown.ctrl.enter="updateIssuable" > diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue index 14f0acf6540..6f955928d8e 100644 --- a/app/assets/javascripts/issue_show/components/fields/description_template.vue +++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue @@ -56,22 +56,31 @@ export default { data-selected="null" data-toggle="dropdown" > - <span class="dropdown-toggle-text"> Choose a template </span> + <span class="dropdown-toggle-text">{{ __('Choose a template') }}</span> <i aria-hidden="true" class="fa fa-chevron-down"> </i> </button> <div class="dropdown-menu dropdown-select"> <div class="dropdown-title"> Choose a template - <button class="dropdown-title-button dropdown-menu-close" aria-label="Close" type="button"> + <button + class="dropdown-title-button dropdown-menu-close" + :aria-label="__('Close')" + type="button" + > <i aria-hidden="true" class="fa fa-times dropdown-menu-close-icon"> </i> </button> </div> <div class="dropdown-input"> - <input type="search" class="dropdown-input-field" placeholder="Filter" autocomplete="off" /> + <input + type="search" + class="dropdown-input-field" + :placeholder="__('Filter')" + autocomplete="off" + /> <i aria-hidden="true" class="fa fa-search dropdown-input-search"> </i> <i role="button" - aria-label="Clear templates search input" + :aria-label="__('Clear templates search input')" class="fa fa-times dropdown-input-clear js-dropdown-input-clear" > </i> @@ -79,8 +88,12 @@ export default { <div class="dropdown-content"></div> <div class="dropdown-footer"> <ul class="dropdown-footer-list"> - <li><a class="no-template"> No template </a></li> - <li><a class="reset-template"> Reset template </a></li> + <li> + <a class="no-template">{{ __('No template') }}</a> + </li> + <li> + <a class="reset-template">{{ __('Reset template') }}</a> + </li> </ul> </div> </div> diff --git a/app/assets/javascripts/issue_show/components/fields/title.vue b/app/assets/javascripts/issue_show/components/fields/title.vue index ce4baf17d09..34eb0451d53 100644 --- a/app/assets/javascripts/issue_show/components/fields/title.vue +++ b/app/assets/javascripts/issue_show/components/fields/title.vue @@ -14,7 +14,7 @@ export default { <template> <fieldset> - <label class="sr-only" for="issuable-title"> Title </label> + <label class="sr-only" for="issuable-title">{{ __('Title') }}</label> <input id="issuable-title" ref="input" @@ -22,8 +22,8 @@ export default { class="form-control qa-title-input" dir="auto" type="text" - placeholder="Title" - aria-label="Title" + :placeholder="__('Title')" + :aria-label="__('Title')" @keydown.meta.enter="updateIssuable" @keydown.ctrl.enter="updateIssuable" /> diff --git a/app/assets/javascripts/issue_show/components/locked_warning.vue b/app/assets/javascripts/issue_show/components/locked_warning.vue index 639221473b1..2f3e611e089 100644 --- a/app/assets/javascripts/issue_show/components/locked_warning.vue +++ b/app/assets/javascripts/issue_show/components/locked_warning.vue @@ -10,8 +10,9 @@ export default { <template> <div class="alert alert-danger"> - Someone edited the issue at the same time you did. Please check out - <a :href="currentPath" target="_blank" rel="nofollow">the issue</a> and make sure your changes - will not unintentionally remove theirs. + {{ sprintf(__("Someone edited the issue at the same time you did. Please check out + %{linkStart}%the issue%{linkEnd} and make sure your changes will not unintentionally remove + theirs."), { linkStart: `<a href="${currentPath}" target="_blank" rel="nofollow">` linkEnd: '</a + >', }) }} </div> </template> diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue index 607b2bd1c74..156735441ca 100644 --- a/app/assets/javascripts/jobs/components/job_log_controllers.vue +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -3,7 +3,7 @@ import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui'; import { polyfillSticky } from '~/lib/utils/sticky'; import Icon from '~/vue_shared/components/icon.vue'; import { numberToHumanSize } from '~/lib/utils/number_utils'; -import { sprintf } from '~/locale'; +import { __, sprintf } from '~/locale'; import scrollDown from '../svg/scroll_down.svg'; export default { @@ -50,7 +50,7 @@ export default { }, computed: { jobLogSize() { - return sprintf('Showing last %{size} of log -', { + return sprintf(__('Showing last %{size} of log -'), { size: numberToHumanSize(this.size), }); }, @@ -74,14 +74,12 @@ export default { <div class="js-truncated-info truncated-info d-none d-sm-block float-left"> <template v-if="isTraceSizeVisible"> {{ jobLogSize }} - <gl-link v-if="rawPath" :href="rawPath" class="js-raw-link text-plain text-underline prepend-left-5" + >{{ s__('Job|Complete Raw') }}</gl-link > - {{ s__('Job|Complete Raw') }} - </gl-link> </template> </div> <!-- eo truncate information --> diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue index 24276c06486..e9704584c9f 100644 --- a/app/assets/javascripts/jobs/components/sidebar.vue +++ b/app/assets/javascripts/jobs/components/sidebar.vue @@ -1,4 +1,5 @@ <script> +import { __, sprintf } from '~/locale'; import _ from 'underscore'; import { mapActions, mapState } from 'vuex'; import { GlLink, GlButton } from '@gitlab/ui'; @@ -63,7 +64,9 @@ export default { let t = this.job.metadata.timeout_human_readable; if (this.job.metadata.timeout_source !== '') { - t += ` (from ${this.job.metadata.timeout_source})`; + t += sprintf(__(` (from %{timeoutSource})`), { + timeoutSource: this.job.metadata.timeout_source, + }); } return t; diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index d521c462ad8..062d21ed247 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -479,9 +479,13 @@ export const pikadayToString = date => { * Seconds can be negative or positive, zero or non-zero. Can be configured for any day * or week length. */ -export const parseSeconds = (seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) => { +export const parseSeconds = ( + seconds, + { daysPerWeek = 5, hoursPerDay = 8, limitToHours = false } = {}, +) => { const DAYS_PER_WEEK = daysPerWeek; const HOURS_PER_DAY = hoursPerDay; + const SECONDS_PER_MINUTE = 60; const MINUTES_PER_HOUR = 60; const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR; const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; @@ -493,9 +497,18 @@ export const parseSeconds = (seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) minutes: 1, }; - let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR); + if (limitToHours) { + timePeriodConstraints.weeks = 0; + timePeriodConstraints.days = 0; + } + + let unorderedMinutes = Math.abs(seconds / SECONDS_PER_MINUTE); return _.mapObject(timePeriodConstraints, minutesPerPeriod => { + if (minutesPerPeriod === 0) { + return 0; + } + const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); unorderedMinutes -= periodCount * minutesPerPeriod; diff --git a/app/assets/javascripts/manual_ordering.js b/app/assets/javascripts/manual_ordering.js new file mode 100644 index 00000000000..e16ddbfef7e --- /dev/null +++ b/app/assets/javascripts/manual_ordering.js @@ -0,0 +1,58 @@ +import Sortable from 'sortablejs'; +import { s__ } from '~/locale'; +import createFlash from '~/flash'; +import { + getBoardSortableDefaultOptions, + sortableStart, +} from '~/boards/mixins/sortable_default_options'; +import axios from '~/lib/utils/axios_utils'; + +const updateIssue = (url, issueList, { move_before_id, move_after_id }) => + axios + .put(`${url}/reorder`, { + move_before_id, + move_after_id, + group_full_path: issueList.dataset.groupFullPath, + }) + .catch(() => { + createFlash(s__("ManualOrdering|Couldn't save the order of the issues")); + }); + +const initManualOrdering = () => { + const issueList = document.querySelector('.manual-ordering'); + + if (!issueList || !(gon.features && gon.features.manualSorting)) { + return; + } + + Sortable.create( + issueList, + getBoardSortableDefaultOptions({ + scroll: true, + dataIdAttr: 'data-id', + fallbackOnBody: false, + group: { + name: 'issues', + }, + draggable: 'li.issue', + onStart: () => { + sortableStart(); + }, + onUpdate: event => { + const el = event.item; + + const url = el.getAttribute('url'); + + const prev = el.previousElementSibling; + const next = el.nextElementSibling; + + const beforeId = prev && parseInt(prev.dataset.id, 10); + const afterId = next && parseInt(next.dataset.id, 10); + + updateIssue(url, issueList, { move_after_id: afterId, move_before_id: beforeId }); + }, + }), + ); +}; + +export default initManualOrdering; diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue index 9de4e96e4da..9a3ce5174db 100644 --- a/app/assets/javascripts/monitoring/components/charts/area.vue +++ b/app/assets/javascripts/monitoring/components/charts/area.vue @@ -1,4 +1,5 @@ <script> +import { __ } from '~/locale'; import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; import dateFormat from 'dateformat'; import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils'; @@ -99,7 +100,7 @@ export default { chartOptions() { return { xAxis: { - name: 'Time', + name: __('Time'), type: 'time', axisLabel: { formatter: date => dateFormat(date, 'h:MM TT'), diff --git a/app/assets/javascripts/monitoring/components/charts/column.vue b/app/assets/javascripts/monitoring/components/charts/column.vue new file mode 100644 index 00000000000..05a2036f4c3 --- /dev/null +++ b/app/assets/javascripts/monitoring/components/charts/column.vue @@ -0,0 +1,131 @@ +<script> +import { GlColumnChart } from '@gitlab/ui/dist/charts'; +import { debounceByAnimationFrame } from '~/lib/utils/common_utils'; +import { getSvgIconPathContent } from '~/lib/utils/icon_utils'; +import { chartHeight } from '../../constants'; +import { makeDataSeries } from '~/helpers/monitor_helper'; + +export default { + components: { + GlColumnChart, + }, + inheritAttrs: false, + props: { + graphData: { + type: Object, + required: true, + validator(data) { + return ( + Array.isArray(data.queries) && + data.queries.filter(query => { + if (Array.isArray(query.result)) { + return ( + query.result.filter(res => Array.isArray(res.values)).length === query.result.length + ); + } + return false; + }).length === data.queries.length + ); + }, + containerWidth: { + type: Number, + required: true, + }, + }, + }, + data() { + return { + width: 0, + height: chartHeight, + svgs: {}, + debouncedResizeCallback: {}, + }; + }, + computed: { + chartData() { + const queryData = this.graphData.queries.reduce((acc, query) => { + const series = makeDataSeries(query.result, { + name: this.formatLegendLabel(query), + }); + + return acc.concat(series); + }, []); + + return { + values: queryData[0].data, + }; + }, + xAxisTitle() { + return this.graphData.queries[0].result[0].x_label !== undefined + ? this.graphData.queries[0].result[0].x_label + : ''; + }, + yAxisTitle() { + return this.graphData.queries[0].result[0].y_label !== undefined + ? this.graphData.queries[0].result[0].y_label + : ''; + }, + xAxisType() { + return this.graphData.x_type !== undefined ? this.graphData.x_type : 'category'; + }, + dataZoomConfig() { + const handleIcon = this.svgs['scroll-handle']; + + return handleIcon ? { handleIcon } : {}; + }, + chartOptions() { + return { + dataZoom: this.dataZoomConfig, + }; + }, + }, + watch: { + containerWidth: 'onResize', + }, + beforeDestroy() { + window.removeEventListener('resize', this.debouncedResizeCallback); + }, + created() { + this.debouncedResizeCallback = debounceByAnimationFrame(this.onResize); + window.addEventListener('resize', this.debouncedResizeCallback); + this.setSvg('scroll-handle'); + }, + methods: { + formatLegendLabel(query) { + return `${query.label}`; + }, + onResize() { + const { width } = this.$refs.columnChart.$el.getBoundingClientRect(); + this.width = width; + }, + setSvg(name) { + getSvgIconPathContent(name) + .then(path => { + if (path) { + this.$set(this.svgs, name, `path://${path}`); + } + }) + .catch(() => {}); + }, + }, +}; +</script> +<template> + <div class="prometheus-graph col-12 col-lg-6"> + <div class="prometheus-graph-header"> + <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5> + <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div> + </div> + <gl-column-chart + ref="columnChart" + v-bind="$attrs" + :data="chartData" + :option="chartOptions" + :width="width" + :height="height" + :x-axis-title="xAxisTitle" + :y-axis-title="yAxisTitle" + :x-axis-type="xAxisType" + /> + </div> +</template> diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 0a652329dfe..23687c54fd3 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -106,17 +106,24 @@ export default { }, customMetricsPath: { type: String, - required: true, + required: false, + default: invalidUrl, }, validateQueryPath: { type: String, - required: true, + required: false, + default: invalidUrl, }, dashboardEndpoint: { type: String, required: false, default: invalidUrl, }, + currentDashboard: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -139,10 +146,15 @@ export default { 'deploymentData', 'metricsWithData', 'useDashboardEndpoint', + 'allDashboards', + 'multipleDashboardsEnabled', ]), groupsWithData() { return this.groups.filter(group => this.chartsWithData(group.metrics).length > 0); }, + selectedDashboardText() { + return this.currentDashboard || (this.allDashboards[0] && this.allDashboards[0].display_name); + }, }, created() { this.setEndpoints({ @@ -150,6 +162,7 @@ export default { environmentsEndpoint: this.environmentsEndpoint, deploymentsEndpoint: this.deploymentsEndpoint, dashboardEndpoint: this.dashboardEndpoint, + currentDashboard: this.currentDashboard, }); this.timeWindows = timeWindows; @@ -234,12 +247,30 @@ export default { </script> <template> - <div v-if="!showEmptyState" class="prometheus-graphs"> + <div class="prometheus-graphs"> <div class="gl-p-3 border-bottom bg-gray-light d-flex justify-content-between"> <div v-if="environmentsEndpoint" class="dropdowns d-flex align-items-center justify-content-between" > + <div v-if="multipleDashboardsEnabled" class="d-flex align-items-center"> + <label class="mb-0">{{ __('Dashboard') }}</label> + <gl-dropdown + class="ml-2 mr-3 js-dashboards-dropdown" + toggle-class="dropdown-menu-toggle" + :text="selectedDashboardText" + > + <gl-dropdown-item + v-for="dashboard in allDashboards" + :key="dashboard.path" + :active="dashboard.path === currentDashboard" + active-class="is-active" + :href="`?dashboard=${dashboard.path}`" + > + {{ dashboard.display_name || dashboard.path }} + </gl-dropdown-item> + </gl-dropdown> + </div> <div class="d-flex align-items-center"> <strong>{{ s__('Metrics|Environment') }}</strong> <gl-dropdown @@ -253,11 +284,12 @@ export default { :key="environment.id" :active="environment.name === currentEnvironmentName" active-class="is-active" + :href="environment.metrics_path" >{{ environment.name }}</gl-dropdown-item > </gl-dropdown> </div> - <div class="d-flex align-items-center prepend-left-8"> + <div v-if="!showEmptyState" class="d-flex align-items-center prepend-left-8"> <strong>{{ s__('Metrics|Show last') }}</strong> <gl-dropdown class="prepend-left-10 js-time-window-dropdown" @@ -276,7 +308,7 @@ export default { </div> </div> <div class="d-flex"> - <div v-if="isEE && canAddMetrics"> + <div v-if="isEE && canAddMetrics && !showEmptyState"> <gl-button v-gl-modal-directive="$options.addMetric.modalId" class="js-add-metric-button text-success border-success" @@ -317,40 +349,42 @@ export default { </gl-button> </div> </div> - <graph-group - v-for="(groupData, index) in groupsWithData" - :key="index" - :name="groupData.group" - :show-panels="showPanels" - > - <monitor-area-chart - v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)" - :key="graphIndex" - :graph-data="graphData" - :deployment-data="deploymentData" - :thresholds="getGraphAlertValues(graphData.queries)" - :container-width="elWidth" - group-id="monitor-area-chart" + <div v-if="!showEmptyState"> + <graph-group + v-for="(groupData, index) in groupsWithData" + :key="index" + :name="groupData.group" + :show-panels="showPanels" > - <alert-widget - v-if="isEE && prometheusAlertsAvailable && alertsEndpoint && graphData" - :alerts-endpoint="alertsEndpoint" - :relevant-queries="graphData.queries" - :alerts-to-manage="getGraphAlerts(graphData.queries)" - @setAlerts="setAlerts" - /> - </monitor-area-chart> - </graph-group> + <monitor-area-chart + v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)" + :key="graphIndex" + :graph-data="graphData" + :deployment-data="deploymentData" + :thresholds="getGraphAlertValues(graphData.queries)" + :container-width="elWidth" + group-id="monitor-area-chart" + > + <alert-widget + v-if="isEE && prometheusAlertsAvailable && alertsEndpoint && graphData" + :alerts-endpoint="alertsEndpoint" + :relevant-queries="graphData.queries" + :alerts-to-manage="getGraphAlerts(graphData.queries)" + @setAlerts="setAlerts" + /> + </monitor-area-chart> + </graph-group> + </div> + <empty-state + v-else + :selected-state="emptyState" + :documentation-path="documentationPath" + :settings-path="settingsPath" + :clusters-path="clustersPath" + :empty-getting-started-svg-path="emptyGettingStartedSvgPath" + :empty-loading-svg-path="emptyLoadingSvgPath" + :empty-no-data-svg-path="emptyNoDataSvgPath" + :empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath" + /> </div> - <empty-state - v-else - :selected-state="emptyState" - :documentation-path="documentationPath" - :settings-path="settingsPath" - :clusters-path="clustersPath" - :empty-getting-started-svg-path="emptyGettingStartedSvgPath" - :empty-loading-svg-path="emptyLoadingSvgPath" - :empty-no-data-svg-path="emptyNoDataSvgPath" - :empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath" - /> </template> diff --git a/app/assets/javascripts/monitoring/components/empty_state.vue b/app/assets/javascripts/monitoring/components/empty_state.vue index 0e141d02ead..a3c6de14aa4 100644 --- a/app/assets/javascripts/monitoring/components/empty_state.vue +++ b/app/assets/javascripts/monitoring/components/empty_state.vue @@ -1,4 +1,6 @@ <script> +import { __ } from '~/locale'; + export default { props: { documentationPath: { @@ -41,35 +43,35 @@ export default { states: { gettingStarted: { svgUrl: this.emptyGettingStartedSvgPath, - title: 'Get started with performance monitoring', - description: `Stay updated about the performance and health - of your environment by configuring Prometheus to monitor your deployments.`, - buttonText: 'Install on clusters', + title: __('Get started with performance monitoring'), + description: __(`Stay updated about the performance and health + of your environment by configuring Prometheus to monitor your deployments.`), + buttonText: __('Install on clusters'), buttonPath: this.clustersPath, - secondaryButtonText: 'Configure existing installation', + secondaryButtonText: __('Configure existing installation'), secondaryButtonPath: this.settingsPath, }, loading: { svgUrl: this.emptyLoadingSvgPath, - title: 'Waiting for performance data', - description: `Creating graphs uses the data from the Prometheus server. - If this takes a long time, ensure that data is available.`, - buttonText: 'View documentation', + title: __('Waiting for performance data'), + description: __(`Creating graphs uses the data from the Prometheus server. + If this takes a long time, ensure that data is available.`), + buttonText: __('View documentation'), buttonPath: this.documentationPath, }, noData: { svgUrl: this.emptyNoDataSvgPath, - title: 'No data found', - description: `You are connected to the Prometheus server, but there is currently - no data to display.`, - buttonText: 'Configure Prometheus', + title: __('No data found'), + description: __(`You are connected to the Prometheus server, but there is currently + no data to display.`), + buttonText: __('Configure Prometheus'), buttonPath: this.settingsPath, }, unableToConnect: { svgUrl: this.emptyUnableToConnectSvgPath, - title: 'Unable to connect to Prometheus server', + title: __('Unable to connect to Prometheus server'), description: 'Ensure connectivity is available from the GitLab server to the ', - buttonText: 'View documentation', + buttonText: __('View documentation'), buttonPath: this.documentationPath, }, }, @@ -90,7 +92,9 @@ export default { <template> <div class="row empty-state js-empty-state"> <div class="col-12"> - <div class="state-svg svg-content"><img :src="currentState.svgUrl" /></div> + <div class="state-svg svg-content"> + <img :src="currentState.svgUrl" /> + </div> </div> <div class="col-12"> @@ -98,20 +102,22 @@ export default { <h4 class="state-title text-center">{{ currentState.title }}</h4> <p class="state-description"> {{ currentState.description }} - <a v-if="showButtonDescription" :href="settingsPath"> Prometheus server </a> + <a v-if="showButtonDescription" :href="settingsPath">{{ __('Prometheus server') }}</a> </p> <div class="text-center"> - <a v-if="currentState.buttonPath" :href="currentState.buttonPath" class="btn btn-success"> - {{ currentState.buttonText }} - </a> + <a + v-if="currentState.buttonPath" + :href="currentState.buttonPath" + class="btn btn-success" + >{{ currentState.buttonText }}</a + > <a v-if="currentState.secondaryButtonPath" :href="currentState.secondaryButtonPath" class="btn" + >{{ currentState.secondaryButtonText }}</a > - {{ currentState.secondaryButtonText }} - </a> </div> </div> </div> diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js index 1d33537b3b2..edbcf84b342 100644 --- a/app/assets/javascripts/monitoring/monitoring_bundle.js +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import { parseBoolean } from '~/lib/utils/common_utils'; +import { getParameterValues } from '~/lib/utils/url_utility'; import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue'; import store from './stores'; @@ -7,10 +8,12 @@ export default (props = {}) => { const el = document.getElementById('prometheus-graphs'); if (el && el.dataset) { - store.dispatch( - 'monitoringDashboard/setDashboardEnabled', - gon.features.environmentMetricsUsePrometheusEndpoint, - ); + store.dispatch('monitoringDashboard/setFeatureFlags', { + prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint, + multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards, + }); + + const [currentDashboard] = getParameterValues('dashboard'); // eslint-disable-next-line no-new new Vue({ @@ -20,6 +23,7 @@ export default (props = {}) => { return createElement(Dashboard, { props: { ...el.dataset, + currentDashboard, hasMetrics: parseBoolean(el.dataset.hasMetrics), ...props, }, diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index f41e215cb5d..0fa2a5d6370 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -35,14 +35,24 @@ export const setEndpoints = ({ commit }, endpoints) => { commit(types.SET_ENDPOINTS, endpoints); }; -export const setDashboardEnabled = ({ commit }, enabled) => { - commit(types.SET_DASHBOARD_ENABLED, enabled); +export const setFeatureFlags = ( + { commit }, + { prometheusEndpointEnabled, multipleDashboardsEnabled }, +) => { + commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled); + commit(types.SET_MULTIPLE_DASHBOARDS_ENABLED, multipleDashboardsEnabled); }; export const requestMetricsDashboard = ({ commit }) => { commit(types.REQUEST_METRICS_DATA); }; -export const receiveMetricsDashboardSuccess = ({ commit, dispatch }, { response, params }) => { +export const receiveMetricsDashboardSuccess = ( + { state, commit, dispatch }, + { response, params }, +) => { + if (state.multipleDashboardsEnabled) { + commit(types.SET_ALL_DASHBOARDS, response.all_dashboards); + } commit(types.RECEIVE_METRICS_DATA_SUCCESS, response.dashboard.panel_groups); dispatch('fetchPrometheusMetrics', params); }; @@ -95,6 +105,11 @@ export const fetchMetricsData = ({ state, dispatch }, params) => { export const fetchDashboard = ({ state, dispatch }, params) => { dispatch('requestMetricsDashboard'); + if (state.currentDashboard) { + // eslint-disable-next-line no-param-reassign + params.dashboard = state.currentDashboard; + } + return axios .get(state.dashboardEndpoint, { params }) .then(resp => resp.data) diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js index 63894e83362..2c78a0b9315 100644 --- a/app/assets/javascripts/monitoring/stores/mutation_types.js +++ b/app/assets/javascripts/monitoring/stores/mutation_types.js @@ -10,6 +10,8 @@ export const RECEIVE_ENVIRONMENTS_DATA_FAILURE = 'RECEIVE_ENVIRONMENTS_DATA_FAIL export const SET_QUERY_RESULT = 'SET_QUERY_RESULT'; export const SET_TIME_WINDOW = 'SET_TIME_WINDOW'; export const SET_DASHBOARD_ENABLED = 'SET_DASHBOARD_ENABLED'; +export const SET_MULTIPLE_DASHBOARDS_ENABLED = 'SET_MULTIPLE_DASHBOARDS_ENABLED'; +export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS'; export const SET_ENDPOINTS = 'SET_ENDPOINTS'; export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE'; export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE'; diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js index d4b816e2717..a85a7723c1f 100644 --- a/app/assets/javascripts/monitoring/stores/mutations.js +++ b/app/assets/javascripts/monitoring/stores/mutations.js @@ -74,10 +74,14 @@ export default { state.environmentsEndpoint = endpoints.environmentsEndpoint; state.deploymentsEndpoint = endpoints.deploymentsEndpoint; state.dashboardEndpoint = endpoints.dashboardEndpoint; + state.currentDashboard = endpoints.currentDashboard; }, [types.SET_DASHBOARD_ENABLED](state, enabled) { state.useDashboardEndpoint = enabled; }, + [types.SET_MULTIPLE_DASHBOARDS_ENABLED](state, enabled) { + state.multipleDashboardsEnabled = enabled; + }, [types.SET_GETTING_STARTED_EMPTY_STATE](state) { state.emptyState = 'gettingStarted'; }, @@ -85,4 +89,7 @@ export default { state.showEmptyState = true; state.emptyState = 'noData'; }, + [types.SET_ALL_DASHBOARDS](state, dashboards) { + state.allDashboards = dashboards; + }, }; diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js index c33529cd588..de711d6ccae 100644 --- a/app/assets/javascripts/monitoring/stores/state.js +++ b/app/assets/javascripts/monitoring/stores/state.js @@ -8,10 +8,13 @@ export default () => ({ deploymentsEndpoint: null, dashboardEndpoint: invalidUrl, useDashboardEndpoint: false, + multipleDashboardsEnabled: false, emptyState: 'gettingStarted', showEmptyState: true, groups: [], deploymentData: [], environments: [], metricsWithData: [], + allDashboards: [], + currentDashboard: null, }); diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index eb6a4a67fff..10b15a9c38c 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -382,7 +382,7 @@ Please check your network connection and try again.`; class="discussion-reply-holder" > <user-avatar-link - v-if="!isReplying && currentUser" + v-if="!isReplying && userCanReply" :link-href="currentUser.path" :img-src="currentUser.avatar_url" :img-alt="currentUser.name" diff --git a/app/assets/javascripts/pages/dashboard/issues/index.js b/app/assets/javascripts/pages/dashboard/issues/index.js index 9055738f86e..2ffeed8a584 100644 --- a/app/assets/javascripts/pages/dashboard/issues/index.js +++ b/app/assets/javascripts/pages/dashboard/issues/index.js @@ -2,6 +2,7 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { initFilteredSearch({ @@ -10,4 +11,5 @@ document.addEventListener('DOMContentLoaded', () => { }); projectSelect(); + initManualOrdering(); }); diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 35d4b034654..23fb5656008 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -2,6 +2,7 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); @@ -12,4 +13,5 @@ document.addEventListener('DOMContentLoaded', () => { filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); projectSelect(); + initManualOrdering(); }); diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js index 8fa266a37ce..29de3b7806c 100644 --- a/app/assets/javascripts/pages/projects/branches/index/index.js +++ b/app/assets/javascripts/pages/projects/branches/index/index.js @@ -1,7 +1,9 @@ import AjaxLoadingSpinner from '~/ajax_loading_spinner'; import DeleteModal from '~/branches/branches_delete_modal'; +import initDiverganceGraph from '~/branches/divergence_graph'; document.addEventListener('DOMContentLoaded', () => { AjaxLoadingSpinner.init(); new DeleteModal(); // eslint-disable-line no-new + initDiverganceGraph(); }); diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index c34aff02111..c73ebb31eb3 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -7,6 +7,7 @@ import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; +import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { IssuableFilteredSearchTokenKeys.addExtraTokensForIssues(); @@ -19,4 +20,5 @@ document.addEventListener('DOMContentLoaded', () => { new ShortcutsNavigation(); new UsersSelect(); + initManualOrdering(); }); diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index 7f800d20835..1d8b388e935 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -18,12 +18,12 @@ import UserOverviewBlock from './user_overview_block'; * * <ul class="nav-links"> * <li class="activity-tab active"> - * <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username"> + * <a data-action="activity" data-target="#activity" data-toggle="tab" href="/username"> * Activity * </a> * </li> * <li class="groups-tab"> - * <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups"> + * <a data-action="groups" data-target="#groups" data-toggle="tab" href="/users/username/groups"> * Groups * </a> * </li> diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index c41ecab1294..65a2b61396c 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -60,7 +60,7 @@ export default { </script> <template> <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags"> - <gl-link :href="pipeline.path" class="js-pipeline-url-link"> + <gl-link :href="pipeline.path" class="js-pipeline-url-link js-onboarding-pipeline-item"> <span class="pipeline-id">#{{ pipeline.id }}</span> </gl-link> <div class="label-container"> diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js index d67d88c4dba..c8819cf35cf 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js @@ -1,8 +1,8 @@ import Visibility from 'visibilityjs'; +import PipelineStore from 'ee_else_ce/pipelines/stores/pipeline_store'; import Flash from '../flash'; import Poll from '../lib/utils/poll'; import { __ } from '../locale'; -import PipelineStore from './stores/pipeline_store'; import PipelineService from './services/pipeline_service'; export default class pipelinesMediator { diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index 0d4d431855c..67963dc1923 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -36,7 +36,7 @@ export default { to: `/tree/${this.ref}${path}`, }); }, - [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}` }], + [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }], ); }, }, diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue index 891e3fe9d16..1e66ccbfa29 100644 --- a/app/assets/javascripts/repository/components/table/index.vue +++ b/app/assets/javascripts/repository/components/table/index.vue @@ -131,7 +131,9 @@ export default { v-for="entry in val" :id="entry.id" :key="`${entry.flatPath}-${entry.id}`" + :project-path="projectPath" :current-path="path" + :name="entry.name" :path="entry.flatPath" :type="entry.type" :url="entry.webUrl" diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index 4519f82fc93..c31e7fa71a2 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -1,12 +1,30 @@ <script> -import { GlBadge } from '@gitlab/ui'; +import { GlBadge, GlLink, GlSkeletonLoading } from '@gitlab/ui'; import { visitUrl } from '~/lib/utils/url_utility'; +import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { getIconName } from '../../utils/icon'; import getRefMixin from '../../mixins/get_ref'; +import getCommit from '../../queries/getCommit.query.graphql'; export default { components: { GlBadge, + GlLink, + GlSkeletonLoading, + TimeagoTooltip, + }, + apollo: { + commit: { + query: getCommit, + variables() { + return { + fileName: this.name, + type: this.type, + path: this.currentPath, + projectPath: this.projectPath, + }; + }, + }, }, mixins: [getRefMixin], props: { @@ -14,10 +32,18 @@ export default { type: String, required: true, }, + projectPath: { + type: String, + required: true, + }, currentPath: { type: String, required: true, }, + name: { + type: String, + required: true, + }, path: { type: String, required: true, @@ -37,6 +63,11 @@ export default { default: null, }, }, + data() { + return { + commit: null, + }; + }, computed: { routerLinkTo() { return this.isFolder ? { path: `/tree/${this.ref}/${this.path}` } : null; @@ -73,7 +104,7 @@ export default { </script> <template> - <tr v-once :class="`file_${id}`" class="tree-item" @click="openRow"> + <tr :class="`file_${id}`" class="tree-item" @click="openRow"> <td class="tree-item-file-name"> <i :aria-label="type" role="img" :class="iconName" class="fa fa-fw"></i> <component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated"> @@ -83,10 +114,18 @@ export default { LFS </gl-badge> <template v-if="isSubmodule"> - @ <a href="#" class="commit-sha">{{ shortSha }}</a> + @ <gl-link href="#" class="commit-sha">{{ shortSha }}</gl-link> </template> </td> - <td class="d-none d-sm-table-cell tree-commit"></td> - <td class="tree-time-ago text-right"></td> + <td class="d-none d-sm-table-cell tree-commit"> + <gl-link v-if="commit" :href="commit.commitPath" class="str-truncated-100 tree-commit-link"> + {{ commit.message }} + </gl-link> + <gl-skeleton-loading v-else :lines="1" class="h-auto" /> + </td> + <td class="tree-time-ago text-right"> + <timeago-tooltip v-if="commit" :time="commit.committedDate" tooltip-placement="bottom" /> + <gl-skeleton-loading v-else :lines="1" class="ml-auto h-auto w-50" /> + </td> </tr> </template> diff --git a/app/assets/javascripts/repository/graphql.js b/app/assets/javascripts/repository/graphql.js index ef147ec15cb..6cb253c8169 100644 --- a/app/assets/javascripts/repository/graphql.js +++ b/app/assets/javascripts/repository/graphql.js @@ -3,6 +3,7 @@ import VueApollo from 'vue-apollo'; import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import createDefaultClient from '~/lib/graphql'; import introspectionQueryResultData from './fragmentTypes.json'; +import { fetchLogsTree } from './log_tree'; Vue.use(VueApollo); @@ -13,7 +14,21 @@ const fragmentMatcher = new IntrospectionFragmentMatcher({ }); const defaultClient = createDefaultClient( - {}, + { + Query: { + commit(_, { path, fileName, type }) { + return new Promise(resolve => { + fetchLogsTree(defaultClient, path, '0', { + resolve, + entry: { + name: fileName, + type, + }, + }); + }); + }, + }, + }, { cacheConfig: { fragmentMatcher, diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js index d9216e88676..6280977b05b 100644 --- a/app/assets/javascripts/repository/index.js +++ b/app/assets/javascripts/repository/index.js @@ -16,6 +16,7 @@ export default function setupVueRepositoryList() { projectPath, projectShortPath, ref, + commits: [], }, }); diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js new file mode 100644 index 00000000000..2c19aca2397 --- /dev/null +++ b/app/assets/javascripts/repository/log_tree.js @@ -0,0 +1,64 @@ +import axios from '~/lib/utils/axios_utils'; +import getCommits from './queries/getCommits.query.graphql'; +import getProjectPath from './queries/getProjectPath.query.graphql'; +import getRef from './queries/getRef.query.graphql'; + +let fetchpromise; +let resolvers = []; + +export function normalizeData(data) { + return data.map(d => ({ + sha: d.commit.id, + message: d.commit.message, + committedDate: d.commit.committed_date, + commitPath: d.commit_path, + fileName: d.file_name, + type: d.type, + __typename: 'LogTreeCommit', + })); +} + +export function resolveCommit(commits, { resolve, entry }) { + const commit = commits.find(c => c.fileName === entry.name && c.type === entry.type); + + if (commit) { + resolve(commit); + } +} + +export function fetchLogsTree(client, path, offset, resolver = null) { + if (resolver) { + resolvers.push(resolver); + } + + if (fetchpromise) return fetchpromise; + + const { projectPath } = client.readQuery({ query: getProjectPath }); + const { ref } = client.readQuery({ query: getRef }); + + fetchpromise = axios + .get(`${gon.gitlab_url}/${projectPath}/refs/${ref}/logs_tree${path ? `/${path}` : ''}`, { + params: { format: 'json', offset }, + }) + .then(({ data, headers }) => { + const headerLogsOffset = headers['more-logs-offset']; + const { commits } = client.readQuery({ query: getCommits }); + const newCommitData = [...commits, ...normalizeData(data)]; + client.writeQuery({ + query: getCommits, + data: { commits: newCommitData }, + }); + + resolvers.forEach(r => resolveCommit(newCommitData, r)); + + fetchpromise = null; + + if (headerLogsOffset) { + fetchLogsTree(client, path, headerLogsOffset); + } else { + resolvers = []; + } + }); + + return fetchpromise; +} diff --git a/app/assets/javascripts/repository/queries/getCommit.query.graphql b/app/assets/javascripts/repository/queries/getCommit.query.graphql new file mode 100644 index 00000000000..e2a2d831e47 --- /dev/null +++ b/app/assets/javascripts/repository/queries/getCommit.query.graphql @@ -0,0 +1,10 @@ +query getCommit($fileName: String!, $type: String!, $path: String!) { + commit(path: $path, fileName: $fileName, type: $type) @client { + sha + message + committedDate + commitPath + fileName + type + } +} diff --git a/app/assets/javascripts/repository/queries/getCommits.query.graphql b/app/assets/javascripts/repository/queries/getCommits.query.graphql new file mode 100644 index 00000000000..df9e67cc440 --- /dev/null +++ b/app/assets/javascripts/repository/queries/getCommits.query.graphql @@ -0,0 +1,10 @@ +query getCommits { + commits @client { + sha + message + committedDate + commitPath + fileName + type + } +} diff --git a/app/assets/javascripts/repository/queries/getFiles.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql index ef924fde556..4c24fc4087f 100644 --- a/app/assets/javascripts/repository/queries/getFiles.query.graphql +++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql @@ -1,5 +1,6 @@ fragment TreeEntry on Entry { id + name flatPath type } diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue index f4d926cd3ec..bc263bc36e4 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue @@ -28,11 +28,16 @@ export default { type: String, required: true, }, + limitToHours: { + type: Boolean, + required: false, + default: false, + }, }, computed: { parsedTimeRemaining() { const diffSeconds = this.timeEstimate - this.timeSpent; - return parseSeconds(diffSeconds); + return parseSeconds(diffSeconds, { limitToHours: this.limitToHours }); }, timeRemainingHumanReadable() { return stringifyTime(this.parsedTimeRemaining); @@ -65,9 +70,6 @@ export default { :title="timeRemainingTooltip" :class="timeRemainingStatusClass" class="compare-meter" - data-toggle="tooltip" - data-placement="top" - role="timeRemainingDisplay" > <gl-progress-bar :value="timeRemainingPercent" :variant="progressBarVariant" /> <div class="compare-display-container"> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue index 8e8b9f19b6e..018b30d2a67 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue @@ -53,6 +53,7 @@ export default { :time-spent="store.totalTimeSpent" :human-time-estimate="store.humanTimeEstimate" :human-time-spent="store.humanTotalTimeSpent" + :limit-to-hours="store.timeTrackingLimitToHours" :root-path="store.rootPath" /> </div> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index d84d5344935..682ca600b6a 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -37,6 +37,10 @@ export default { required: false, default: '', }, + limitToHours: { + type: Boolean, + default: false, + }, rootPath: { type: String, required: true, @@ -129,6 +133,7 @@ export default { :time-spent="timeSpent" :time-spent-human-readable="humanTimeSpent" :time-estimate-human-readable="humanTimeEstimate" + :limit-to-hours="limitToHours" /> <transition name="help-state-toggle"> <time-tracking-help-state v-if="showHelpState" :root-path="rootPath" /> diff --git a/app/assets/javascripts/sidebar/mount_milestone_sidebar.js b/app/assets/javascripts/sidebar/mount_milestone_sidebar.js index 1ebdbec7bc9..d934463382f 100644 --- a/app/assets/javascripts/sidebar/mount_milestone_sidebar.js +++ b/app/assets/javascripts/sidebar/mount_milestone_sidebar.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import timeTracker from './components/time_tracking/time_tracker.vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; export default class SidebarMilestone { constructor() { @@ -7,7 +8,7 @@ export default class SidebarMilestone { if (!el) return; - const { timeEstimate, timeSpent, humanTimeEstimate, humanTimeSpent } = el.dataset; + const { timeEstimate, timeSpent, humanTimeEstimate, humanTimeSpent, limitToHours } = el.dataset; // eslint-disable-next-line no-new new Vue({ @@ -22,6 +23,7 @@ export default class SidebarMilestone { timeSpent: parseInt(timeSpent, 10), humanTimeEstimate, humanTimeSpent, + limitToHours: parseBoolean(limitToHours), rootPath: '/', }, }), diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js index 22ac8df9699..643fe6c00b6 100644 --- a/app/assets/javascripts/sidebar/sidebar_mediator.js +++ b/app/assets/javascripts/sidebar/sidebar_mediator.js @@ -1,7 +1,7 @@ import { visitUrl } from '../lib/utils/url_utility'; import Flash from '../flash'; import Service from './services/sidebar_service'; -import Store from './stores/sidebar_store'; +import Store from 'ee_else_ce/sidebar/stores/sidebar_store'; import { __ } from '~/locale'; export default class SidebarMediator { diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js index 7b8b4c5d856..63c4a2a3f84 100644 --- a/app/assets/javascripts/sidebar/stores/sidebar_store.js +++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js @@ -8,7 +8,7 @@ export default class SidebarStore { } initSingleton(options) { - const { currentUser, rootPath, editable } = options; + const { currentUser, rootPath, editable, timeTrackingLimitToHours } = options; this.currentUser = currentUser; this.rootPath = rootPath; this.editable = editable; @@ -16,6 +16,7 @@ export default class SidebarStore { this.totalTimeSpent = 0; this.humanTimeEstimate = ''; this.humanTimeSpent = ''; + this.timeTrackingLimitToHours = timeTrackingLimitToHours; this.assignees = []; this.isFetching = { assignees: true, diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment.js b/app/assets/javascripts/visual_review_toolbar/components/comment.js index 2fec96d1435..04bfb5e9532 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/comment.js +++ b/app/assets/javascripts/visual_review_toolbar/components/comment.js @@ -1,54 +1,62 @@ import { BLACK, COMMENT_BOX, MUTED, LOGOUT } from './constants'; -import { clearNote, note, postError } from './note'; -import { buttonClearStyles, selectCommentBox, selectCommentButton, selectNote } from './utils'; +import { clearNote, postError } from './note'; +import { + buttonClearStyles, + selectCommentBox, + selectCommentButton, + selectNote, + selectNoteContainer, +} from './utils'; const comment = ` <div> <textarea id="${COMMENT_BOX}" name="${COMMENT_BOX}" rows="3" placeholder="Enter your feedback or idea" class="gitlab-input" aria-required="true"></textarea> - ${note} <p class="gitlab-metadata-note">Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p> </div> <div class="gitlab-button-wrapper"> - <button class="gitlab-button gitlab-button-secondary" style="${buttonClearStyles}" type="button" id="${LOGOUT}"> Logout </button> + <button class="gitlab-button gitlab-button-secondary" style="${buttonClearStyles}" type="button" id="${LOGOUT}"> Log out </button> <button class="gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="gitlab-comment-button"> Send feedback </button> </div> `; -const resetCommentBox = () => { - const commentBox = selectCommentBox(); +const resetCommentButton = () => { const commentButton = selectCommentButton(); /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ commentButton.innerText = 'Send feedback'; commentButton.classList.replace('gitlab-button-secondary', 'gitlab-button-success'); commentButton.style.opacity = 1; +}; +const resetCommentBox = () => { + const commentBox = selectCommentBox(); commentBox.style.pointerEvents = 'auto'; commentBox.style.color = BLACK; }; -const resetCommentButton = () => { +const resetCommentText = () => { const commentBox = selectCommentBox(); - const currentNote = selectNote(); - commentBox.value = ''; - currentNote.innerText = ''; }; const resetComment = () => { - resetCommentBox(); resetCommentButton(); + resetCommentBox(); + resetCommentText(); }; -const confirmAndClear = mergeRequestId => { +const confirmAndClear = feedbackInfo => { const commentButton = selectCommentButton(); const currentNote = selectNote(); + const noteContainer = selectNoteContainer(); /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ commentButton.innerText = 'Feedback sent'; - /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ - currentNote.innerText = `Your comment was successfully posted to merge request #${mergeRequestId}`; - setTimeout(resetComment, 2000); + noteContainer.style.visibility = 'visible'; + currentNote.insertAdjacentHTML('beforeend', feedbackInfo); + + setTimeout(resetComment, 1000); + setTimeout(clearNote, 6000); }; const setInProgressState = () => { @@ -71,6 +79,7 @@ const postComment = ({ innerWidth, innerHeight, projectId, + projectPath, mergeRequestId, mrUrl, token, @@ -86,6 +95,7 @@ const postComment = ({ /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ postError('Your comment appears to be empty.', COMMENT_BOX); resetCommentBox(); + resetCommentButton(); return; } @@ -114,18 +124,24 @@ const postComment = ({ }) .then(response => { if (response.ok) { - confirmAndClear(mergeRequestId); - return; + return response.json(); } throw new Error(`${response.status}: ${response.statusText}`); }) + .then(data => { + const commentId = data.notes[0].id; + const feedbackLink = `${mrUrl}/${projectPath}/merge_requests/${mergeRequestId}#note_${commentId}`; + const feedbackInfo = `Feedback sent. View at <a class="gitlab-link" href="${feedbackLink}">${projectPath} #${mergeRequestId} (comment ${commentId})</a>`; + confirmAndClear(feedbackInfo); + }) .catch(err => { postError( `Your comment could not be sent. Please try again. Error: ${err.message}`, COMMENT_BOX, ); resetCommentBox(); + resetCommentButton(); }); }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/constants.js b/app/assets/javascripts/visual_review_toolbar/components/constants.js index 32ed1153515..07fcb179d15 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/constants.js +++ b/app/assets/javascripts/visual_review_toolbar/components/constants.js @@ -2,10 +2,12 @@ const COLLAPSE_BUTTON = 'gitlab-collapse'; const COMMENT_BOX = 'gitlab-comment'; const COMMENT_BUTTON = 'gitlab-comment-button'; -const FORM = 'gitlab-form-wrapper'; +const FORM = 'gitlab-form'; +const FORM_CONTAINER = 'gitlab-form-wrapper'; const LOGIN = 'gitlab-login'; const LOGOUT = 'gitlab-logout-button'; const NOTE = 'gitlab-validation-note'; +const NOTE_CONTAINER = 'gitlab-note-wrapper'; const REMEMBER_TOKEN = 'gitlab-remember_token'; const REVIEW_CONTAINER = 'gitlab-review-container'; const TOKEN_BOX = 'gitlab-token'; @@ -16,16 +18,18 @@ const BLACK = 'rgba(46, 46, 46, 1)'; const CLEAR = 'rgba(255, 255, 255, 0)'; const MUTED = 'rgba(223, 223, 223, 0.5)'; const RED = 'rgba(219, 59, 33, 1)'; -const WHITE = 'rgba(255, 255, 255, 1)'; +const WHITE = 'rgba(250, 250, 250, 1)'; export { COLLAPSE_BUTTON, COMMENT_BOX, COMMENT_BUTTON, FORM, + FORM_CONTAINER, LOGIN, LOGOUT, NOTE, + NOTE_CONTAINER, REMEMBER_TOKEN, REVIEW_CONTAINER, TOKEN_BOX, diff --git a/app/assets/javascripts/visual_review_toolbar/components/index.js b/app/assets/javascripts/visual_review_toolbar/components/index.js index 43581818152..50b52d7d3a2 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/index.js +++ b/app/assets/javascripts/visual_review_toolbar/components/index.js @@ -1,22 +1,32 @@ import { comment, postComment } from './comment'; -import { COLLAPSE_BUTTON, COMMENT_BUTTON, LOGIN, LOGOUT, REVIEW_CONTAINER } from './constants'; +import { + COLLAPSE_BUTTON, + COMMENT_BUTTON, + FORM_CONTAINER, + LOGIN, + LOGOUT, + REVIEW_CONTAINER, +} from './constants'; import { authorizeUser, login } from './login'; +import { note } from './note'; import { selectContainer } from './utils'; -import { form, logoutUser, toggleForm } from './wrapper'; +import { buttonAndForm, logoutUser, toggleForm } from './wrapper'; import { collapseButton } from './wrapper_icons'; export { authorizeUser, + buttonAndForm, collapseButton, comment, - form, login, logoutUser, + note, postComment, selectContainer, toggleForm, COLLAPSE_BUTTON, COMMENT_BUTTON, + FORM_CONTAINER, LOGIN, LOGOUT, REVIEW_CONTAINER, diff --git a/app/assets/javascripts/visual_review_toolbar/components/login.js b/app/assets/javascripts/visual_review_toolbar/components/login.js index ce713cdc520..0a71299f041 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/login.js +++ b/app/assets/javascripts/visual_review_toolbar/components/login.js @@ -1,5 +1,5 @@ import { LOGIN, REMEMBER_TOKEN, TOKEN_BOX } from './constants'; -import { clearNote, note, postError } from './note'; +import { clearNote, postError } from './note'; import { buttonClearStyles, selectRemember, selectToken } from './utils'; import { addCommentForm } from './wrapper'; @@ -7,7 +7,6 @@ const login = ` <div> <label for="${TOKEN_BOX}" class="gitlab-label">Enter your <a class="gitlab-link" href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a></label> <input class="gitlab-input" type="password" id="${TOKEN_BOX}" name="${TOKEN_BOX}" aria-required="true" autocomplete="current-password"> - ${note} </div> <div class="gitlab-checkbox-wrapper"> <input type="checkbox" id="${REMEMBER_TOKEN}" name="${REMEMBER_TOKEN}" value="remember"> diff --git a/app/assets/javascripts/visual_review_toolbar/components/note.js b/app/assets/javascripts/visual_review_toolbar/components/note.js index dfebf58fd95..0150f640aae 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/note.js +++ b/app/assets/javascripts/visual_review_toolbar/components/note.js @@ -1,14 +1,19 @@ -import { NOTE, RED } from './constants'; -import { selectById, selectNote } from './utils'; +import { NOTE, NOTE_CONTAINER, RED } from './constants'; +import { selectById, selectNote, selectNoteContainer } from './utils'; const note = ` - <p id=${NOTE} class='gitlab-message'></p> + <div id="${NOTE_CONTAINER}" style="visibility: hidden;"> + <p id="${NOTE}" class="gitlab-message"></p> + </div> `; const clearNote = inputId => { const currentNote = selectNote(); + const noteContainer = selectNoteContainer(); + currentNote.innerText = ''; currentNote.style.color = ''; + noteContainer.style.visibility = 'hidden'; if (inputId) { const field = document.getElementById(inputId); @@ -18,10 +23,13 @@ const clearNote = inputId => { const postError = (message, inputId) => { const currentNote = selectNote(); + const noteContainer = selectNoteContainer(); const field = selectById(inputId); field.style.borderColor = RED; currentNote.style.color = RED; currentNote.innerText = message; + noteContainer.style.visibility = 'visible'; + setTimeout(clearNote.bind(null, inputId), 5000); }; export { clearNote, note, postError }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/utils.js b/app/assets/javascripts/visual_review_toolbar/components/utils.js index 7bc2e5a905b..00f4460925d 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/utils.js +++ b/app/assets/javascripts/visual_review_toolbar/components/utils.js @@ -5,7 +5,9 @@ import { COMMENT_BOX, COMMENT_BUTTON, FORM, + FORM_CONTAINER, NOTE, + NOTE_CONTAINER, REMEMBER_TOKEN, REVIEW_CONTAINER, TOKEN_BOX, @@ -24,7 +26,9 @@ const selectCommentBox = () => document.getElementById(COMMENT_BOX); const selectCommentButton = () => document.getElementById(COMMENT_BUTTON); const selectContainer = () => document.getElementById(REVIEW_CONTAINER); const selectForm = () => document.getElementById(FORM); +const selectFormContainer = () => document.getElementById(FORM_CONTAINER); const selectNote = () => document.getElementById(NOTE); +const selectNoteContainer = () => document.getElementById(NOTE_CONTAINER); const selectRemember = () => document.getElementById(REMEMBER_TOKEN); const selectToken = () => document.getElementById(TOKEN_BOX); @@ -36,7 +40,9 @@ export { selectCommentBox, selectCommentButton, selectForm, + selectFormContainer, selectNote, + selectNoteContainer, selectRemember, selectToken, }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js index 233b7ec496c..f2eaf1d7916 100644 --- a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js +++ b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js @@ -1,15 +1,28 @@ import { comment } from './comment'; -import { CLEAR, FORM, WHITE } from './constants'; +import { CLEAR, FORM, FORM_CONTAINER, WHITE } from './constants'; import { login } from './login'; -import { selectCollapseButton, selectContainer, selectForm } from './utils'; +import { clearNote } from './note'; +import { + selectCollapseButton, + selectForm, + selectFormContainer, + selectNoteContainer, +} from './utils'; import { commentIcon, compressIcon } from './wrapper_icons'; const form = content => ` - <form id=${FORM}> + <form id="${FORM}"> ${content} </form> `; +const buttonAndForm = ({ content, toggleButton }) => ` + <div id="${FORM_CONTAINER}" class="gitlab-form-open"> + ${toggleButton} + ${form(content)} + </div> +`; + const addCommentForm = () => { const formWrapper = selectForm(); formWrapper.innerHTML = comment; @@ -31,13 +44,15 @@ function logoutUser() { return; } + clearNote(); addLoginForm(); } function toggleForm() { - const container = selectContainer(); const collapseButton = selectCollapseButton(); const currentForm = selectForm(); + const formContainer = selectFormContainer(); + const noteContainer = selectNoteContainer(); const OPEN = 'open'; const CLOSED = 'closed'; @@ -49,7 +64,7 @@ function toggleForm() { const openButtonClasses = ['gitlab-collapse-closed', 'gitlab-collapse-open']; const closedButtonClasses = [...openButtonClasses].reverse(); - const openContainerClasses = ['gitlab-closed-wrapper', 'gitlab-open-wrapper']; + const openContainerClasses = ['gitlab-wrapper-closed', 'gitlab-wrapper-open']; const closedContainerClasses = [...openContainerClasses].reverse(); const stateVals = { @@ -72,11 +87,16 @@ function toggleForm() { const nextState = collapseButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN; const currentVals = stateVals[nextState]; - container.classList.replace(...currentVals.containerClasses); - container.style.backgroundColor = currentVals.backgroundColor; + formContainer.classList.replace(...currentVals.containerClasses); + formContainer.style.backgroundColor = currentVals.backgroundColor; + formContainer.classList.toggle('gitlab-form-open'); currentForm.style.display = currentVals.display; collapseButton.classList.replace(...currentVals.buttonClasses); collapseButton.innerHTML = currentVals.icon; + + if (noteContainer && noteContainer.innerText.length > 0) { + noteContainer.style.display = currentVals.display; + } } -export { addCommentForm, addLoginForm, form, logoutUser, toggleForm }; +export { addCommentForm, addLoginForm, buttonAndForm, logoutUser, toggleForm }; diff --git a/app/assets/javascripts/visual_review_toolbar/index.js b/app/assets/javascripts/visual_review_toolbar/index.js index 941d77e25b4..f94eb88835a 100644 --- a/app/assets/javascripts/visual_review_toolbar/index.js +++ b/app/assets/javascripts/visual_review_toolbar/index.js @@ -1,6 +1,6 @@ import './styles/toolbar.css'; -import { form, selectContainer, REVIEW_CONTAINER } from './components'; +import { buttonAndForm, note, selectContainer, REVIEW_CONTAINER } from './components'; import { debounce, eventLookup, getInitialView, initializeState, updateWindowSize } from './store'; /* @@ -20,12 +20,11 @@ import { debounce, eventLookup, getInitialView, initializeState, updateWindowSiz window.addEventListener('load', () => { initializeState(window, document); - const { content, toggleButton } = getInitialView(window); + const mainContent = buttonAndForm(getInitialView(window)); const container = document.createElement('div'); - container.setAttribute('id', REVIEW_CONTAINER); - container.insertAdjacentHTML('beforeend', toggleButton); - container.insertAdjacentHTML('beforeend', form(content)); + container.insertAdjacentHTML('beforeend', note); + container.insertAdjacentHTML('beforeend', mainContent); document.body.insertBefore(container, document.body.firstChild); diff --git a/app/assets/javascripts/visual_review_toolbar/store/state.js b/app/assets/javascripts/visual_review_toolbar/store/state.js index f5ede6e85b2..22702d524b8 100644 --- a/app/assets/javascripts/visual_review_toolbar/store/state.js +++ b/app/assets/javascripts/visual_review_toolbar/store/state.js @@ -34,7 +34,7 @@ const initializeState = (wind, doc) => { const browser = getBrowserId(userAgent); const scriptEl = doc.getElementById('review-app-toolbar-script'); - const { projectId, mergeRequestId, mrUrl } = scriptEl.dataset; + const { projectId, mergeRequestId, mrUrl, projectPath } = scriptEl.dataset; // This mutates our default state object above. It's weird but it makes the linter happy. Object.assign(state, { @@ -46,6 +46,7 @@ const initializeState = (wind, doc) => { mrUrl, platform, projectId, + projectPath, userAgent, }); }; diff --git a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css index 342b3599a44..00a55c0027a 100644 --- a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css +++ b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css @@ -6,23 +6,42 @@ pointer-events: none; } -#gitlab-form-wrapper { +#gitlab-comment { + background-color: #fafafa; +} + +#gitlab-form { + display: flex; + flex-direction: column; + width: 100%; + margin-bottom: 0; +} + +#gitlab-note-wrapper { display: flex; flex-direction: column; - width: 100% + background-color: #fafafa; + border-radius: 4px; + margin-bottom: .5rem; + padding: 1rem; +} + +#gitlab-form-wrapper { + overflow: auto; + display: flex; + flex-direction: row-reverse; + border-radius: 4px; } #gitlab-review-container { max-width: 22rem; max-height: 22rem; - overflow: scroll; + overflow: auto; + display: flex; + flex-direction: column; position: fixed; bottom: 1rem; right: 1rem; - display: flex; - flex-direction: row-reverse; - padding: 1rem; - background-color: #fff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; @@ -31,12 +50,12 @@ color: #2e2e2e; } -.gitlab-open-wrapper { +.gitlab-wrapper-open { max-width: 22rem; max-height: 22rem; } -.gitlab-closed-wrapper { +.gitlab-wrapper-closed { max-width: 3.4rem; max-height: 3.4rem; } @@ -47,7 +66,7 @@ } .gitlab-button-secondary { - background: none #fff; + background: none #fafafa; margin: 0 .5rem; border: 1px solid #e3e3e3; } @@ -113,6 +132,11 @@ align-items: baseline; } +.gitlab-form-open { + padding: 1rem; + background-color: #fafafa; +} + .gitlab-label { font-weight: 600; display: inline-block; @@ -126,6 +150,10 @@ background-image: none; } +.gitlab-link:hover { + text-decoration: underline; +} + .gitlab-message { padding: .25rem 0; margin: 0; diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue index bf59a6abf3f..edbeab9c600 100644 --- a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue +++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue @@ -26,6 +26,11 @@ export default { required: false, default: '', }, + cssClasses: { + type: String, + required: false, + default: '', + }, modalId: { type: String, required: false, @@ -110,6 +115,7 @@ export default { <template> <gl-button v-gl-tooltip="{ placement: tooltipPlacement, container: tooltipContainer }" + :class="cssClasses" :data-clipboard-target="target" :data-clipboard-text="text" :title="title" diff --git a/app/assets/javascripts/vue_shared/components/pagination/constants.js b/app/assets/javascripts/vue_shared/components/pagination/constants.js index 748ad178c70..229132c0e33 100644 --- a/app/assets/javascripts/vue_shared/components/pagination/constants.js +++ b/app/assets/javascripts/vue_shared/components/pagination/constants.js @@ -3,8 +3,8 @@ import { s__ } from '~/locale'; export const PAGINATION_UI_BUTTON_LIMIT = 4; export const UI_LIMIT = 6; export const SPREAD = '...'; -export const PREV = s__('Pagination|Prev'); -export const NEXT = s__('Pagination|Next'); +export const PREV = s__('Pagination|‹ Prev'); +export const NEXT = s__('Pagination|Next ›'); export const FIRST = s__('Pagination|« First'); export const LAST = s__('Pagination|Last »'); export const LABEL_FIRST_PAGE = s__('Pagination|Go to first page'); diff --git a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue index e77b9ddc7ba..b9311d65360 100644 --- a/app/assets/javascripts/vue_shared/components/project_avatar/image.vue +++ b/app/assets/javascripts/vue_shared/components/project_avatar/image.vue @@ -17,13 +17,9 @@ import defaultAvatarUrl from 'images/no_avatar.png'; import { placeholderImage } from '../../../lazy_loader'; -import tooltip from '../../directives/tooltip'; export default { name: 'ProjectAvatarImage', - directives: { - tooltip, - }, props: { lazy: { type: Boolean, @@ -50,16 +46,6 @@ export default { required: false, default: 20, }, - tooltipText: { - type: String, - required: false, - default: '', - }, - tooltipPlacement: { - type: String, - required: false, - default: 'top', - }, }, computed: { // API response sends null when gravatar is disabled and @@ -71,9 +57,6 @@ export default { resultantSrcAttribute() { return this.lazy ? placeholderImage : this.sanitizedSource; }, - tooltipContainer() { - return this.tooltipText ? 'body' : null; - }, avatarSizeClass() { return `s${this.size}`; }, @@ -83,7 +66,6 @@ export default { <template> <img - v-tooltip :class="{ lazy: lazy, [avatarSizeClass]: true, @@ -94,9 +76,6 @@ export default { :height="size" :alt="imgAlt" :data-src="sanitizedSource" - :data-container="tooltipContainer" - :data-placement="tooltipPlacement" - :title="tooltipText" class="avatar" /> </template> diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss index 58aaca93160..8c40c4adb5c 100644 --- a/app/assets/stylesheets/components/popover.scss +++ b/app/assets/stylesheets/components/popover.scss @@ -135,11 +135,5 @@ .popover { min-width: auto; max-width: 40%; - - .popover-body { - padding-top: $gl-padding; - padding-bottom: $gl-padding; - font-size: $gl-font-size-small; - } } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 555a3fe0dc7..954551fef97 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -133,7 +133,6 @@ ul.content-list { .description { @include str-truncated; - color: $gl-text-color-secondary; } .controls { diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index ad5096761cd..bf0f1da6aa3 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -65,7 +65,7 @@ .stats { float: right; line-height: $list-text-height; - color: $gl-text-color; + color: $gl-text-color-secondary; span { margin-right: 15px; diff --git a/app/assets/stylesheets/pages/branches.scss b/app/assets/stylesheets/pages/branches.scss index ce0622b3d48..e1715b8e1bf 100644 --- a/app/assets/stylesheets/pages/branches.scss +++ b/app/assets/stylesheets/pages/branches.scss @@ -14,62 +14,26 @@ $graph-side-width: 80px; $graph-separator-width: 1px; - padding: 0 6px; - .graph-side { - position: relative; width: $graph-side-width; - height: 22px; - padding: 5px 0 13px; - float: left; &.full { width: $graph-side-width * 2 + $graph-separator-width; - display: flex; - justify-content: center; } .bar { - position: absolute; height: 4px; background-color: $gl-gray-200; } - .bar-behind { - right: 0; - border-radius: 3px 0 0 3px; - } - - .bar-ahead { - left: 0; - border-radius: 0 3px 3px 0; - } - .count { - padding-top: 6px; - padding-bottom: 0; font-size: 12px; - color: $gl-text-color; - display: block; - } - - .count-behind { - padding-right: 4px; - text-align: right; - } - - .count-ahead { - padding-left: 4px; - text-align: left; } } .graph-separator { - position: relative; width: $graph-separator-width; height: 18px; - margin: 5px 0 0; - float: left; background-color: $gl-gray-200; } } diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 656202f4e58..cff2e274390 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -168,12 +168,6 @@ } } -.groups-listing { - .group-list-tree .group-row:first-child { - border-top: 0; - } -} - .card { .shared_runners_limit_under_quota { color: $green-500; @@ -260,7 +254,6 @@ table.pipeline-project-metrics tr td { color: $gl-text-color-secondary; font-size: 12px; line-height: 20px; - margin: -5px 3px; padding: 0 $label-padding; border: 1px solid $border-color; border-radius: $label-border-radius; @@ -294,39 +287,6 @@ table.pipeline-project-metrics tr td { } .group-list-tree { - .avatar-container.content-loading { - position: relative; - - > a, - > a .avatar { - height: 100%; - border-radius: 50%; - } - - > a { - padding: 2px; - - .avatar { - border: 2px solid $white-normal; - - &.identicon { - line-height: 15px; - } - } - } - - &::after { - content: ''; - position: absolute; - height: 100%; - width: 100%; - background-color: transparent; - border: 2px outset $gl-gray-200; - border-radius: 50%; - animation: spin-avatar 3s infinite linear; - } - } - .folder-toggle-wrap { font-size: 0; flex-shrink: 0; @@ -339,13 +299,14 @@ table.pipeline-project-metrics tr td { .folder-caret, .item-type-icon { display: inline-block; + color: $gl-text-color-secondary; } .folder-caret { - width: 15px; + width: $gl-font-size-large; svg { - margin-bottom: 1px; + margin-bottom: 2px; } } @@ -420,7 +381,7 @@ table.pipeline-project-metrics tr td { } .group-row-contents { - padding: $gl-padding-top; + padding: $gl-padding; &:hover { border-color: $blue-200; @@ -428,10 +389,15 @@ table.pipeline-project-metrics tr td { cursor: pointer; } + .group-text-container, .group-text { min-width: 0; // allows for truncated text within flex children } + .group-text { + flex-basis: 100%; + } + .avatar-container { flex-shrink: 0; @@ -441,6 +407,21 @@ table.pipeline-project-metrics tr td { } } + .title { + margin-top: -$gl-padding-8; // negative margin required for flex-wrap + font-size: $gl-font-size-large; + } + + .item-visibility { + color: $gl-text-color-secondary; + } + + @include media-breakpoint-down(md) { + .title { + font-size: $gl-font-size; + } + } + &.has-more-items { display: block; padding: 20px 10px; @@ -477,17 +458,18 @@ table.pipeline-project-metrics tr td { } .controls { - flex-shrink: 0; + flex-basis: 90px; > .btn { - margin: 0 0 0 $btn-margin-5; + margin: 0 $btn-side-margin 0 0; + color: $gl-text-color-secondary; } } - } - @include media-breakpoint-down(xs) { - .group-stats { - display: none; + .metadata { + @include media-breakpoint-up(md) { + flex-basis: 240px; + } } } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index dcbb23684d1..6a0127eb51c 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -594,18 +594,18 @@ padding: 16px 0; small { - color: $gray-darkest; + color: $gray-700; } } .edited-text { - color: $gray-darkest; + color: $gray-700; display: block; margin: 16px 0 0; font-size: 85%; .author-link { - color: $gray-darkest; + color: $gray-700; } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 48289c8f381..8359a60ec9f 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -1,4 +1,18 @@ .issues-list { + &.manual-ordering { + background-color: $gray-light; + border-radius: $border-radius-default; + padding: $gl-padding-8; + + .issue { + background-color: $white-light; + margin-bottom: $gl-padding-8; + border-radius: $border-radius-default; + border: 1px solid $gray-100; + box-shadow: 0 1px 2px $issue-boards-card-shadow; + } + } + .issue { padding: 10px 0 10px $gl-padding; position: relative; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 5cacd42bf0d..824edb2869f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -628,7 +628,7 @@ $note-form-margin-left: 72px; .note-headline-meta { .system-note-separator { - color: $gl-text-color-disabled; + color: $gray-700; } .note-timestamp { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 151af843c95..c80beceae52 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -889,7 +889,6 @@ pre.light-well { @include basic-list-stats; display: flex; align-items: center; - color: $gl-text-color-secondary; padding: $gl-padding 0; @include media-breakpoint-up(lg) { @@ -952,10 +951,6 @@ pre.light-well { .description { line-height: 1.5; - - @include media-breakpoint-up(md) { - color: $gl-text-color; - } } @include media-breakpoint-down(md) { diff --git a/app/controllers/concerns/continue_params.rb b/app/controllers/concerns/continue_params.rb index 54c0510497f..d5830f6648c 100644 --- a/app/controllers/concerns/continue_params.rb +++ b/app/controllers/concerns/continue_params.rb @@ -6,7 +6,7 @@ module ContinueParams def continue_params continue_params = params[:continue] - return unless continue_params + return {} unless continue_params continue_params = continue_params.permit(:to, :notice, :notice_now) continue_params[:to] = safe_redirect_path(continue_params[:to]) diff --git a/app/controllers/concerns/internal_redirect.rb b/app/controllers/concerns/internal_redirect.rb index 6785e6972d0..fa3716502a0 100644 --- a/app/controllers/concerns/internal_redirect.rb +++ b/app/controllers/concerns/internal_redirect.rb @@ -5,8 +5,8 @@ module InternalRedirect def safe_redirect_path(path) return unless path - # Verify that the string starts with a `/` but not a double `/`. - return unless path =~ %r{^/\w.*$} + # Verify that the string starts with a `/` and a known route character. + return unless path =~ %r{^/[-\w].*$} uri = URI(path) # Ignore anything path of the redirect except for the path, querystring and, diff --git a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb index 426f224d26b..f47ead2f0da 100644 --- a/app/controllers/concerns/requires_whitelisted_monitoring_client.rb +++ b/app/controllers/concerns/requires_whitelisted_monitoring_client.rb @@ -14,6 +14,10 @@ module RequiresWhitelistedMonitoringClient end def client_ip_whitelisted? + # Always allow developers to access http://localhost:3000/-/metrics for + # debugging purposes + return true if Rails.env.development? && request.local? + ip_whitelist.any? { |e| e.include?(Gitlab::RequestContext.client_ip) } end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index f173c263474..27980466a42 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -10,6 +10,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController def index @sort = params[:sort] @todos = @todos.page(params[:page]) + @todos = @todos.with_entity_associations return if redirect_out_of_range(@todos) end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index e936d771502..316da8f129d 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -7,6 +7,10 @@ class GroupsController < Groups::ApplicationController include PreviewMarkdown include RecordUserLastActivity + before_action do + push_frontend_feature_flag(:manual_sorting) + end + respond_to :html prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) } diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb index 7a1700a206a..ac1c4bc7fd3 100644 --- a/app/controllers/projects/forks_controller.rb +++ b/app/controllers/projects/forks_controller.rb @@ -46,18 +46,14 @@ class Projects::ForksController < Projects::ApplicationController @forked_project ||= ::Projects::ForkService.new(project, current_user, namespace: namespace).execute - if @forked_project.saved? && @forked_project.forked? - if @forked_project.import_in_progress? - redirect_to project_import_path(@forked_project, continue: continue_params) - else - if continue_params - redirect_to continue_params[:to], notice: continue_params[:notice] - else - redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked." - end - end - else + if !@forked_project.saved? || !@forked_project.forked? render :error + elsif @forked_project.import_in_progress? + redirect_to project_import_path(@forked_project, continue: continue_params) + elsif continue_params[:to] + redirect_to continue_params[:to], notice: continue_params[:notice] + else + redirect_to project_path(@forked_project), notice: "The project '#{@forked_project.name}' was successfully forked." end end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index afbf9fd7720..da32ab9e2e0 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -23,7 +23,7 @@ class Projects::ImportsController < Projects::ApplicationController def show if @project.import_finished? - if continue_params&.key?(:to) + if continue_params[:to] redirect_to continue_params[:to], notice: continue_params[:notice] else redirect_to project_path(@project), notice: finished_notice @@ -31,11 +31,7 @@ class Projects::ImportsController < Projects::ApplicationController elsif @project.import_failed? redirect_to new_project_import_path(@project) else - if continue_params && continue_params[:notice_now] - flash.now[:notice] = continue_params[:notice_now] - end - - # Render + flash.now[:notice] = continue_params[:notice_now] end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index b16f3dd9d82..f221f0363d3 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -10,6 +10,10 @@ class Projects::IssuesController < Projects::ApplicationController include SpammableActions include RecordUserLastActivity + before_action do + push_frontend_feature_flag(:manual_sorting) + end + def issue_except_actions %i[index calendar new create bulk_update import_csv] end diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index d7c0039b234..02ff6e872c9 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -103,7 +103,7 @@ class Projects::JobsController < Projects::ApplicationController @build.cancel - if continue_params + if continue_params[:to] redirect_to continue_params[:to] else redirect_to builds_project_pipeline_path(@project, @build.pipeline.id) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9e7e3ed5afb..fc37ce1dbc4 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -16,7 +16,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] - around_action :allow_gitaly_ref_name_caching, only: [:index, :show] + around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions] def index @merge_requests = @issuables @@ -33,7 +33,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo def show close_merge_request_if_no_source_project - mark_merge_request_mergeable + @merge_request.check_mergeability respond_to do |format| format.html do @@ -251,10 +251,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @merge_request.has_no_commits? && !@merge_request.target_branch_exists? end - def mark_merge_request_mergeable - @merge_request.check_if_can_be_merged - end - def merge! # Disable the CI check if auto_merge_strategy is specified since we have # to wait until CI completes to know diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index b3447812ef2..b4ca9074ca9 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -55,6 +55,7 @@ class Projects::RefsController < Projects::ApplicationController format.html { render_404 } format.json do response.headers["More-Logs-Url"] = @more_log_url if summary.more? + response.headers["More-Logs-Offset"] = summary.next_offset if summary.more? render json: @logs end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 07b38371ab9..b2b151bbcf0 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -3,6 +3,7 @@ class RegistrationsController < Devise::RegistrationsController include Recaptcha::Verify include AcceptsPendingInvitations + include RecaptchaExperimentHelper prepend_before_action :check_captcha, only: :create before_action :whitelist_query_limiting, only: [:destroy] @@ -15,13 +16,6 @@ class RegistrationsController < Devise::RegistrationsController end def create - # To avoid duplicate form fields on the login page, the registration form - # names fields using `new_user`, but Devise still wants the params in - # `user`. - if params["new_#{resource_name}"].present? && params[resource_name].blank? - params[resource_name] = params.delete(:"new_#{resource_name}") - end - accept_pending_invitations super do |new_user| @@ -74,19 +68,35 @@ class RegistrationsController < Devise::RegistrationsController end def after_sign_up_path_for(user) - Gitlab::AppLogger.info("User Created: username=#{user.username} email=#{user.email} ip=#{request.remote_ip} confirmed:#{user.confirmed?}") + Gitlab::AppLogger.info(user_created_message(confirmed: user.confirmed?)) user.confirmed? ? stored_location_for(user) || dashboard_projects_path : users_almost_there_path end def after_inactive_sign_up_path_for(resource) - Gitlab::AppLogger.info("User Created: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip} confirmed:false") + Gitlab::AppLogger.info(user_created_message) users_almost_there_path end private + def user_created_message(confirmed: false) + "User Created: username=#{resource.username} email=#{resource.email} ip=#{request.remote_ip} confirmed:#{confirmed}" + end + + def ensure_correct_params! + # To avoid duplicate form fields on the login page, the registration form + # names fields using `new_user`, but Devise still wants the params in + # `user`. + if params["new_#{resource_name}"].present? && params[resource_name].blank? + params[resource_name] = params.delete(:"new_#{resource_name}") + end + end + def check_captcha - return unless Feature.enabled?(:registrations_recaptcha, default_enabled: true) + ensure_correct_params! + + return unless Feature.enabled?(:registrations_recaptcha, default_enabled: true) # reCAPTCHA on the UI will still display however + return unless show_recaptcha_sign_up? return unless Gitlab::Recaptcha.load_configurations! return if verify_recaptcha diff --git a/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb b/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb index f38c187799c..78a17312e26 100644 --- a/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb +++ b/app/finders/autocomplete/acts_as_taggable_on/tags_finder.rb @@ -22,8 +22,7 @@ module Autocomplete end def filter_by_name(tags) - return tags unless search - return tags.none if search.empty? + return tags unless search.present? if search.length >= Gitlab::SQL::Pattern::MIN_CHARS_FOR_PARTIAL_MATCHING tags.named_like(search) diff --git a/app/finders/environments_finder.rb b/app/finders/environments_finder.rb index 419be46fafe..29c00e4b2c2 100644 --- a/app/finders/environments_finder.rb +++ b/app/finders/environments_finder.rb @@ -47,6 +47,19 @@ class EnvironmentsFinder end # rubocop: enable CodeReuse/ActiveRecord + # This method will eventually take the place of `#execute` as an + # efficient way to get relevant environment entries. + # Currently, `#execute` method has a serious technical debt and + # we will likely rework on it in the future. + # See more https://gitlab.com/gitlab-org/gitlab-ce/issues/63381 + def find + environments = project.environments + environments = by_name(environments) + environments = by_search(environments) + + environments + end + private def ref @@ -56,4 +69,20 @@ class EnvironmentsFinder def commit params[:commit] end + + def by_name(environments) + if params[:name].present? + environments.for_name(params[:name]) + else + environments + end + end + + def by_search(environments) + if params[:search].present? + environments.for_name_like(params[:search], limit: nil) + else + environments + end + end end diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index c0db9910143..6b43d52c775 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -2,6 +2,7 @@ module AppearancesHelper include MarkupHelper + include Gitlab::Utils::StrongMemoize def brand_title current_appearance&.title.presence || default_brand_title @@ -25,7 +26,9 @@ module AppearancesHelper end def current_appearance - @appearance ||= Appearance.current + strong_memoize(:current_appearance) do + Appearance.current + end end def brand_header_logo diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 4469118f065..aaaa954047f 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -165,8 +165,6 @@ module ApplicationSettingsHelper :authorized_keys_enabled, :auto_devops_enabled, :auto_devops_domain, - :clientside_sentry_dsn, - :clientside_sentry_enabled, :container_registry_token_expire_delay, :default_artifacts_expire_in, :default_branch_protection, @@ -235,8 +233,6 @@ module ApplicationSettingsHelper :restricted_visibility_levels, :rsa_key_restriction, :send_user_confirmation_email, - :sentry_dsn, - :sentry_enabled, :session_expire_delay, :shared_runners_enabled, :shared_runners_text, @@ -253,6 +249,7 @@ module ApplicationSettingsHelper :throttle_unauthenticated_enabled, :throttle_unauthenticated_period_in_seconds, :throttle_unauthenticated_requests_per_period, + :time_tracking_limit_to_hours, :two_factor_grace_period, :unique_ips_limit_enabled, :unique_ips_limit_per_user, diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index 1640f4fc93f..c5130b430b9 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -14,7 +14,8 @@ module BoardsHelper issue_link_base: build_issue_link_base, root_path: root_path, bulk_update_path: @bulk_issues_path, - default_avatar: image_path(default_avatar) + default_avatar: image_path(default_avatar), + time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s } end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 150f24a5d5b..045de105b77 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -430,7 +430,8 @@ module IssuablesHelper editable: issuable.dig(:current_user, :can_edit), currentUser: issuable[:current_user], rootPath: root_path, - fullPath: issuable[:project_full_path] + fullPath: issuable[:project_full_path], + timeTrackingLimitToHours: Gitlab::CurrentSettings.time_tracking_limit_to_hours } end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 957ab06b0ca..dfadcfc33b2 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -5,6 +5,7 @@ module IssuesHelper classes = ["issue"] classes << "closed" if issue.closed? classes << "today" if issue.today? + classes << "user-can-drag" if @sort == 'relative_position' classes.join(' ') end @@ -135,6 +136,20 @@ module IssuesHelper can?(current_user, :create_issue, project) end + def create_confidential_merge_request_enabled? + Feature.enabled?(:create_confidential_merge_request, @project) + end + + def show_new_branch_button? + can_create_confidential_merge_request? || !@issue.confidential? + end + + def can_create_confidential_merge_request? + @issue.confidential? && !@project.private? && + create_confidential_merge_request_enabled? && + can?(current_user, :create_merge_request_in, @project) + end + # Required for Banzai::Filter::IssueReferenceFilter module_function :url_for_issue module_function :url_for_internal_issue diff --git a/app/helpers/recaptcha_experiment_helper.rb b/app/helpers/recaptcha_experiment_helper.rb new file mode 100644 index 00000000000..d2eb9ac54f6 --- /dev/null +++ b/app/helpers/recaptcha_experiment_helper.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module RecaptchaExperimentHelper + def show_recaptcha_sign_up? + !!Gitlab::Recaptcha.enabled? + end +end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index dfa34ad7020..f5c4686a3bf 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -169,18 +169,17 @@ module SearchHelper autocomplete: 'off' } + opts[:data]['runner-tags-endpoint'] = tag_list_admin_runners_path + if @project.present? opts[:data]['project-id'] = @project.id - opts[:data]['base-endpoint'] = project_path(@project) opts[:data]['labels-endpoint'] = project_labels_path(@project) opts[:data]['milestones-endpoint'] = project_milestones_path(@project) elsif @group.present? opts[:data]['group-id'] = @group.id - opts[:data]['base-endpoint'] = group_canonical_path(@group) opts[:data]['labels-endpoint'] = group_labels_path(@group) opts[:data]['milestones-endpoint'] = group_milestones_path(@group) else - opts[:data]['base-endpoint'] = root_dashboard_path opts[:data]['labels-endpoint'] = dashboard_labels_path opts[:data]['milestones-endpoint'] = dashboard_milestones_path end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 6bd78336ed3..645160077f5 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -170,7 +170,7 @@ module TodosHelper end def todo_group_options - groups = current_user.authorized_groups.map do |group| + groups = current_user.authorized_groups.with_route.map do |group| { id: group.id, text: group.full_name } end diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 70d296fe3b8..506c8d251b7 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -60,7 +60,7 @@ module Emails # `note_id` is a `Note` when originating in `NotifyPreview` @note = note_id.is_a?(Note) ? note_id : Note.find(note_id) @project = @note.project - @group = @note.noteable.try(:group) + @group = @project.try(:group) || @note.noteable.try(:group) if (@project || @group) && @note.persisted? @sent_notification = SentNotification.record_note(@note, recipient_id, reply_key) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index bbe2d2e8fd4..cd645850af3 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -30,6 +30,10 @@ class ApplicationSetting < ApplicationRecord ignore_column :circuitbreaker_check_interval ignore_column :koding_url ignore_column :koding_enabled + ignore_column :sentry_enabled + ignore_column :sentry_dsn + ignore_column :clientside_sentry_enabled + ignore_column :clientside_sentry_dsn cache_markdown_field :sign_in_text cache_markdown_field :help_page_text @@ -75,14 +79,6 @@ class ApplicationSetting < ApplicationRecord presence: true, if: :recaptcha_enabled - validates :sentry_dsn, - presence: true, - if: :sentry_enabled - - validates :clientside_sentry_dsn, - presence: true, - if: :clientside_sentry_enabled - validates :akismet_api_key, presence: true, if: :akismet_enabled @@ -264,7 +260,6 @@ class ApplicationSetting < ApplicationRecord encode: true before_validation :ensure_uuid! - before_validation :strip_sentry_values before_save :ensure_runners_registration_token before_save :ensure_health_check_access_token diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb index 904d650ef96..df4caed175d 100644 --- a/app/models/application_setting_implementation.rb +++ b/app/models/application_setting_implementation.rb @@ -82,6 +82,7 @@ module ApplicationSettingImplementation throttle_unauthenticated_enabled: false, throttle_unauthenticated_period_in_seconds: 3600, throttle_unauthenticated_requests_per_period: 3600, + time_tracking_limit_to_hours: false, two_factor_grace_period: 48, unique_ips_limit_enabled: false, unique_ips_limit_per_user: 10, @@ -179,27 +180,6 @@ module ApplicationSettingImplementation super(levels&.map { |level| Gitlab::VisibilityLevel.level_value(level) }) end - def strip_sentry_values - sentry_dsn.strip! if sentry_dsn.present? - clientside_sentry_dsn.strip! if clientside_sentry_dsn.present? - end - - def sentry_enabled - Gitlab.config.sentry.enabled || read_attribute(:sentry_enabled) - end - - def sentry_dsn - Gitlab.config.sentry.dsn || read_attribute(:sentry_dsn) - end - - def clientside_sentry_enabled - Gitlab.config.sentry.enabled || read_attribute(:clientside_sentry_enabled) - end - - def clientside_sentry_dsn - Gitlab.config.sentry.clientside_dsn || read_attribute(:clientside_sentry_dsn) - end - def performance_bar_allowed_group Group.find_by_id(performance_bar_allowed_group_id) end diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 0fd8dca70b4..da4584228ce 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -45,7 +45,7 @@ class BroadcastMessage < ApplicationRecord end def self.cache_expires_in - nil + 2.weeks end def active? diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index 6a4241c94bc..ba8cea0cea9 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -55,15 +55,20 @@ module Ci # This way, a schedule like `*/1 * * * *` won't be triggered in a short interval # when PipelineScheduleWorker runs irregularly by Sidekiq Memory Killer. def set_next_run_at - self.next_run_at = Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], - Time.zone.name) - .next_time_from(ideal_next_run_at) + now = Time.zone.now + ideal_next_run = ideal_next_run_from(now) + + self.next_run_at = if ideal_next_run == cron_worker_next_run_from(now) + ideal_next_run + else + cron_worker_next_run_from(ideal_next_run) + end end def schedule_next_run! save! # with set_next_run_at rescue ActiveRecord::RecordInvalid - update_attribute(:next_run_at, nil) # update without validation + update_column(:next_run_at, nil) # update without validation end def job_variables @@ -72,9 +77,15 @@ module Ci private - def ideal_next_run_at + def ideal_next_run_from(start_time) Gitlab::Ci::CronParser.new(cron, cron_timezone) - .next_time_from(Time.zone.now) + .next_time_from(start_time) + end + + def cron_worker_next_run_from(start_time) + Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], + Time.zone.name) + .next_time_from(start_time) end end end diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index db7fd8524c2..f0256ff4d41 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ApplicationRecord - VERSION = '0.5.2'.freeze + VERSION = '0.6.0'.freeze self.table_name = 'clusters_applications_runners' diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 2afe471d1cc..9296c28776b 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -4,7 +4,6 @@ module Clusters module Platforms class Kubernetes < ApplicationRecord include Gitlab::Kubernetes - include ReactiveCaching include EnumWithNil include AfterCommitQueue @@ -46,9 +45,6 @@ module Clusters validate :prevent_modification, on: :update - after_save :clear_reactive_cache! - after_update :update_kubernetes_namespace - alias_attribute :ca_pem, :ca_cert delegate :enabled?, to: :cluster, allow_nil: true @@ -97,27 +93,16 @@ module Clusters end end - # Constructs a list of terminals from the reactive cache - # - # Returns nil if the cache is empty, in which case you should try again a - # short time later - def terminals(environment) - with_reactive_cache do |data| - project = environment.project - - pods = filter_by_project_environment(data[:pods], project.full_path_slug, environment.slug) - terminals = pods.flat_map { |pod| terminals_for_pod(api_url, cluster.kubernetes_namespace_for(project), pod) }.compact - terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) } - end - end - - # Caches resources in the namespace so other calls don't need to block on - # network access - def calculate_reactive_cache + def calculate_reactive_cache_for(environment) return unless enabled? - # We may want to cache extra things in the future - { pods: read_pods } + { pods: read_pods(environment.deployment_namespace) } + end + + def terminals(environment, data) + pods = filter_by_project_environment(data[:pods], environment.project.full_path_slug, environment.slug) + terminals = pods.flat_map { |pod| terminals_for_pod(api_url, environment.deployment_namespace, pod) }.compact + terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) } end def kubeclient @@ -134,6 +119,12 @@ module Clusters ca_pem: ca_pem) end + def read_pods(namespace) + kubeclient.get_pods(namespace: namespace).as_json + rescue Kubeclient::ResourceNotFoundError + [] + end + def build_kube_client! raise "Incomplete settings" unless api_url @@ -149,19 +140,6 @@ module Clusters ) end - # Returns a hash of all pods in the namespace - def read_pods - # TODO: The project lookup here should be moved (to environment?), - # which will enable reading pods from the correct namespace for group - # and instance clusters. - # This will be done in https://gitlab.com/gitlab-org/gitlab-ce/issues/61156 - return [] unless cluster.project_type? - - kubeclient.get_pods(namespace: cluster.kubernetes_namespace_for(cluster.first_project)).as_json - rescue Kubeclient::ResourceNotFoundError - [] - end - def kubeclient_ssl_options opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER } @@ -210,14 +188,6 @@ module Clusters true end - - def update_kubernetes_namespace - return unless saved_change_to_namespace? - - run_after_commit do - ClusterConfigureWorker.perform_async(cluster_id) - end - end end end end diff --git a/app/models/concerns/service_push_data_validations.rb b/app/models/concerns/service_push_data_validations.rb new file mode 100644 index 00000000000..defc5794142 --- /dev/null +++ b/app/models/concerns/service_push_data_validations.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +# This concern is used by registerd services such as TeamCityService and +# DroneCiService and add methods to perform validations on the received +# data. + +module ServicePushDataValidations + extend ActiveSupport::Concern + + def merge_request_valid?(data) + data.dig(:object_attributes, :state) == 'opened' && merge_request_unchecked?(data) + end + + def push_valid?(data) + data[:total_commits_count] > 0 && + !branch_removed?(data) && + # prefer merge request trigger over push to avoid double builds + !opened_merge_requests?(data) + end + + def tag_push_valid?(data) + data[:total_commits_count] > 0 && !branch_removed?(data) + end + + private + + def branch_removed?(data) + Gitlab::Git.blank_ref?(data[:after]) + end + + def opened_merge_requests?(data) + project.merge_requests + .opened + .from_project(project) + .from_source_branches(Gitlab::Git.ref_name(data[:ref])) + .exists? + end + + def merge_request_unchecked?(data) + MergeRequest.state_machines[:merge_status] + .check_state?(data.dig(:object_attributes, :merge_status)) + end +end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 92c7311014a..f0fa5974787 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -84,12 +84,9 @@ class Deployment < ApplicationRecord Commit.truncate_sha(sha) end - def cluster - platform = project.deployment_platform(environment: environment.name) - - if platform.present? && platform.respond_to?(:cluster) - platform.cluster - end + # Deprecated - will be replaced by a persisted cluster_id + def deployment_platform_cluster + environment.deployment_platform&.cluster end def execute_hooks @@ -179,7 +176,7 @@ class Deployment < ApplicationRecord end def has_metrics? - prometheus_adapter&.can_query? && success? + success? && prometheus_adapter&.can_query? end def metrics diff --git a/app/models/environment.rb b/app/models/environment.rb index aff20dae09b..b8ee54c1696 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -2,6 +2,8 @@ class Environment < ApplicationRecord include Gitlab::Utils::StrongMemoize + include ReactiveCaching + # Used to generate random suffixes for the slug LETTERS = ('a'..'z').freeze NUMBERS = ('0'..'9').freeze @@ -17,6 +19,7 @@ class Environment < ApplicationRecord before_validation :generate_slug, if: ->(env) { env.slug.blank? } before_save :set_environment_type + after_save :clear_reactive_cache! validates :name, presence: true, @@ -155,15 +158,29 @@ class Environment < ApplicationRecord end def has_terminals? - deployment_platform.present? && available? && last_deployment.present? + available? && deployment_platform.present? && last_deployment.present? end def terminals - deployment_platform.terminals(self) if has_terminals? + with_reactive_cache do |data| + deployment_platform.terminals(self, data) + end + end + + def calculate_reactive_cache + return unless has_terminals? && !project.pending_delete? + + deployment_platform.calculate_reactive_cache_for(self) + end + + def deployment_namespace + strong_memoize(:kubernetes_namespace) do + deployment_platform&.kubernetes_namespace_for(project) + end end def has_metrics? - prometheus_adapter&.can_query? && available? + available? && prometheus_adapter&.can_query? end def metrics diff --git a/app/models/issue.rb b/app/models/issue.rb index 6da6fbe55cb..30e29911758 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -254,6 +254,10 @@ class Issue < ApplicationRecord merge_requests_closing_issues.count end + def labels_hook_attrs + labels.map(&:hook_attrs) + end + private def ensure_metrics diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index f07636e8f77..82034f5946b 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -725,19 +725,16 @@ class MergeRequest < ApplicationRecord MergeRequests::ReloadDiffsService.new(self, current_user).execute end - # rubocop: enable CodeReuse/ServiceClass - - def check_if_can_be_merged - return unless self.class.state_machines[:merge_status].check_state?(merge_status) && Gitlab::Database.read_write? - can_be_merged = - !broken? && project.repository.can_be_merged?(diff_head_sha, target_branch) + def check_mergeability + MergeRequests::MergeabilityCheckService.new(self).execute + end + # rubocop: enable CodeReuse/ServiceClass - if can_be_merged - mark_as_mergeable - else - mark_as_unmergeable - end + # Returns boolean indicating the merge_status should be rechecked in order to + # switch to either can_be_merged or cannot_be_merged. + def recheck_merge_status? + self.class.state_machines[:merge_status].check_state?(merge_status) end def merge_event @@ -763,7 +760,7 @@ class MergeRequest < ApplicationRecord def mergeable?(skip_ci_check: false) return false unless mergeable_state?(skip_ci_check: skip_ci_check) - check_if_can_be_merged + check_mergeability can_be_merged? && !should_be_rebased? end @@ -778,15 +775,6 @@ class MergeRequest < ApplicationRecord true end - def mergeable_to_ref? - return false unless mergeable_state?(skip_ci_check: true, skip_discussions_check: true) - - # Given the `merge_ref_path` will have the same - # state the `target_branch` would have. Ideally - # we need to check if it can be merged to it. - project.repository.can_be_merged?(diff_head_sha, target_branch) - end - def ff_merge_possible? project.repository.ancestor?(target_branch_sha, diff_head_sha) end @@ -1042,9 +1030,9 @@ class MergeRequest < ApplicationRecord def mergeable_ci_state? return true unless project.only_allow_merge_if_pipeline_succeeds? - return true unless head_pipeline + return false unless actual_head_pipeline - actual_head_pipeline&.success? || actual_head_pipeline&.skipped? + actual_head_pipeline.success? || actual_head_pipeline.skipped? end def environments_for(current_user) @@ -1099,6 +1087,12 @@ class MergeRequest < ApplicationRecord target_project.repository.fetch_source_branch!(source_project.repository, source_branch, ref_path) end + # Returns the current merge-ref HEAD commit. + # + def merge_ref_head + project.repository.commit(merge_ref_path) + end + def ref_path "refs/#{Repository::REF_MERGE_REQUEST}/#{iid}/head" end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 3c270c7396a..f9b53b2b70a 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -35,6 +35,8 @@ class Namespace < ApplicationRecord belongs_to :parent, class_name: "Namespace" has_many :children, class_name: "Namespace", foreign_key: :parent_id has_one :chat_team, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_one :root_storage_statistics, class_name: 'Namespace::RootStorageStatistics' + has_one :aggregation_schedule, class_name: 'Namespace::AggregationSchedule' validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, diff --git a/app/models/namespace/aggregation_schedule.rb b/app/models/namespace/aggregation_schedule.rb new file mode 100644 index 00000000000..43afd0b954c --- /dev/null +++ b/app/models/namespace/aggregation_schedule.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class Namespace::AggregationSchedule < ApplicationRecord + self.primary_key = :namespace_id + + belongs_to :namespace +end diff --git a/app/models/namespace/root_storage_statistics.rb b/app/models/namespace/root_storage_statistics.rb new file mode 100644 index 00000000000..de28eb6b37f --- /dev/null +++ b/app/models/namespace/root_storage_statistics.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class Namespace::RootStorageStatistics < ApplicationRecord + self.primary_key = :namespace_id + + belongs_to :namespace + has_one :route, through: :namespace + + delegate :all_projects, to: :namespace +end diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb index 07195c0bfd3..d6d879c6d89 100644 --- a/app/models/pages_domain.rb +++ b/app/models/pages_domain.rb @@ -3,6 +3,7 @@ class PagesDomain < ApplicationRecord VERIFICATION_KEY = 'gitlab-pages-verification-code'.freeze VERIFICATION_THRESHOLD = 3.days.freeze + SSL_RENEWAL_THRESHOLD = 30.days.freeze enum certificate_source: { user_provided: 0, gitlab_provided: 1 }, _prefix: :certificate @@ -41,6 +42,15 @@ class PagesDomain < ApplicationRecord where(verified_at.eq(nil).or(enabled_until.eq(nil).or(enabled_until.lt(threshold)))) end + scope :need_auto_ssl_renewal, -> do + expiring = where(certificate_valid_not_after: nil).or( + where(arel_table[:certificate_valid_not_after].lt(SSL_RENEWAL_THRESHOLD.from_now))) + + user_provided_or_expiring = certificate_user_provided.or(expiring) + + where(auto_ssl_enabled: true).merge(user_provided_or_expiring) + end + scope :for_removal, -> { where("remove_at < ?", Time.now) } def verified? diff --git a/app/models/project.rb b/app/models/project.rb index 7851f37116c..351d08eaf63 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -55,6 +55,8 @@ class Project < ApplicationRecord VALID_MIRROR_PORTS = [22, 80, 443].freeze VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze + ignore_column :import_status, :import_jid, :import_error + cache_markdown_field :description, pipeline: :description delegate :feature_available?, :builds_enabled?, :wiki_enabled?, diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 5ccc2f019cb..dbdc8345c93 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -2,6 +2,7 @@ class DroneCiService < CiService include ReactiveService + include ServicePushDataValidations prop_accessor :drone_url, :token boolean_accessor :enable_ssl_verification @@ -96,23 +97,4 @@ class DroneCiService < CiService { type: 'checkbox', name: 'enable_ssl_verification', title: "Enable SSL verification" } ] end - - private - - def tag_push_valid?(data) - data[:total_commits_count] > 0 && !Gitlab::Git.blank_ref?(data[:after]) - end - - def push_valid?(data) - opened_merge_requests = project.merge_requests.opened.where(source_project_id: project.id, - source_branch: Gitlab::Git.ref_name(data[:ref])) - - opened_merge_requests.empty? && data[:total_commits_count] > 0 && - !Gitlab::Git.blank_ref?(data[:after]) - end - - def merge_request_valid?(data) - data[:object_attributes][:state] == 'opened' && - MergeRequest.state_machines[:merge_status].check_state?(data[:object_attributes][:merge_status]) - end end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 3245cd22e73..68c07fa37f2 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -2,6 +2,7 @@ class TeamcityService < CiService include ReactiveService + include ServicePushDataValidations prop_accessor :teamcity_url, :build_type, :username, :password @@ -19,6 +20,25 @@ class TeamcityService < CiService after_save :compose_service_hook, if: :activated? before_update :reset_password + class << self + def to_param + 'teamcity' + end + + def supported_events + %w(push merge_request) + end + + def event_description(event) + case event + when 'push', 'push_events' + 'TeamCity CI will be triggered after every push to the repository except branch delete' + when 'merge_request', 'merge_request_events' + 'TeamCity CI will be triggered after a merge request has been created or updated' + end + end + end + def compose_service_hook hook = service_hook || build_service_hook hook.save @@ -43,10 +63,6 @@ class TeamcityService < CiService 'requests build, that setting is in the vsc root advanced settings.' end - def self.to_param - 'teamcity' - end - def fields [ { type: 'text', name: 'teamcity_url', @@ -74,26 +90,25 @@ class TeamcityService < CiService end def execute(data) - return unless supported_events.include?(data[:object_kind]) + case data[:object_kind] + when 'push' + execute_push(data) + when 'merge_request' + execute_merge_request(data) + end + end - auth = { - username: username, - password: password - } + private + def execute_push(data) branch = Gitlab::Git.ref_name(data[:ref]) - - Gitlab::HTTP.post( - build_url('httpAuth/app/rest/buildQueue'), - body: "<build branchName=\"#{branch}\">"\ - "<buildType id=\"#{build_type}\"/>"\ - '</build>', - headers: { 'Content-type' => 'application/xml' }, - basic_auth: auth - ) + post_to_build_queue(data, branch) if push_valid?(data) end - private + def execute_merge_request(data) + branch = data[:object_attributes][:source_branch] + post_to_build_queue(data, branch) if merge_request_valid?(data) + end def read_build_page(response) if response.code != 200 @@ -134,10 +149,21 @@ class TeamcityService < CiService end def get_path(path) - Gitlab::HTTP.get(build_url(path), verify: false, - basic_auth: { - username: username, - password: password - }) + Gitlab::HTTP.get(build_url(path), verify: false, basic_auth: basic_auth) + end + + def post_to_build_queue(data, branch) + Gitlab::HTTP.post( + build_url('httpAuth/app/rest/buildQueue'), + body: "<build branchName=#{branch.encode(xml: :attr)}>"\ + "<buildType id=#{build_type.encode(xml: :attr)}/>"\ + '</build>', + headers: { 'Content-type' => 'application/xml' }, + basic_auth: basic_auth + ) + end + + def basic_auth + { username: username, password: password } end end diff --git a/app/models/todo.rb b/app/models/todo.rb index f1fc5e599eb..240c91da5b6 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -60,7 +60,7 @@ class Todo < ApplicationRecord scope :for_type, -> (type) { where(target_type: type) } scope :for_target, -> (id) { where(target_id: id) } scope :for_commit, -> (id) { where(commit_id: id) } - scope :with_api_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) } + scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) } scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) } state_machine :state, initial: :pending do diff --git a/app/services/clusters/gcp/finalize_creation_service.rb b/app/services/clusters/gcp/finalize_creation_service.rb index 5525c1b9b7f..2f3c1df7651 100644 --- a/app/services/clusters/gcp/finalize_creation_service.rb +++ b/app/services/clusters/gcp/finalize_creation_service.rb @@ -12,9 +12,6 @@ module Clusters create_gitlab_service_account! configure_kubernetes cluster.save! - - ClusterConfigureWorker.perform_async(cluster.id) - rescue Google::Apis::ServerError, Google::Apis::ClientError, Google::Apis::AuthorizationError => e log_service_error(e.class.name, provider.id, e.message) provider.make_errored!(s_('ClusterIntegration|Failed to request to Google Cloud Platform: %{message}') % { message: e.message }) diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb index 87147d90c32..efe4dcd6255 100644 --- a/app/services/merge_requests/merge_to_ref_service.rb +++ b/app/services/merge_requests/merge_to_ref_service.rb @@ -11,6 +11,8 @@ module MergeRequests # be executed regardless of the `target_ref` current state). # class MergeToRefService < MergeRequests::MergeBaseService + extend ::Gitlab::Utils::Override + def execute(merge_request) @merge_request = merge_request @@ -26,14 +28,18 @@ module MergeRequests success(commit_id: commit.id, target_id: target_id, source_id: source_id) - rescue MergeError => error + rescue MergeError, ArgumentError => error error(error.message) end private + override :source + def source + merge_request.diff_head_sha + end + def validate! - authorization_check! error_check! end @@ -43,21 +49,13 @@ module MergeRequests error = if !hooks_validation_pass?(merge_request) hooks_validation_error(merge_request) - elsif !@merge_request.mergeable_to_ref? - "Merge request is not mergeable to #{target_ref}" - elsif !source + elsif source.blank? 'No source for merge' end raise_error(error) if error end - def authorization_check! - unless Ability.allowed?(current_user, :admin_merge_request, project) - raise_error("You are not allowed to merge to this ref") - end - end - def target_ref merge_request.merge_ref_path end diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb new file mode 100644 index 00000000000..9fa50c9448f --- /dev/null +++ b/app/services/merge_requests/mergeability_check_service.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +module MergeRequests + class MergeabilityCheckService < ::BaseService + include Gitlab::Utils::StrongMemoize + + delegate :project, to: :@merge_request + delegate :repository, to: :project + + def initialize(merge_request) + @merge_request = merge_request + end + + # Updates the MR merge_status. Whenever it switches to a can_be_merged state, + # the merge-ref is refreshed. + # + # recheck - When given, it'll enforce a merge-ref refresh if the current merge_status is + # can_be_merged or cannot_be_merged and merge-ref is outdated. + # Given MergeRequests::RefreshService is called async, it might happen that the target + # branch gets updated, but the MergeRequest#merge_status lags behind. So in scenarios + # where we need the current state of the merge ref in repository, the `recheck` + # argument is required. + # + # Returns a ServiceResponse indicating merge_status is/became can_be_merged + # and the merge-ref is synced. Success in case of being/becoming mergeable, + # error otherwise. + def execute(recheck: false) + return ServiceResponse.error(message: 'Invalid argument') unless merge_request + return ServiceResponse.error(message: 'Unsupported operation') if Gitlab::Database.read_only? + + recheck! if recheck + update_merge_status + + unless merge_request.can_be_merged? + return ServiceResponse.error(message: 'Merge request is not mergeable') + end + + unless merge_ref_auto_sync_enabled? + return ServiceResponse.error(message: 'Merge ref is outdated due to disabled feature') + end + + unless payload.fetch(:merge_ref_head) + return ServiceResponse.error(message: 'Merge ref cannot be updated') + end + + ServiceResponse.success(payload: payload) + end + + private + + attr_reader :merge_request + + def payload + strong_memoize(:payload) do + { + merge_ref_head: merge_ref_head_payload + } + end + end + + def merge_ref_head_payload + commit = merge_request.merge_ref_head + + return unless commit + + target_id, source_id = commit.parent_ids + + { + commit_id: commit.id, + source_id: source_id, + target_id: target_id + } + end + + def update_merge_status + return unless merge_request.recheck_merge_status? + + if can_git_merge? && merge_to_ref + merge_request.mark_as_mergeable + else + merge_request.mark_as_unmergeable + end + end + + def recheck! + if !merge_request.recheck_merge_status? && outdated_merge_ref? + merge_request.mark_as_unchecked + end + end + + # Checks if the existing merge-ref is synced with the target branch. + # + # Returns true if the merge-ref does not exists or is out of sync. + def outdated_merge_ref? + return false unless merge_ref_auto_sync_enabled? + return false unless merge_request.open? + + return true unless ref_head = merge_request.merge_ref_head + return true unless target_sha = merge_request.target_branch_sha + return true unless source_sha = merge_request.source_branch_sha + + ref_head.parent_ids != [target_sha, source_sha] + end + + def can_git_merge? + !merge_request.broken? && repository.can_be_merged?(merge_request.diff_head_sha, merge_request.target_branch) + end + + def merge_to_ref + return true unless merge_ref_auto_sync_enabled? + + result = MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request) + result[:status] == :success + end + + def merge_ref_auto_sync_enabled? + Feature.enabled?(:merge_ref_auto_sync, project, default_enabled: true) + end + end +end diff --git a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb index 3413a9e4612..58f795e639e 100644 --- a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb +++ b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb @@ -2,6 +2,14 @@ module PagesDomains class ObtainLetsEncryptCertificateService + # time for processing validation requests for acme challenges + # 5-15 seconds is usually enough + CHALLENGE_PROCESSING_DELAY = 1.minute.freeze + + # time LetsEncrypt ACME server needs to generate the certificate + # no particular SLA, usually takes 10-15 seconds + CERTIFICATE_PROCESSING_DELAY = 1.minute.freeze + attr_reader :pages_domain def initialize(pages_domain) @@ -14,6 +22,7 @@ module PagesDomains unless acme_order ::PagesDomains::CreateAcmeOrderService.new(pages_domain).execute + PagesDomainSslRenewalWorker.perform_in(CHALLENGE_PROCESSING_DELAY, pages_domain.id) return end @@ -23,6 +32,7 @@ module PagesDomains case api_order.status when 'ready' api_order.request_certificate(private_key: acme_order.private_key, domain: pages_domain.domain) + PagesDomainSslRenewalWorker.perform_in(CERTIFICATE_PROCESSING_DELAY, pages_domain.id) when 'valid' save_certificate(acme_order.private_key, api_order) acme_order.destroy! diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb index a2f36d2bd1b..a25c985585b 100644 --- a/app/services/projects/propagate_service_template.rb +++ b/app/services/projects/propagate_service_template.rb @@ -24,7 +24,7 @@ module Projects def propagate_projects_with_template loop do - batch = project_ids_batch + batch = Project.uncached { project_ids_batch } bulk_create_from_template(batch) unless batch.empty? diff --git a/app/services/service_response.rb b/app/services/service_response.rb index 1de30e68d87..f3437ba16de 100644 --- a/app/services/service_response.rb +++ b/app/services/service_response.rb @@ -1,19 +1,20 @@ # frozen_string_literal: true class ServiceResponse - def self.success(message: nil) - new(status: :success, message: message) + def self.success(message: nil, payload: {}) + new(status: :success, message: message, payload: payload) end - def self.error(message:, http_status: nil) - new(status: :error, message: message, http_status: http_status) + def self.error(message:, payload: {}, http_status: nil) + new(status: :error, message: message, payload: payload, http_status: http_status) end - attr_reader :status, :message, :http_status + attr_reader :status, :message, :http_status, :payload - def initialize(status:, message: nil, http_status: nil) + def initialize(status:, message: nil, payload: {}, http_status: nil) self.status = status self.message = message + self.payload = payload self.http_status = http_status end @@ -27,5 +28,5 @@ class ServiceResponse private - attr_writer :status, :message, :http_status + attr_writer :status, :message, :http_status, :payload end diff --git a/app/views/admin/application_settings/_localization.html.haml b/app/views/admin/application_settings/_localization.html.haml index bb4d1fa1241..e01c123d1db 100644 --- a/app/views/admin/application_settings/_localization.html.haml +++ b/app/views/admin/application_settings/_localization.html.haml @@ -8,4 +8,11 @@ .form-text.text-muted = _('Default first day of the week in calendars and date pickers.') + .form-group + = f.label :time_tracking, _('Time tracking'), class: 'label-bold' + .form-check + = f.check_box :time_tracking_limit_to_hours, class: 'form-check-input' + = f.label :time_tracking_limit_to_hours, class: 'form-check-label' do + = _('Limit display of time tracking units to hours.') + = f.submit _('Save changes'), class: "btn btn-success" diff --git a/app/views/admin/application_settings/_logging.html.haml b/app/views/admin/application_settings/_logging.html.haml deleted file mode 100644 index d57066bba01..00000000000 --- a/app/views/admin/application_settings/_logging.html.haml +++ /dev/null @@ -1,38 +0,0 @@ -= form_for @application_setting, url: reporting_admin_application_settings_path(anchor: 'js-logging-settings'), html: { class: 'fieldset-form' } do |f| - = form_errors(@application_setting) - - %p - %strong - NOTE: - These settings will be removed from the UI in a GitLab 12.0 release and made available within gitlab.yml. - In addition, you will be able to define a Sentry Environment to differentiate between multiple deployments. For example, development, staging, and production. - - %fieldset - .form-group - .form-check - = f.check_box :sentry_enabled, class: 'form-check-input' - = f.label :sentry_enabled, class: 'form-check-label' do - Enable Sentry - .form-text.text-muted - %p This setting requires a restart to take effect. - Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: - %a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com - - .form-group - = f.label :sentry_dsn, 'Sentry DSN', class: 'label-bold' - = f.text_field :sentry_dsn, class: 'form-control' - - .form-group - .form-check - = f.check_box :clientside_sentry_enabled, class: 'form-check-input' - = f.label :clientside_sentry_enabled, class: 'form-check-label' do - Enable Clientside Sentry - .form-text.text-muted - Sentry can also be used for reporting and logging clientside exceptions. - %a{ href: 'https://sentry.io/for/javascript/', target: '_blank', rel: 'noopener noreferrer' } https://sentry.io/for/javascript/ - - .form-group - = f.label :clientside_sentry_dsn, 'Clientside Sentry DSN', class: 'label-bold' - = f.text_field :clientside_sentry_dsn, class: 'form-control' - - = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/admin/application_settings/_spam.html.haml b/app/views/admin/application_settings/_spam.html.haml index a34fc15acb1..d24e46b2815 100644 --- a/app/views/admin/application_settings/_spam.html.haml +++ b/app/views/admin/application_settings/_spam.html.haml @@ -7,7 +7,10 @@ = f.check_box :recaptcha_enabled, class: 'form-check-input' = f.label :recaptcha_enabled, class: 'form-check-label' do Enable reCAPTCHA - %span.form-text.text-muted#recaptcha_help_block Helps prevent bots from creating accounts + - recaptcha_v2_link_url = 'https://developers.google.com/recaptcha/docs/versions' + - recaptcha_v2_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: recaptcha_v2_link_url } + %span.form-text.text-muted#recaptcha_help_block + = _('Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}').html_safe % { recaptcha_v2_link_start: recaptcha_v2_link_start, recaptcha_v2_link_end: '</a>'.html_safe } .form-group = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'label-bold' diff --git a/app/views/admin/application_settings/reporting.html.haml b/app/views/admin/application_settings/reporting.html.haml index 1c2d9ccdb2d..46e3d1c4570 100644 --- a/app/views/admin/application_settings/reporting.html.haml +++ b/app/views/admin/application_settings/reporting.html.haml @@ -23,14 +23,3 @@ = _('Set notification email for abuse reports.') .settings-content = render 'abuse' - -%section.settings.as-logging.no-animate#js-logging-settings{ class: ('expanded' if expanded_by_default?) } - .settings-header - %h4 - = _('Error Reporting and Logging') - %button.btn.btn-default.js-settings-toggle{ type: 'button' } - = expanded_by_default? ? _('Collapse') : _('Expand') - %p - = _('Enable Sentry for error reporting and logging.') - .settings-content - = render 'logging' diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 2e23b748edb..5129f5d193b 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -58,7 +58,7 @@ .scroll-container %ul.tokens-container.list-unstyled %li.input-token - %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } } + %input.form-control.filtered-search{ search_filter_input_options('runners') } #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { action: 'submit' } } diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index dcd6f7c8078..5c6131db37d 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -32,11 +32,12 @@ %li %span.light Email: %strong - = mail_to @user.email + = render partial: 'shared/email_with_badge', locals: { email: mail_to(@user.email), verified: @user.confirmed? } - @user.emails.each do |email| %li %span.light Secondary email: - %strong= email.email + %strong + = render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? } = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-sm btn btn-remove float-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do %i.fa.fa-times %li diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index eae3ee6339f..034273558bb 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -33,7 +33,7 @@ = accept_terms_label.html_safe = render_if_exists 'devise/shared/email_opted_in', f: f %div - - if Gitlab::Recaptcha.enabled? + - if show_recaptcha_sign_up? = recaptcha_tags .submit-container = f.submit _("Register"), class: "btn-register btn qa-new-user-register-button" diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 7535aee83a3..20b844f9fd8 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -46,7 +46,7 @@ = yield :library_javascripts = javascript_include_tag locale_path unless I18n.locale == :en - = webpack_bundle_tag "raven" if Gitlab::CurrentSettings.clientside_sentry_enabled + = webpack_bundle_tag "raven" if Gitlab.config.sentry.enabled - if content_for?(:page_specific_javascripts) = yield :page_specific_javascripts diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml index 438340464bd..1d7a501e5c2 100644 --- a/app/views/layouts/header/_new_dropdown.haml +++ b/app/views/layouts/header/_new_dropdown.haml @@ -1,5 +1,5 @@ %li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown" } } - = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do + = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", id: "js-onboarding-new-project-link", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do = sprite_icon('plus-square', size: 16) = sprite_icon('angle-down', css_class: 'caret-down') .dropdown-menu.dropdown-menu-right diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 49ff976f8e8..e401488ecff 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -50,7 +50,7 @@ = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do .nav-icon-container = sprite_icon('doc-text') - %span.nav-item-name + %span.nav-item-name#js-onboarding-repo-link = _('Repository') %ul.sidebar-sub-level-items @@ -64,11 +64,11 @@ = _('Files') = nav_link(controller: [:commit, :commits]) do - = link_to project_commits_path(@project, current_ref) do + = link_to project_commits_path(@project, current_ref), id: 'js-onboarding-commits-link' do = _('Commits') = nav_link(html_options: {class: branches_tab_class}) do - = link_to project_branches_path(@project), class: 'qa-branches-link' do + = link_to project_branches_path(@project), class: 'qa-branches-link', id: 'js-onboarding-branches-link' do = _('Branches') = nav_link(controller: [:tags]) do @@ -98,7 +98,7 @@ = link_to project_issues_path(@project), class: 'shortcuts-issues qa-issues-item' do .nav-icon-container = sprite_icon('issues') - %span.nav-item-name + %span.nav-item-name#js-onboarding-issues-link = _('Issues') - if @project.issues_enabled? %span.badge.badge-pill.count.issue_counter @@ -153,7 +153,7 @@ = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests qa-merge-requests-link' do .nav-icon-container = sprite_icon('git-merge') - %span.nav-item-name + %span.nav-item-name#js-onboarding-mr-link = _('Merge Requests') %span.badge.badge-pill.count.merge_counter.js-merge-counter = number_with_delimiter(@project.open_merge_requests_count) @@ -170,7 +170,7 @@ = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines' do .nav-icon-container = sprite_icon('rocket') - %span.nav-item-name + %span.nav-item-name#js-onboarding-pipelines-link = _('CI / CD') %ul.sidebar-sub-level-items @@ -335,7 +335,7 @@ = link_to edit_project_path(@project), class: 'shortcuts-tree' do .nav-icon-container = sprite_icon('settings') - %span.nav-item-name.qa-settings-item + %span.nav-item-name.qa-settings-item#js-onboarding-settings-link = _('Settings') %ul.sidebar-sub-level-items @@ -351,7 +351,7 @@ %span = _('General') = nav_link(controller: :project_members) do - = link_to project_project_members_path(@project), title: _('Members'), class: 'qa-link-members-settings' do + = link_to project_project_members_path(@project), title: _('Members'), class: 'qa-link-members-settings', id: 'js-onboarding-settings-members-link' do %span = _('Members') - if can_edit diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml index 4c18398e3dc..65ef9690062 100644 --- a/app/views/profiles/personal_access_tokens/index.html.haml +++ b/app/views/profiles/personal_access_tokens/index.html.haml @@ -1,5 +1,5 @@ -- breadcrumb_title "Access Tokens" -- page_title "Personal Access Tokens" +- breadcrumb_title s_('AccessTokens|Access Tokens') +- page_title s_('AccessTokens|Personal Access Tokens') - @content_class = "limit-container-width" unless fluid_layout .row.prepend-top-default @@ -7,10 +7,10 @@ %h4.prepend-top-0 = page_title %p - You can generate a personal access token for each application you use that needs access to the GitLab API. + = s_('AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API.') %p - You can also use personal access tokens to authenticate against Git over HTTP. - They are the only accepted password when you have Two-Factor Authentication (2FA) enabled. + = s_('AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP.') + = s_('AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled.') .col-lg-8 - if @new_personal_access_token @@ -24,35 +24,33 @@ .row.prepend-top-default .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 - Feed token + = s_('AccessTokens|Feed token') %p - Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs. + = s_('AccessTokens|Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs.') %p - It cannot be used to access any other data. + = s_('AccessTokens|It cannot be used to access any other data.') .col-lg-8.feed-token-reset - = label_tag :feed_token, 'Feed token', class: "label-bold" + = label_tag :feed_token, s_('AccessTokens|Feed token'), class: "label-bold" = text_field_tag :feed_token, current_user.feed_token, class: 'form-control', readonly: true, onclick: 'this.select()' %p.form-text.text-muted - Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. - You should - = link_to 'reset it', [:reset, :feed_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS or calendar URLs currently in use will stop working.' } - if that ever happens. + - reset_link = link_to s_('AccessTokens|reset it'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.') } + - reset_message = s_('AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens.') % { link_reset_it: reset_link } + = reset_message.html_safe - if incoming_email_token_enabled? %hr .row.prepend-top-default .col-lg-4.profile-settings-sidebar %h4.prepend-top-0 - Incoming email token + = s_('AccessTokens|Incoming email token') %p - Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses. + = s_('AccessTokens|Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses.') %p - It cannot be used to access any other data. + = s_('AccessTokens|It cannot be used to access any other data.') .col-lg-8.incoming-email-token-reset - = label_tag :incoming_email_token, 'Incoming email token', class: "label-bold" + = label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: "label-bold" = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control', readonly: true, onclick: 'this.select()' %p.form-text.text-muted - Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. - You should - = link_to 'reset it', [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: 'Are you sure? Any issue email addresses currently in use will stop working.' } - if that ever happens. + - reset_link = link_to s_('AccessTokens|reset it'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.') } + - reset_message = s_('AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens.') % { link_reset_it: reset_link } + = reset_message.html_safe diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index a5eaae2dff4..3638334d61c 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -1,6 +1,5 @@ - merged = local_assigns.fetch(:merged, false) - commit = @repository.commit(branch.dereferenced_target) -- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0 - diverging_commit_counts = @repository.diverging_commit_counts(branch) - number_commits_distance = diverging_commit_counts[:distance] - number_commits_behind = diverging_commit_counts[:behind] @@ -31,23 +30,7 @@ = s_('Branches|Cant find HEAD commit for this branch') - if branch.name != @repository.root_ref - - if number_commits_distance.nil? - .divergence-graph.d-none.d-md-block{ title: s_('%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead') % { number_commits_behind: diverging_count_label(number_commits_behind), - default_branch: @repository.root_ref, - number_commits_ahead: diverging_count_label(number_commits_ahead) } } - .graph-side - .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" } - %span.count.count-behind= diverging_count_label(number_commits_behind) - .graph-separator - .graph-side - .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" } - %span.count.count-ahead= diverging_count_label(number_commits_ahead) - - else - .divergence-graph.d-none.d-md-block{ title: s_('More than %{number_commits_distance} commits different with %{default_branch}') % { number_commits_distance: diverging_count_label(number_commits_distance), - default_branch: @repository.root_ref} } - .graph-side.full - .bar{ style: "width: #{number_commits_distance * bar_graph_width_factor}%" } - %span.count= diverging_count_label(number_commits_distance) + .js-branch-divergence-graph{ data: { distance: number_commits_distance, ahead_count: number_commits_ahead, behind_count: number_commits_behind, default_branch: @repository.root_ref, max_commits: @max_commits } } .controls.d-none.d-md-block< - if merge_project && create_mr_button?(@repository.root_ref, branch.name) @@ -56,7 +39,7 @@ - if branch.name != @repository.root_ref = link_to project_compare_path(@project, @repository.root_ref, branch.name), - class: "btn btn-default #{'prepend-left-10' unless merge_project}", + class: "btn btn-default js-onboarding-compare-branches #{'prepend-left-10' unless merge_project}", title: s_('Branches|Compare') do = s_('Branches|Compare') diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 87b9920e8b4..2c78e74be2f 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -20,9 +20,9 @@ .commit-detail.flex-list .commit-content.qa-commit-content - if view_details && merge_request - = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title" + = link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title js-onboarding-commit-item" - else - = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title") + = link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title js-onboarding-commit-item") %span.commit-row-message.d-inline.d-sm-none · = commit.short_id diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 99cbbc11acd..3ec92676cde 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -6,4 +6,5 @@ "can-create-environment" => can?(current_user, :create_environment, @project).to_s, "new-environment-path" => new_project_environment_path(@project), "help-page-path" => help_page_path("ci/environments"), + "deploy-boards-help-path" => help_page_path("user/project/deploy_boards", anchor: "enabling-deploy-boards"), "css-class" => container_class } } diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 9293aa1b309..4759991449e 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -6,7 +6,7 @@ .issuable-info-container .issuable-main-info .issue-title.title - %span.issue-title-text{ dir: "auto" } + %span.issue-title-text.js-onboarding-issue-item{ dir: "auto" } - if issue.confidential? %span.has-tooltip{ title: _('Confidential') } = confidential_icon(issue) diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 6f7713124ac..7d539c9d749 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,6 +1,6 @@ - empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues') -%ul.content-list.issues-list.issuable-list +%ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position') } = render partial: "projects/issues/issue", collection: @issues - if @issues.blank? = render empty_state_path diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index 457b2936278..52bb797b5b3 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -2,6 +2,7 @@ - can_create_merge_request = can?(current_user, :create_merge_request_in, @project) - data_action = can_create_merge_request ? 'create-mr' : 'create-branch' - value = can_create_merge_request ? 'Create merge request' : 'Create branch' + - value = can_create_confidential_merge_request? ? _('Create confidential merge request') : value - can_create_path = can_create_branch_project_issue_path(@project, @issue) - create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch) @@ -23,12 +24,15 @@ = icon('caret-down') .droplab-dropdown - %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-right.gl-show-field-errors{ data: { dropdown: true } } + %ul#create-merge-request-dropdown.create-merge-request-dropdown-menu.dropdown-menu.dropdown-menu-right.gl-show-field-errors{ class: ("create-confidential-merge-request-dropdown-menu" if can_create_confidential_merge_request?), data: { dropdown: true } } - if can_create_merge_request %li.droplab-item-selected{ role: 'button', data: { value: 'create-mr', text: _('Create merge request') } } .menu-item = icon('check', class: 'icon') - = _('Create merge request and branch') + - if can_create_confidential_merge_request? + = _('Create confidential merge request and branch') + - else + = _('Create merge request and branch') %li{ class: [!can_create_merge_request && 'droplab-item-selected'], role: 'button', data: { value: 'create-branch', text: _('Create branch') } } .menu-item diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index d55afee4523..8ec07dc3bb4 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -91,7 +91,7 @@ = render 'award_emoji/awards_block', awardable: @issue, inline: true .col-md-12.col-lg-6.new-branch-col #js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } } - = render 'new_branch' unless @issue.confidential? + = render 'new_branch' if show_new_branch_button? = render_if_exists 'projects/issues/discussion' diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 67e5e4ca62d..eb516684e52 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -6,7 +6,7 @@ .issuable-info-container .issuable-main-info .merge-request-title.title - %span.merge-request-title-text + %span.merge-request-title-text.js-onboarding-mr-item = link_to merge_request.title, merge_request_path(merge_request) - if merge_request.tasks? %span.task-status.d-none.d-sm-inline-block diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index d7e16dbd40c..1cfe302fdc7 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -46,11 +46,11 @@ = render_if_exists 'projects/new_ci_cd_only_project_tab', active_tab: active_tab .tab-content.gitlab-tab-content - .tab-pane{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' } + .tab-pane.js-toggle-container{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' } = form_for @project, html: { class: 'new_project' } do |f| = render 'new_project_fields', f: f, project_name_id: "blank-project-name" - #create-from-template-pane.tab-pane.px-0.pb-0{ class: active_when(active_tab == 'template'), role: 'tabpanel' } + #create-from-template-pane.tab-pane.js-toggle-container.px-0.pb-0{ class: active_when(active_tab == 'template'), role: 'tabpanel' } .card-slim.m-4.p-4 %div - contributing_templates_url = 'https://gitlab.com/gitlab-org/project-templates/contributing' diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml index e7edb93f05b..5b657966909 100644 --- a/app/views/projects/pages_domains/_form.html.haml +++ b/app/views/projects/pages_domains/_form.html.haml @@ -11,7 +11,7 @@ - if Gitlab.config.pages.external_https - - auto_ssl_available = ::Gitlab::LetsEncrypt::Client.new.enabled? + - auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?(@domain) - auto_ssl_enabled = @domain.auto_ssl_enabled? - auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index aa30ebdc3b8..de1b95692d6 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -4,7 +4,7 @@ = @service.title = boolean_to_icon @service.activated? - %p= @service.description + %p= #{@service.description}. - if @service.respond_to?(:detailed_description) %p= @service.detailed_description diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml index 5847751b268..b351ecd4edf 100644 --- a/app/views/search/results/_wiki_blob.html.haml +++ b/app/views/search/results/_wiki_blob.html.haml @@ -1,5 +1,5 @@ - project = find_project_for_result_blob(projects, wiki_blob) - wiki_blob = parse_search_result(wiki_blob) -- wiki_blob_link = project_wiki_path(project, Pathname.new(wiki_blob.filename).sub_ext('')) +- wiki_blob_link = project_wiki_path(project, wiki_blob.basename) = render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: wiki_blob.filename, blob_link: wiki_blob_link } diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 987a5d4f13f..a21dcabb485 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,6 +1,6 @@ - if @issues.to_a.any? .card.card-small.card-without-border - %ul.content-list.issues-list.issuable-list + %ul.content-list.issues-list.issuable-list{ class: ("manual-ordering" if @sort == 'relative_position'), data: { group_full_path: @group&.full_path } } = render partial: 'projects/issues/issue', collection: @issues = paginate @issues, theme: "gitlab" - else diff --git a/app/views/shared/_personal_access_tokens_created_container.html.haml b/app/views/shared/_personal_access_tokens_created_container.html.haml index a8d3de66418..42989b145a2 100644 --- a/app/views/shared/_personal_access_tokens_created_container.html.haml +++ b/app/views/shared/_personal_access_tokens_created_container.html.haml @@ -1,5 +1,5 @@ -- container_title = local_assigns.fetch(:container_title, 'Your New Personal Access Token') -- clipboard_button_title = local_assigns.fetch(:clipboard_button_title, 'Copy personal access token to clipboard') +- container_title = local_assigns.fetch(:container_title, _('Your New Personal Access Token')) +- clipboard_button_title = local_assigns.fetch(:clipboard_button_title, _('Copy personal access token to clipboard')) .created-personal-access-token-container %h5.prepend-top-0 @@ -9,6 +9,7 @@ = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "qa-created-personal-access-token form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block" %span.input-group-append = clipboard_button(text: new_token_value, title: clipboard_button_title, placement: "left", class: "input-group-text btn-default btn-clipboard") - %span#created-token-help-block.form-text.text-muted.text-danger Make sure you save it - you won't be able to access it again. + %span#created-token-help-block.form-text.text-muted.text-danger + = _("Make sure you save it - you won't be able to access it again.") %hr diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml index 0891b3459ec..1d96feda3b0 100644 --- a/app/views/shared/_personal_access_tokens_form.html.haml +++ b/app/views/shared/_personal_access_tokens_form.html.haml @@ -1,9 +1,9 @@ -- type = impersonation ? "impersonation" : "personal access" +- type = impersonation ? s_('Profiles|impersonation') : s_('Profiles|personal access') %h5.prepend-top-0 - Add a #{type} token + = _('Add a %{type} token') % { type: type } %p.profile-settings-content - Pick a name for the application, and we'll give you a unique #{type} token. + = _("Pick a name for the application, and we'll give you a unique %{type} token.") % { type: type } = form_for token, url: path, method: :post, html: { class: 'js-requires-input' } do |f| @@ -11,19 +11,19 @@ .row .form-group.col-md-6 - = f.label :name, class: 'label-bold' + = f.label :name, _('Name'), class: 'label-bold' = f.text_field :name, class: "form-control qa-personal-access-token-name-field", required: true .row .form-group.col-md-6 - = f.label :expires_at, class: 'label-bold' + = f.label :expires_at, _('Expires at'), class: 'label-bold' .input-icon-wrapper = f.text_field :expires_at, class: "datepicker form-control", placeholder: 'YYYY-MM-DD' = icon('calendar', { class: 'input-icon-right' }) .form-group - = f.label :scopes, class: 'label-bold' + = f.label :scopes, _('Scopes'), class: 'label-bold' = render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes .prepend-top-default - = f.submit "Create #{type} token", class: "btn btn-success qa-create-token-button" + = f.submit _('Create %{type} token') % { type: type }, class: "btn btn-success qa-create-token-button" diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml index 49f3aae0f98..823117f37ca 100644 --- a/app/views/shared/_personal_access_tokens_table.html.haml +++ b/app/views/shared/_personal_access_tokens_table.html.haml @@ -1,20 +1,21 @@ -- type = impersonation ? "Impersonation" : "Personal Access" +- type = impersonation ? s_('Profiles|Impersonation') : s_('Profiles|Personal Access') %hr -%h5 Active #{type} Tokens (#{active_tokens.length}) +%h5 + = _('Active %{type} Tokens (%{token_length})') % { type: type, token_length: active_tokens.length } - if impersonation %p.profile-settings-content - To see all the user's personal access tokens you must impersonate them first. + = _("To see all the user's personal access tokens you must impersonate them first.") - if active_tokens.present? .table-responsive %table.table.active-tokens %thead %tr - %th Name - %th Created - %th Expires - %th Scopes + %th= _('Name') + %th= s_('AccessTokens|Created') + %th= _('Expires') + %th= _('Scopes') %th %tbody - active_tokens.each do |token| @@ -26,10 +27,10 @@ %span{ class: ('text-warning' if token.expires_soon?) } In #{distance_of_time_in_words_to_now(token.expires_at)} - else - %span.token-never-expires-label Never - %td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>" + %span.token-never-expires-label= _('Never') + %td= token.scopes.present? ? token.scopes.join(", ") : _('<no scopes selected>') - path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token) - %td= link_to "Revoke", path, method: :put, class: "btn btn-danger float-right qa-revoke-button", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." } + %td= link_to _('Revoke'), path, method: :put, class: "btn btn-danger float-right qa-revoke-button", data: { confirm: _('Are you sure you want to revoke this %{type} Token? This action cannot be undone.') % { type: type } } - else .settings-message.text-center - This user has no active #{type} Tokens. + = _('This user has no active %{type} Tokens.') % { type: type } diff --git a/app/views/shared/boards/components/sidebar/_time_tracker.html.haml b/app/views/shared/boards/components/sidebar/_time_tracker.html.haml index b76d44c5907..43081499920 100644 --- a/app/views/shared/boards/components/sidebar/_time_tracker.html.haml +++ b/app/views/shared/boards/components/sidebar/_time_tracker.html.haml @@ -3,4 +3,5 @@ ":time-spent" => "issue.timeSpent || 0", ":human-time-estimate" => "issue.humanTimeEstimate", ":human-time-spent" => "issue.humanTimeSpent", + ":limit-to-hours" => "timeTrackingLimitToHours", "root-path" => "#{root_url}" } diff --git a/app/views/shared/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml index 1dd97bc4ed1..403e001bfe8 100644 --- a/app/views/shared/issuable/_sort_dropdown.html.haml +++ b/app/views/shared/issuable/_sort_dropdown.html.haml @@ -1,6 +1,7 @@ - sort_value = @sort - sort_title = issuable_sort_option_title(sort_value) - viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues' +- manual_sorting = viewing_issues && controller.controller_name != 'dashboard' && Feature.enabled?(:manual_sorting) .dropdown.inline.prepend-left-10.issue-sort-dropdown .btn-group{ role: 'group' } @@ -17,6 +18,6 @@ = sortable_item(sort_title_due_date, page_filter_path(sort: sort_value_due_date), sort_title) if viewing_issues = sortable_item(sort_title_popularity, page_filter_path(sort: sort_value_popularity), sort_title) = sortable_item(sort_title_label_priority, page_filter_path(sort: sort_value_label_priority), sort_title) - = sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if viewing_issues && Feature.enabled?(:manual_sorting) + = sortable_item(sort_title_relative_position, page_filter_path(sort: sort_value_relative_position), sort_title) if manual_sorting = render_if_exists('shared/ee/issuable/sort_dropdown', viewing_issues: viewing_issues, sort_title: sort_title) = issuable_sort_direction_button(sort_value) diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index b24075c7849..ced6af50501 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -93,7 +93,11 @@ = milestone.issues_visible_to_user(current_user).closed.count .block - #issuable-time-tracker{ data: { time_estimate: @milestone.total_issue_time_estimate, time_spent: @milestone.total_issue_time_spent, human_time_estimate: @milestone.human_total_issue_time_estimate, human_time_spent: @milestone.human_total_issue_time_spent } } + #issuable-time-tracker{ data: { time_estimate: @milestone.total_issue_time_estimate, + time_spent: @milestone.total_issue_time_spent, + human_time_estimate: @milestone.human_total_issue_time_estimate, + human_time_spent: @milestone.human_total_issue_time_spent, + limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s } } // Fallback while content is loading .title.hide-collapsed = _('Time tracking') diff --git a/app/views/shared/tokens/_scopes_list.html.haml b/app/views/shared/tokens/_scopes_list.html.haml index f99e905e95c..428861485b4 100644 --- a/app/views/shared/tokens/_scopes_list.html.haml +++ b/app/views/shared/tokens/_scopes_list.html.haml @@ -4,7 +4,7 @@ %tr %td - Scopes + = _('Scopes') %td %ul.scopes-list.append-bottom-0 - token.scopes.each do |scope| diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index fd0cc5fb24e..e55962b629e 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -9,6 +9,7 @@ - cronjob:import_export_project_cleanup - cronjob:pages_domain_verification_cron - cronjob:pages_domain_removal_cron +- cronjob:pages_domain_ssl_renewal_cron - cronjob:pipeline_schedule - cronjob:prune_old_events - cronjob:remove_expired_group_links @@ -133,6 +134,7 @@ - new_note - pages - pages_domain_verification +- pages_domain_ssl_renewal - plugin - post_receive - process_commit diff --git a/app/workers/build_success_worker.rb b/app/workers/build_success_worker.rb index 9a865fea621..ac947f3cf38 100644 --- a/app/workers/build_success_worker.rb +++ b/app/workers/build_success_worker.rb @@ -9,7 +9,6 @@ class BuildSuccessWorker # rubocop: disable CodeReuse/ActiveRecord def perform(build_id) Ci::Build.find_by(id: build_id).try do |build| - create_deployment(build) if build.has_environment? stop_environment(build) if build.stops_environment? end end @@ -18,17 +17,6 @@ class BuildSuccessWorker private ## - # Deprecated: - # As of 11.5, we started creating a deployment record when ci_builds record is created. - # Therefore we no longer need to create a deployment, after a build succeeded. - # We're leaving this code for the transition period, but we can remove this code in 11.6. - def create_deployment(build) - build.create_deployment.try do |deployment| - deployment.succeed - end - end - - ## # TODO: This should be processed in DeploymentSuccessWorker once we started storing `action` value in `deployments` records def stop_environment(build) build.persisted_environment.fire_state_event(:stop) diff --git a/app/workers/cluster_provision_worker.rb b/app/workers/cluster_provision_worker.rb index 926ae2b7286..59de7903c1c 100644 --- a/app/workers/cluster_provision_worker.rb +++ b/app/workers/cluster_provision_worker.rb @@ -9,8 +9,6 @@ class ClusterProvisionWorker cluster.provider.try do |provider| Clusters::Gcp::ProvisionService.new.execute(provider) if cluster.gcp? end - - ClusterConfigureWorker.perform_async(cluster.id) if cluster.user? end end end diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb new file mode 100644 index 00000000000..40c34d29970 --- /dev/null +++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class PagesDomainSslRenewalCronWorker + include ApplicationWorker + include CronjobQueue + + def perform + PagesDomain.need_auto_ssl_renewal.find_each do |domain| + next unless ::Gitlab::LetsEncrypt.enabled?(domain) + + PagesDomainSslRenewalWorker.perform_async(domain.id) + end + end +end diff --git a/app/workers/pages_domain_ssl_renewal_worker.rb b/app/workers/pages_domain_ssl_renewal_worker.rb new file mode 100644 index 00000000000..b32458ca777 --- /dev/null +++ b/app/workers/pages_domain_ssl_renewal_worker.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class PagesDomainSslRenewalWorker + include ApplicationWorker + + def perform(domain_id) + domain = PagesDomain.find_by_id(domain_id) + return unless domain&.enabled? + return unless ::Gitlab::LetsEncrypt.enabled?(domain) + + ::PagesDomains::ObtainLetsEncryptCertificateService.new(domain).execute + end +end diff --git a/changelogs/add-name-parameter-to-project-environments-api.yml b/changelogs/add-name-parameter-to-project-environments-api.yml new file mode 100644 index 00000000000..01d456eb75c --- /dev/null +++ b/changelogs/add-name-parameter-to-project-environments-api.yml @@ -0,0 +1,5 @@ +--- +title: Add `name` and `search` parameters to project environments API +merge_request: 29385 +author: Lee Tickett +type: added diff --git a/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml b/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml deleted file mode 100644 index 1297e9712fa..00000000000 --- a/changelogs/unreleased/10088-move-code-differences-EE-to-CE.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Added code differnces from EE in file 'app/assets/javascripts/pages/projects/project.js' to CE" -merge_request: 29271 -author: Michel Engelen -type: other diff --git a/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml b/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml deleted file mode 100644 index 4c85d4f9acb..00000000000 --- a/changelogs/unreleased/10795-add-epic-tree-BE-CE-epic-graphql-support.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added reference, web_path, and relative_position fields to GraphQL Issue -merge_request: 28998 -author: -type: changed diff --git a/changelogs/unreleased/10798-remove-dind-req-for-dast.yml b/changelogs/unreleased/10798-remove-dind-req-for-dast.yml deleted file mode 100644 index 33545fc7cfd..00000000000 --- a/changelogs/unreleased/10798-remove-dind-req-for-dast.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove dind from DAST template -merge_request: 28083 -author: -type: changed diff --git a/changelogs/unreleased/10842-add-missing-environments-variable-to-the-sast-analyzer-docker-container.yml b/changelogs/unreleased/10842-add-missing-environments-variable-to-the-sast-analyzer-docker-container.yml deleted file mode 100644 index 112b472aa3b..00000000000 --- a/changelogs/unreleased/10842-add-missing-environments-variable-to-the-sast-analyzer-docker-container.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Fix: propagate all documented ENV vars to CI when using SAST' -merge_request: 29564 -author: -type: fixed diff --git a/changelogs/unreleased/11105-fix-cs-with-proxy.yml b/changelogs/unreleased/11105-fix-cs-with-proxy.yml deleted file mode 100644 index ee32427d20e..00000000000 --- a/changelogs/unreleased/11105-fix-cs-with-proxy.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix proxy support in Container Scanning -merge_request: 27246 -author: -type: fixed diff --git a/changelogs/unreleased/11204-turn-off-mask-by-default.yml b/changelogs/unreleased/11204-turn-off-mask-by-default.yml deleted file mode 100644 index 5c554e04d45..00000000000 --- a/changelogs/unreleased/11204-turn-off-mask-by-default.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Default masked to false for new variables -merge_request: 28186 -author: -type: changed diff --git a/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml b/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml deleted file mode 100644 index 6570cb3e2a3..00000000000 --- a/changelogs/unreleased/11609-geo-remove-support-for-using-geo-with-an-installation-from-source-docs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove support for using Geo with an installation from source -merge_request: 28737 -author: -type: other diff --git a/changelogs/unreleased/12106-sp-ce.yml b/changelogs/unreleased/12106-sp-ce.yml deleted file mode 100644 index 2d073401b45..00000000000 --- a/changelogs/unreleased/12106-sp-ce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Moves snowplow to CE repo -merge_request: -author: -type: other diff --git a/changelogs/unreleased/1340-request-job-with-age.yml b/changelogs/unreleased/1340-request-job-with-age.yml deleted file mode 100644 index 766ac008c2e..00000000000 --- a/changelogs/unreleased/1340-request-job-with-age.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Added option to filter jobs by age in the /job/request API endpoint." -merge_request: 1340 -author: Dmitry Chepurovskiy -type: added diff --git a/changelogs/unreleased/13453_minimal_atom_fix.yml b/changelogs/unreleased/13453_minimal_atom_fix.yml deleted file mode 100644 index e0c18de3b1f..00000000000 --- a/changelogs/unreleased/13453_minimal_atom_fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added a content field to atom feed -merge_request: 27652 -author: -type: fixed diff --git a/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml b/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml new file mode 100644 index 00000000000..741a0faf469 --- /dev/null +++ b/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml @@ -0,0 +1,5 @@ +--- +title: Protect TeamCity builds from triggering when a branch has been deleted. And a MR-option +merge_request: 29836 +author: Nikolay Novikov, Raphael Tweitmann +type: fixed diff --git a/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml b/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml deleted file mode 100644 index bb2fc9af2a1..00000000000 --- a/changelogs/unreleased/19569-include-information-if-issue-was-closed-via-mr.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Include information if issue was clossed via merge request or commit -merge_request: 15610 -author: Michał Zając -type: changed diff --git a/changelogs/unreleased/237-style-toast-component.yml b/changelogs/unreleased/237-style-toast-component.yml deleted file mode 100644 index 2420df0ee55..00000000000 --- a/changelogs/unreleased/237-style-toast-component.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Style the toast component according to design specs. -merge_request: 27734 -author: -type: added diff --git a/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml b/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml deleted file mode 100644 index 8d3501e0171..00000000000 --- a/changelogs/unreleased/27987-use-findorcreateservice-to-create-labels.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use FindOrCreateService to create labels and check for existing ones -merge_request: 27987 -author: Matt Duren -type: fixed diff --git a/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml b/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml deleted file mode 100644 index e954b97f05b..00000000000 --- a/changelogs/unreleased/28757-remove-docker-pull-prefix-when-copying-a-tag-from-the-registry.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove `docker pull` prefix when copying a tag from the registry -merge_request: 28757 -author: Benedikt Franke -type: changed diff --git a/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml b/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml deleted file mode 100644 index 9c6897babb4..00000000000 --- a/changelogs/unreleased/28996-create-mvc-ui-in-haml.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add auto SSL toggle option to Pages domain settings page -merge_request: 26438 -author: -type: added diff --git a/changelogs/unreleased/30138-display-cycle-analytics-issue-logic-fixes.yml b/changelogs/unreleased/30138-display-cycle-analytics-issue-logic-fixes.yml deleted file mode 100644 index 574995f20fa..00000000000 --- a/changelogs/unreleased/30138-display-cycle-analytics-issue-logic-fixes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change logic behind cycle analytics -merge_request: 29018 -author: -type: changed diff --git a/changelogs/unreleased/30138-display-cycle-analytics-issue.yml b/changelogs/unreleased/30138-display-cycle-analytics-issue.yml deleted file mode 100644 index c7faa0480bd..00000000000 --- a/changelogs/unreleased/30138-display-cycle-analytics-issue.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show data on Cycle Analytics page when value is less than a second -merge_request: 28507 -author: -type: fixed diff --git a/changelogs/unreleased/30355-use-hours-only-for-time-tracking.yml b/changelogs/unreleased/30355-use-hours-only-for-time-tracking.yml new file mode 100644 index 00000000000..b0252f9e81b --- /dev/null +++ b/changelogs/unreleased/30355-use-hours-only-for-time-tracking.yml @@ -0,0 +1,5 @@ +--- +title: Add option to limit time tracking units to hours +merge_request: 29469 +author: Jon Kolb +type: added diff --git a/changelogs/unreleased/33064-add-labels-to-note-event-payload.yml b/changelogs/unreleased/33064-add-labels-to-note-event-payload.yml deleted file mode 100644 index e0a6253e1ef..00000000000 --- a/changelogs/unreleased/33064-add-labels-to-note-event-payload.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add labels to note event payload -merge_request: 29384 -author: Sujay Patel -type: added diff --git a/changelogs/unreleased/35428-docker-registry-date-not-accurate.yml b/changelogs/unreleased/35428-docker-registry-date-not-accurate.yml deleted file mode 100644 index f51ecdf83a6..00000000000 --- a/changelogs/unreleased/35428-docker-registry-date-not-accurate.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Changed the 'Created' label to 'Last Updated' on the container registry table - to more accurately reflect what the date represents. -merge_request: 29464 -author: -type: other diff --git a/changelogs/unreleased/37495.yml b/changelogs/unreleased/37495.yml deleted file mode 100644 index f6d421fc45b..00000000000 --- a/changelogs/unreleased/37495.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add documentation links for confidental and locked discussions -merge_request: 29073 -author: -type: changed diff --git a/changelogs/unreleased/38561-border-radii.yml b/changelogs/unreleased/38561-border-radii.yml deleted file mode 100644 index 510af18d651..00000000000 --- a/changelogs/unreleased/38561-border-radii.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix border radii on diff files and repo files -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/39304-broadcast-message-buttons.yml b/changelogs/unreleased/39304-broadcast-message-buttons.yml deleted file mode 100644 index 7eb289fca1f..00000000000 --- a/changelogs/unreleased/39304-broadcast-message-buttons.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update broadcast message action icons -merge_request: 28496 -author: Jarek Ostrowski @jareko -type: fixed diff --git a/changelogs/unreleased/45687-web-ide-empty-state.yml b/changelogs/unreleased/45687-web-ide-empty-state.yml deleted file mode 100644 index 9ef148275ab..00000000000 --- a/changelogs/unreleased/45687-web-ide-empty-state.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Empty project state for Web IDE -merge_request: 26556 -author: -type: added diff --git a/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml b/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml deleted file mode 100644 index 21dc170f1ca..00000000000 --- a/changelogs/unreleased/47846-position-is-off-when-visiting-files-with-anchors.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Position is off when visiting files with anchors -merge_request: 28913 -author: -type: fixed diff --git a/changelogs/unreleased/49041-issue-board-input-height.yml b/changelogs/unreleased/49041-issue-board-input-height.yml deleted file mode 100644 index de3fbb2ee11..00000000000 --- a/changelogs/unreleased/49041-issue-board-input-height.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reduce height of issue board input to align with buttons -merge_request: -author: -type: other diff --git a/changelogs/unreleased/49517-fix-notes-import-export.yml b/changelogs/unreleased/49517-fix-notes-import-export.yml deleted file mode 100644 index a9f4d736e0b..00000000000 --- a/changelogs/unreleased/49517-fix-notes-import-export.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix diff notes and discussion notes being exported as regular notes -merge_request: 28401 -author: -type: fixed diff --git a/changelogs/unreleased/49814-display-in-admin-area-if-emails-are-verified-or-not.yml b/changelogs/unreleased/49814-display-in-admin-area-if-emails-are-verified-or-not.yml new file mode 100644 index 00000000000..db1391edd73 --- /dev/null +++ b/changelogs/unreleased/49814-display-in-admin-area-if-emails-are-verified-or-not.yml @@ -0,0 +1,5 @@ +--- +title: Add a verified pill next to email addresses under the admin users section. +merge_request: 29669 +author: +type: added diff --git a/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml b/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml deleted file mode 100644 index 307c2bfb49d..00000000000 --- a/changelogs/unreleased/49915-fix-error-500-admin-projects-nil-storage.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix an error in projects admin when statistics are missing -merge_request: 28355 -author: -type: fixed diff --git a/changelogs/unreleased/50106-hide-whitespace-changes.yml b/changelogs/unreleased/50106-hide-whitespace-changes.yml deleted file mode 100644 index e95953c8665..00000000000 --- a/changelogs/unreleased/50106-hide-whitespace-changes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix whitespace changes visibility when the related file was initially collapsed -merge_request: 28950 -author: Ondřej Budai -type: fixed diff --git a/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml b/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml new file mode 100644 index 00000000000..b51079d5c74 --- /dev/null +++ b/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml @@ -0,0 +1,5 @@ +--- +title: "Changed HTTP Status Code for disabled repository on /branches and /commits to 404" +merge_request: 29585 +author: Sam Battalio +type: changed diff --git a/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml b/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml deleted file mode 100644 index 45770e1012c..00000000000 --- a/changelogs/unreleased/50850-kerrizor-extend-api-to-accept-start_project-option.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add API support for committing changes to different projects in same fork network -merge_request: 27915 -author: -type: added diff --git a/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml b/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml deleted file mode 100644 index 07bf8b04bbe..00000000000 --- a/changelogs/unreleased/51022-added-extended-height-to-labels-dropdown.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Added the `.extended-height` class to the labels-dropdown" -merge_request: 28659 -author: Michel Engelen -type: other diff --git a/changelogs/unreleased/51636-task-list-api-pderichs.yml b/changelogs/unreleased/51636-task-list-api-pderichs.yml deleted file mode 100644 index f18a0936ab2..00000000000 --- a/changelogs/unreleased/51636-task-list-api-pderichs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add task count and completed count to responses of Issue and MR -merge_request: 28859 -author: -type: added diff --git a/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml b/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml deleted file mode 100644 index 4e16b95ec11..00000000000 --- a/changelogs/unreleased/51854-api-to-get-all-project-group-members-returns-duplicates.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Removes duplicated members from api/projects/:id/members/all -merge_request: 24005 -author: Jacopo Beschi @jacopo-beschi -type: changed diff --git a/changelogs/unreleased/51952-forking-via-webide.yml b/changelogs/unreleased/51952-forking-via-webide.yml new file mode 100644 index 00000000000..4497c6b6ca4 --- /dev/null +++ b/changelogs/unreleased/51952-forking-via-webide.yml @@ -0,0 +1,5 @@ +--- +title: Resolve "500 error when forking via the web IDE button" +merge_request: 29909 +author: +type: fixed diff --git a/changelogs/unreleased/52366-improved-group-lists-ui.yml b/changelogs/unreleased/52366-improved-group-lists-ui.yml new file mode 100644 index 00000000000..99e5b67a56c --- /dev/null +++ b/changelogs/unreleased/52366-improved-group-lists-ui.yml @@ -0,0 +1,5 @@ +--- +title: Improve group list UI +merge_request: 26542 +author: +type: changed diff --git a/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml b/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml deleted file mode 100644 index e09de8ac8fc..00000000000 --- a/changelogs/unreleased/53134-multiple-extendes-for-a-job.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add support for multiple job parents in GitLab CI YAML. -merge_request: 26801 -author: Wolphin (Nikita) -type: added diff --git a/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml b/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml deleted file mode 100644 index efda07380a4..00000000000 --- a/changelogs/unreleased/54140-non-ar-cache-commit-markdown.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use Redis for CacheMarkDownField on non AR models -merge_request: 29054 -author: -type: performance diff --git a/changelogs/unreleased/54169-flash-warning-rebrush.yml b/changelogs/unreleased/54169-flash-warning-rebrush.yml deleted file mode 100644 index 420cc26a8cc..00000000000 --- a/changelogs/unreleased/54169-flash-warning-rebrush.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Rebrush of flash-warning according to the new design (brighter background and darker font)" -merge_request: 28916 -author: Michel Engelen -type: changed diff --git a/changelogs/unreleased/55033-discussion-system-note-alignment.yml b/changelogs/unreleased/55033-discussion-system-note-alignment.yml deleted file mode 100644 index 27072ec7e12..00000000000 --- a/changelogs/unreleased/55033-discussion-system-note-alignment.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Align system note within discussion with other notes -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/55125-mr-tab-scrolling.yml b/changelogs/unreleased/55125-mr-tab-scrolling.yml deleted file mode 100644 index e03ff6c5060..00000000000 --- a/changelogs/unreleased/55125-mr-tab-scrolling.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update merge request tabs so they no longer scroll -merge_request: -author: -type: other diff --git a/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml b/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml deleted file mode 100644 index f7dd8c59a7c..00000000000 --- a/changelogs/unreleased/55253-activity-feed-ui-enhance-line-height.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enhance line-height of Activity feed UI -merge_request: 28856 -author: Jacopo Beschi @jacopo-beschi -type: changed diff --git a/changelogs/unreleased/55362-refresh-blank-service-account-token.yml b/changelogs/unreleased/55362-refresh-blank-service-account-token.yml deleted file mode 100644 index 3189de97e8b..00000000000 --- a/changelogs/unreleased/55362-refresh-blank-service-account-token.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refresh service_account_token for kubernetes_namespaces -merge_request: 29657 -author: -type: fixed diff --git a/changelogs/unreleased/55447-validate-k8s-credentials.yml b/changelogs/unreleased/55447-validate-k8s-credentials.yml deleted file mode 100644 index 81f0efdb325..00000000000 --- a/changelogs/unreleased/55447-validate-k8s-credentials.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Validate Kubernetes credentials at cluster creation -merge_request: 27403 -author: -type: added diff --git a/changelogs/unreleased/5615-non-admins-only-archieve-ce.yml b/changelogs/unreleased/5615-non-admins-only-archieve-ce.yml deleted file mode 100644 index ac1aa249b82..00000000000 --- a/changelogs/unreleased/5615-non-admins-only-archieve-ce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add deletion protection setting column to application_settings table -merge_request: 29268 -author: -type: other diff --git a/changelogs/unreleased/56737-commits-and-mr-events-on-jira-api.yml b/changelogs/unreleased/56737-commits-and-mr-events-on-jira-api.yml deleted file mode 100644 index ee2ce8acaeb..00000000000 --- a/changelogs/unreleased/56737-commits-and-mr-events-on-jira-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose all current events properly on services API -merge_request: 29736 -author: Zsolt Kovari -type: fixed diff --git a/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml b/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml deleted file mode 100644 index c529749670d..00000000000 --- a/changelogs/unreleased/56959-drop-project_auto_devops_domain.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Removes project_auto_devops#domain column -merge_request: 28574 -author: -type: other diff --git a/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml b/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml deleted file mode 100644 index c1c1708f935..00000000000 --- a/changelogs/unreleased/56959-remove-auto-devops-domain-ci-variable.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Removes support for AUTO_DEVOPS_DOMAIN -merge_request: 28460 -author: -type: removed diff --git a/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml b/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml deleted file mode 100644 index a2de6cd6d45..00000000000 --- a/changelogs/unreleased/57037-fix-mr-checkboxes-mobile-alignment.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Merge Request merge checkbox alignment on mobile view -merge_request: 28845 -author: -type: fixed diff --git a/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml b/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml deleted file mode 100644 index 86b6bb20253..00000000000 --- a/changelogs/unreleased/57189-css-label-in-forms-with-bootstrap4.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix col-sm-* in forms to keep layout -merge_request: 24885 -author: Takuya Noguchi -type: fixed diff --git a/changelogs/unreleased/57813-merge-request-tabs-do-not-handle-ctrl-click-correctly.yml b/changelogs/unreleased/57813-merge-request-tabs-do-not-handle-ctrl-click-correctly.yml deleted file mode 100644 index 272faa67fcf..00000000000 --- a/changelogs/unreleased/57813-merge-request-tabs-do-not-handle-ctrl-click-correctly.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow command/control click to open link in new tab on Merge Request tabs -merge_request: 29506 -author: -type: fixed diff --git a/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml b/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml deleted file mode 100644 index faa1784ea21..00000000000 --- a/changelogs/unreleased/57825-moving-an-issue-results-in-broken-image-links-in-comments.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve moving an issue results in broken image links in comments -merge_request: 28654 -author: -type: fixed diff --git a/changelogs/unreleased/58269-separate-update-patch.yml b/changelogs/unreleased/58269-separate-update-patch.yml deleted file mode 100644 index e9b44257b07..00000000000 --- a/changelogs/unreleased/58269-separate-update-patch.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Do not display Update app button when saving Knative domain name -merge_request: 28904 -author: -type: changed diff --git a/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml b/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml deleted file mode 100644 index 25cc973159f..00000000000 --- a/changelogs/unreleased/58297-remove-extraneous-gitaly-calls-from-md-rendering.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reduce Gitaly calls to improve performance when rendering suggestions -merge_request: 29027 -author: -type: performance diff --git a/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml b/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml deleted file mode 100644 index 7e95158a0e0..00000000000 --- a/changelogs/unreleased/58404-set-default-max-depth-for-GraphQL.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 58404 - setup max depth for GraphQL -merge_request: 25737 -author: Ken Ding -type: added diff --git a/changelogs/unreleased/58433-email-notifications-do-not-work-properly-issue-due-date.yml b/changelogs/unreleased/58433-email-notifications-do-not-work-properly-issue-due-date.yml deleted file mode 100644 index 4579721446a..00000000000 --- a/changelogs/unreleased/58433-email-notifications-do-not-work-properly-issue-due-date.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix email notifications for user excluded actions -merge_request: 28835 -author: -type: fixed diff --git a/changelogs/unreleased/58538-discussion-top-radius.yml b/changelogs/unreleased/58538-discussion-top-radius.yml deleted file mode 100644 index 0fb16055623..00000000000 --- a/changelogs/unreleased/58538-discussion-top-radius.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix border radius of discussions -merge_request: 28490 -author: -type: fixed diff --git a/changelogs/unreleased/58632-fix-mr-widget-padding.yml b/changelogs/unreleased/58632-fix-mr-widget-padding.yml deleted file mode 100644 index fb4e1791359..00000000000 --- a/changelogs/unreleased/58632-fix-mr-widget-padding.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix padding in MR widget -merge_request: 28472 -author: -type: fixed diff --git a/changelogs/unreleased/58802-rename-webide.yml b/changelogs/unreleased/58802-rename-webide.yml new file mode 100644 index 00000000000..40471d967ce --- /dev/null +++ b/changelogs/unreleased/58802-rename-webide.yml @@ -0,0 +1,5 @@ +--- +title: Re-name files in Web IDE in a more natural way +merge_request: 29948 +author: +type: changed diff --git a/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml b/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml deleted file mode 100644 index 53be008816d..00000000000 --- a/changelogs/unreleased/58941-use-gitlab-serverless-with-existing-knative-installation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enable function features for external Knative installations -merge_request: 27173 -author: -type: changed diff --git a/changelogs/unreleased/58984-doc-missing-milestones-and-labels-links.yml b/changelogs/unreleased/58984-doc-missing-milestones-and-labels-links.yml deleted file mode 100644 index 6b74303c16e..00000000000 --- a/changelogs/unreleased/58984-doc-missing-milestones-and-labels-links.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Document when milestones and labels links are missing -merge_request: 29355 -author: -type: other diff --git a/changelogs/unreleased/59023-fix-web-ide-creating-branches-off-new-commits.yml b/changelogs/unreleased/59023-fix-web-ide-creating-branches-off-new-commits.yml deleted file mode 100644 index f7e0ee333aa..00000000000 --- a/changelogs/unreleased/59023-fix-web-ide-creating-branches-off-new-commits.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix IDE commit using latest ref in branch and overriding contents -merge_request: 29769 -author: -type: fixed diff --git a/changelogs/unreleased/59026-replace-favicon.yml b/changelogs/unreleased/59026-replace-favicon.yml deleted file mode 100644 index 34228bb8438..00000000000 --- a/changelogs/unreleased/59026-replace-favicon.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update favicon from next -merge_request: 28601 -author: Jarek Ostrowski @jareko -type: fixed diff --git a/changelogs/unreleased/59105-padding-unclickable-pipeline-job.yml b/changelogs/unreleased/59105-padding-unclickable-pipeline-job.yml deleted file mode 100644 index 95f08af3cb1..00000000000 --- a/changelogs/unreleased/59105-padding-unclickable-pipeline-job.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix padding of unclickable pipeline dropdown items to match links -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml b/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml deleted file mode 100644 index 0904f788b6f..00000000000 --- a/changelogs/unreleased/59376-Report-abuse-to-GitLab-should-be-Report-abuse-in-non-gitlab-com-instances.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change "Report abuse to GitLab" to more generic wording -merge_request: 28884 -author: Marc Schwede -type: other diff --git a/changelogs/unreleased/59587-add-graphql-logging.yml b/changelogs/unreleased/59587-add-graphql-logging.yml deleted file mode 100644 index 74c2a734f37..00000000000 --- a/changelogs/unreleased/59587-add-graphql-logging.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add dedicated logging for GraphQL queries -merge_request: 27885 -author: -type: other diff --git a/changelogs/unreleased/59651-remove-unnecessary-decimal-places-on-chart-axes.yml b/changelogs/unreleased/59651-remove-unnecessary-decimal-places-on-chart-axes.yml deleted file mode 100644 index fea34099135..00000000000 --- a/changelogs/unreleased/59651-remove-unnecessary-decimal-places-on-chart-axes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove unnecessary decimals on Metrics chart axis -merge_request: 29468 -author: -type: fixed diff --git a/changelogs/unreleased/59702-fix-notification-flags-for-ms-teams.yml b/changelogs/unreleased/59702-fix-notification-flags-for-ms-teams.yml deleted file mode 100644 index 14a8da95ed9..00000000000 --- a/changelogs/unreleased/59702-fix-notification-flags-for-ms-teams.yml +++ /dev/null @@ -1,5 +0,0 @@ ----
-title: Fix missing API notification flags for Microsoft Teams
-merge_request: 29824
-author: Seiji Suenaga
-type: fixed
diff --git a/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml b/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml deleted file mode 100644 index 7863f1f7bbe..00000000000 --- a/changelogs/unreleased/59987-move-sign-in-2fa-on-users-sign_in-above-intro-content-on-mobile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prioritize login form on mobile breakpoint -merge_request: 28360 -author: -type: changed diff --git a/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml b/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml deleted file mode 100644 index fdf80c660f7..00000000000 --- a/changelogs/unreleased/60034-default-web-ide-s-merge-request-checkbox-to-true.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Default MR checkbox to true in most cases -merge_request: !28665 -author: -type: changed diff --git a/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml b/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml deleted file mode 100644 index 6891a9ca83c..00000000000 --- a/changelogs/unreleased/60180-jira-service-fix-nil-on-find-call.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Resolved JIRA service: NoMethodError: undefined method ''find'' for nil:NilClass' -merge_request: 28206 -author: -type: fixed diff --git a/changelogs/unreleased/60250-remove-mr_push_options-flag.yml b/changelogs/unreleased/60250-remove-mr_push_options-flag.yml deleted file mode 100644 index b429d83dcc7..00000000000 --- a/changelogs/unreleased/60250-remove-mr_push_options-flag.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove the mr_push_options feature flag -merge_request: 28278 -author: -type: changed diff --git a/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml b/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml deleted file mode 100644 index 90b72ec05c7..00000000000 --- a/changelogs/unreleased/60303-replace-sidekiq-mtail-metrics.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replaces sidekiq mtail metrics with ruby instrumentation metrics -merge_request: 29215 -author: -type: changed diff --git a/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml b/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml deleted file mode 100644 index 83b7bd3433e..00000000000 --- a/changelogs/unreleased/60323-inline-validation-for-users-name-and-username-length.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update registration form to indicate invalid name or username length on input -merge_request: 28095 -author: Jiaan Louw -type: changed diff --git a/changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml b/changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml deleted file mode 100644 index a9b7aeb3024..00000000000 --- a/changelogs/unreleased/60379-remove-ci-preparing-state-feature-flag.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove ability for group clusters to be automatically configured on creation -merge_request: 27245 -author: -type: removed diff --git a/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml b/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml deleted file mode 100644 index 4274dc5918c..00000000000 --- a/changelogs/unreleased/60425-fix-500-when-accessing-charts-with-anonymous-user.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Fix 500 error when accessing charts with an anonymous user" -merge_request: 28091 -author: Diego Silva -type: fixed diff --git a/changelogs/unreleased/60617-allow-switching-from-gitlab-managed-to-unmanaged-clusters.yml b/changelogs/unreleased/60617-allow-switching-from-gitlab-managed-to-unmanaged-clusters.yml deleted file mode 100644 index 1127dde4fcf..00000000000 --- a/changelogs/unreleased/60617-allow-switching-from-gitlab-managed-to-unmanaged-clusters.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow switching clusters between managed and unmanaged -merge_request: 29322 -author: -type: added diff --git a/changelogs/unreleased/60617-enable-project-cluster-jit.yml b/changelogs/unreleased/60617-enable-project-cluster-jit.yml new file mode 100644 index 00000000000..b7d745d4385 --- /dev/null +++ b/changelogs/unreleased/60617-enable-project-cluster-jit.yml @@ -0,0 +1,5 @@ +--- +title: Enable just-in-time Kubernetes resource creation for project-level clusters +merge_request: 29515 +author: +type: changed diff --git a/changelogs/unreleased/60750-milestone-header.yml b/changelogs/unreleased/60750-milestone-header.yml deleted file mode 100644 index 62cfdaf6ea7..00000000000 --- a/changelogs/unreleased/60750-milestone-header.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix layout of group milestone header -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/60818_yamllint_project_root.yml b/changelogs/unreleased/60818_yamllint_project_root.yml deleted file mode 100644 index b34a50e6a9c..00000000000 --- a/changelogs/unreleased/60818_yamllint_project_root.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix yaml linting for project root *.yml files -merge_request: 27579 -author: Will Hall -type: fixed diff --git a/changelogs/unreleased/60819_yamllint_gitlabci.yml b/changelogs/unreleased/60819_yamllint_gitlabci.yml deleted file mode 100644 index aba3b206f7e..00000000000 --- a/changelogs/unreleased/60819_yamllint_gitlabci.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix yaml linting for GitLab CI inside project (.gitlab/ci) *.yml files and CI template files -merge_request: 27576 -author: Will Hall -type: fixed diff --git a/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml b/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml new file mode 100644 index 00000000000..237d0fd6aef --- /dev/null +++ b/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml @@ -0,0 +1,5 @@ +--- +title: Keep the empty folders in the tree +merge_request: 29196 +author: +type: fixed diff --git a/changelogs/unreleased/60879-fix-reports-timing-out.yml b/changelogs/unreleased/60879-fix-reports-timing-out.yml new file mode 100644 index 00000000000..845162fe10f --- /dev/null +++ b/changelogs/unreleased/60879-fix-reports-timing-out.yml @@ -0,0 +1,5 @@ +--- +title: Fix reports jobs timing out because of cache +merge_request: 29780 +author: +type: fixed diff --git a/changelogs/unreleased/609120-ref-link.yml b/changelogs/unreleased/609120-ref-link.yml deleted file mode 100644 index 97c93b7ff53..00000000000 --- a/changelogs/unreleased/609120-ref-link.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixes Ref link being displayed as raw HTML in the Pipelines page -merge_request: 28823 -author: -type: fixed diff --git a/changelogs/unreleased/60987-emoji-picker-popup.yml b/changelogs/unreleased/60987-emoji-picker-popup.yml deleted file mode 100644 index 3bccec8e164..00000000000 --- a/changelogs/unreleased/60987-emoji-picker-popup.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix emoji picker visibility issue -merge_request: 28984 -author: -type: fixed diff --git a/changelogs/unreleased/61024-update-resolved-icon.yml b/changelogs/unreleased/61024-update-resolved-icon.yml deleted file mode 100644 index 4a4de9eb13a..00000000000 --- a/changelogs/unreleased/61024-update-resolved-icon.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add check circle filled icon for resolved comments -merge_request: 28663 -author: -type: changed diff --git a/changelogs/unreleased/6104-ee-ce-difference.yml b/changelogs/unreleased/6104-ee-ce-difference.yml deleted file mode 100644 index 59d31daf0eb..00000000000 --- a/changelogs/unreleased/6104-ee-ce-difference.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Unified EE/CS differences in repository/show.html -merge_request: 13562 -author: -type: other diff --git a/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml b/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml deleted file mode 100644 index 53cc0a15417..00000000000 --- a/changelogs/unreleased/61045-charts-with-many-overlapping-series-display-incorrectly.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Eliminate color inconsistencies in metric graphs -merge_request: 29127 -author: -type: fixed diff --git a/changelogs/unreleased/61049-links-activity-stream.yml b/changelogs/unreleased/61049-links-activity-stream.yml deleted file mode 100644 index 3aac84adc31..00000000000 --- a/changelogs/unreleased/61049-links-activity-stream.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use blue for activity stream links; use monospace font for commit sha -merge_request: -author: -type: other diff --git a/changelogs/unreleased/61072-link-to-user-profile-not-distinguishable-on-latest-commit-widget.yml b/changelogs/unreleased/61072-link-to-user-profile-not-distinguishable-on-latest-commit-widget.yml deleted file mode 100644 index 5285ac767cc..00000000000 --- a/changelogs/unreleased/61072-link-to-user-profile-not-distinguishable-on-latest-commit-widget.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: User link styling for commits -merge_request: 29150 -author: -type: other diff --git a/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml b/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml deleted file mode 100644 index ed4cf0507c7..00000000000 --- a/changelogs/unreleased/61144-style-secondary-button-type-to-be-aligned-with-pattern-library.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bring secondary button styles up to design standard -merge_request: 27920 -author: -type: fixed diff --git a/changelogs/unreleased/61156-instance-level-cluster-pod-terminal-access.yml b/changelogs/unreleased/61156-instance-level-cluster-pod-terminal-access.yml new file mode 100644 index 00000000000..0b8d301352b --- /dev/null +++ b/changelogs/unreleased/61156-instance-level-cluster-pod-terminal-access.yml @@ -0,0 +1,5 @@ +--- +title: Enable terminals for instance and group clusters +merge_request: 28613 +author: +type: added diff --git a/changelogs/unreleased/61157-reviewer-roulette-shouldn-t-include-the-author-as-a-possibility.yml b/changelogs/unreleased/61157-reviewer-roulette-shouldn-t-include-the-author-as-a-possibility.yml deleted file mode 100644 index 8d1a38b3db5..00000000000 --- a/changelogs/unreleased/61157-reviewer-roulette-shouldn-t-include-the-author-as-a-possibility.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Excludes MR author from Review roulette -merge_request: 28886 -author: Jacopo Beschi @jacopo-beschi -type: fixed diff --git a/changelogs/unreleased/61313-fix-dropdown-searchbar.yml b/changelogs/unreleased/61313-fix-dropdown-searchbar.yml deleted file mode 100644 index ba191a89da2..00000000000 --- a/changelogs/unreleased/61313-fix-dropdown-searchbar.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix loading.. dropdown at search field -merge_request: 28275 -author: Pavel Chausov -type: fixed diff --git a/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml b/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml deleted file mode 100644 index 94666ac12ec..00000000000 --- a/changelogs/unreleased/61323-snippet-copy-icon-button-is-misaligned.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Snippet icon button is misaligned -merge_request: 28522 -author: -type: other diff --git a/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml b/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml deleted file mode 100644 index a7f5706058d..00000000000 --- a/changelogs/unreleased/61324-non-project-snippet-new-snippet-button-should-be-green-outline.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Give New Snippet button green outline -merge_request: 28559 -author: -type: other diff --git a/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml b/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml deleted file mode 100644 index e446459ffc8..00000000000 --- a/changelogs/unreleased/61339-Add-underline-to-attach-a-file.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add hover and focus to Attach a file -merge_request: 28682 -author: -type: fixed diff --git a/changelogs/unreleased/61393-emoji-button.yml b/changelogs/unreleased/61393-emoji-button.yml deleted file mode 100644 index 2bcfde308db..00000000000 --- a/changelogs/unreleased/61393-emoji-button.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change default color of award emoji button -merge_request: -author: -type: other diff --git a/changelogs/unreleased/61441.yml b/changelogs/unreleased/61441.yml deleted file mode 100644 index 2ad0c6f62d3..00000000000 --- a/changelogs/unreleased/61441.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow user to set primary email first when 2FA is required -merge_request: 28097 -author: Kartikey Tanna -type: fixed diff --git a/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml b/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml deleted file mode 100644 index 1a3a019e1f4..00000000000 --- a/changelogs/unreleased/61511-add-expand-collapse-to-project-operation-settings.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add expand/collapse to error tracking settings -merge_request: 28619 -author: -type: added diff --git a/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml b/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml deleted file mode 100644 index 718604c9ceb..00000000000 --- a/changelogs/unreleased/61565-merge-request-discussion-text-jumps-when-resolved.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Merge request discussion text jumps when resolved -merge_request: 28995 -author: -type: fixed diff --git a/changelogs/unreleased/61606-support-string-piwik-website-ids.yml b/changelogs/unreleased/61606-support-string-piwik-website-ids.yml deleted file mode 100644 index 5c525294132..00000000000 --- a/changelogs/unreleased/61606-support-string-piwik-website-ids.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Supports Matomo/Piwik string website ID (\"Protect Track ID\" plugin)" -merge_request: 28214 -author: DUVERGIER Claude -type: fixed
\ No newline at end of file diff --git a/changelogs/unreleased/61629-dependency-installation-error-on-fsevents-1-2-4-with-node-js-12.yml b/changelogs/unreleased/61629-dependency-installation-error-on-fsevents-1-2-4-with-node-js-12.yml deleted file mode 100644 index bbe43760953..00000000000 --- a/changelogs/unreleased/61629-dependency-installation-error-on-fsevents-1-2-4-with-node-js-12.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update indirect dependency fsevents from 1.2.4 to 1.2.9 -merge_request: 28220 -author: Takuya Noguchi -type: other diff --git a/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml b/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml deleted file mode 100644 index 9b4f13353f5..00000000000 --- a/changelogs/unreleased/61639-flaky-spec-issue-boards-labels-creates-project-label-spec-features-boards-sidebar_spec-rb-350.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix dropdown position when loading remote data -merge_request: 28526 -author: -type: fixed diff --git a/changelogs/unreleased/61697-add-project-id-to-le-common-name.yml b/changelogs/unreleased/61697-add-project-id-to-le-common-name.yml deleted file mode 100644 index 8ffa8d0a51a..00000000000 --- a/changelogs/unreleased/61697-add-project-id-to-le-common-name.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent common name collisions when requesting multiple Let's Encrypt certificates concurrently -merge_request: 28373 -author: -type: fixed diff --git a/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml b/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml deleted file mode 100644 index 25c83d24007..00000000000 --- a/changelogs/unreleased/61788-predefined-colours-dont-have-descriptive-labels.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds a text label to color pickers to improve accessibility. -merge_request: 28343 -author: Chris Toynbee -type: changed diff --git a/changelogs/unreleased/61795-fix-error-when-moving-issues.yml b/changelogs/unreleased/61795-fix-error-when-moving-issues.yml deleted file mode 100644 index 6812baa07c3..00000000000 --- a/changelogs/unreleased/61795-fix-error-when-moving-issues.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove unintended error message shown when moving issues -merge_request: 28317 -author: -type: fixed diff --git a/changelogs/unreleased/61821-tooltip-consistency.yml b/changelogs/unreleased/61821-tooltip-consistency.yml deleted file mode 100644 index 9b131907ebf..00000000000 --- a/changelogs/unreleased/61821-tooltip-consistency.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Resolve Tooltip Consistency -merge_request: 28839 -author: -type: fixed diff --git a/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml b/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml deleted file mode 100644 index 4d6464eed52..00000000000 --- a/changelogs/unreleased/61827-prevent-user-popover-icon-shrink.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent icons from shrinking in User popover when contents exceed container -merge_request: 28696 -author: -type: fixed diff --git a/changelogs/unreleased/61880-download-btn-group.yml b/changelogs/unreleased/61880-download-btn-group.yml deleted file mode 100644 index c1f6b2767c6..00000000000 --- a/changelogs/unreleased/61880-download-btn-group.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Group download buttons into a .btn-group -merge_request: -author: -type: other diff --git a/changelogs/unreleased/61914-fix-emojis-urls.yml b/changelogs/unreleased/61914-fix-emojis-urls.yml deleted file mode 100644 index 578edf4a063..00000000000 --- a/changelogs/unreleased/61914-fix-emojis-urls.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix emojis URLs -merge_request: 28371 -author: -type: fixed diff --git a/changelogs/unreleased/61928-remove-throttle-from-dirty-submit.yml b/changelogs/unreleased/61928-remove-throttle-from-dirty-submit.yml deleted file mode 100644 index f8ef5dbb53b..00000000000 --- a/changelogs/unreleased/61928-remove-throttle-from-dirty-submit.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix issue that causes "Save changes" button in project settings pages to be - enabled/disabled incorrectly when changes are made to the form -merge_request: 28377 -author: -type: fixed diff --git a/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml b/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml deleted file mode 100644 index 50b3efba0a5..00000000000 --- a/changelogs/unreleased/61960-translatable-strings-in-issue-closure-emails.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: I18n for issue closure reason in emails -merge_request: 28489 -author: Michał Zając -type: changed diff --git a/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml b/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml deleted file mode 100644 index 4d2f73ce2ff..00000000000 --- a/changelogs/unreleased/61988-collapse-icon-on-merge-request-diff-larger-than-profile-picture.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change collapse icon size to size of profile picture -merge_request: 28512 -author: -type: other diff --git a/changelogs/unreleased/61990-spinner.yml b/changelogs/unreleased/61990-spinner.yml deleted file mode 100644 index 27d35e56cc9..00000000000 --- a/changelogs/unreleased/61990-spinner.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Updates loading icon in commits page -merge_request: 28475 -author: -type: fixed diff --git a/changelogs/unreleased/62061-note-icon-color.yml b/changelogs/unreleased/62061-note-icon-color.yml deleted file mode 100644 index 5bfea1a9ed3..00000000000 --- a/changelogs/unreleased/62061-note-icon-color.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update icon color to match design system, pass accessibility -merge_request: 28498 -author: Jarek Ostrowski @jareko -type: fixed diff --git a/changelogs/unreleased/62091-remove-time-windows-flag.yml b/changelogs/unreleased/62091-remove-time-windows-flag.yml deleted file mode 100644 index c6c11328312..00000000000 --- a/changelogs/unreleased/62091-remove-time-windows-flag.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow users to specify a time range on metrics dashboard -merge_request: 28670 -author: -type: added diff --git a/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml b/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml deleted file mode 100644 index 3317d505924..00000000000 --- a/changelogs/unreleased/62092-missing-padding-next-to-time-windows-dropdown-on-metrics-dashboard.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added padding to time window dropdown in monitor dashboard -merge_request: 28897 -author: -type: fixed diff --git a/changelogs/unreleased/62107-fix-detail-page-header-height.yml b/changelogs/unreleased/62107-fix-detail-page-header-height.yml deleted file mode 100644 index 3723f323bcc..00000000000 --- a/changelogs/unreleased/62107-fix-detail-page-header-height.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix the height of the page headers on issues/merge request/snippets pages -merge_request: 28650 -author: Erik van der Gaag -type: fixed diff --git a/changelogs/unreleased/62116-performance-issue-502-errors-on-rendering-of-issues-with-heavy-markdown-contents.yml b/changelogs/unreleased/62116-performance-issue-502-errors-on-rendering-of-issues-with-heavy-markdown-contents.yml deleted file mode 100644 index 9596f487116..00000000000 --- a/changelogs/unreleased/62116-performance-issue-502-errors-on-rendering-of-issues-with-heavy-markdown-contents.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Fix performance issue with large Markdown content in issue or merge request - description -merge_request: 28597 -author: -type: performance diff --git a/changelogs/unreleased/62124-new-threaded-discussion-design.yml b/changelogs/unreleased/62124-new-threaded-discussion-design.yml deleted file mode 100644 index 6614e05be74..00000000000 --- a/changelogs/unreleased/62124-new-threaded-discussion-design.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Implement borderless discussion design with new reply field -merge_request: 28580 -author: -type: added diff --git a/changelogs/unreleased/62134-fix-non-wraping-project-description.yml b/changelogs/unreleased/62134-fix-non-wraping-project-description.yml deleted file mode 100644 index 8c734c526fe..00000000000 --- a/changelogs/unreleased/62134-fix-non-wraping-project-description.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Correctly word-wrapping project descriptions with very long words -merge_request: 28695 -author: Erik van der Gaag -type: fixed diff --git a/changelogs/unreleased/62144-fix-option-dropdown-button-size.yml b/changelogs/unreleased/62144-fix-option-dropdown-button-size.yml deleted file mode 100644 index 86d8f4536f9..00000000000 --- a/changelogs/unreleased/62144-fix-option-dropdown-button-size.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix inconsistent option dropdown button height to match adjacent button -merge_request: 29096 -author: -type: fixed diff --git a/changelogs/unreleased/62154-fe-create-fix-long-branch-name-in-dropdown.yml b/changelogs/unreleased/62154-fe-create-fix-long-branch-name-in-dropdown.yml deleted file mode 100644 index 1d951c1dc24..00000000000 --- a/changelogs/unreleased/62154-fe-create-fix-long-branch-name-in-dropdown.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Add support to view entirety of long branch name in dropdown instead of it - being cut off -merge_request: 29069 -author: -type: fixed diff --git a/changelogs/unreleased/62227-webkit-icon-overlap.yml b/changelogs/unreleased/62227-webkit-icon-overlap.yml deleted file mode 100644 index 47d7583f4c2..00000000000 --- a/changelogs/unreleased/62227-webkit-icon-overlap.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add style to disable webkit icons for search inputs -merge_request: 28833 -author: Jarek Ostrowski @jareko -type: fixed diff --git a/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml b/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml deleted file mode 100644 index c01106a15ec..00000000000 --- a/changelogs/unreleased/62253-add-kubernetes-logs-to-monitoring-ui.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Kubernetes logs to Admin Logs UI -merge_request: 28685 -author: -type: added diff --git a/changelogs/unreleased/62300-target-area-for-dropdown-list-items-is-too-small-on-metrics-dashboard.yml b/changelogs/unreleased/62300-target-area-for-dropdown-list-items-is-too-small-on-metrics-dashboard.yml deleted file mode 100644 index 7944e6faa27..00000000000 --- a/changelogs/unreleased/62300-target-area-for-dropdown-list-items-is-too-small-on-metrics-dashboard.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Enlarge metrics time-window dropdown links -merge_request: 29458 -author: -type: fixed diff --git a/changelogs/unreleased/62408-dropdown-truncate.yml b/changelogs/unreleased/62408-dropdown-truncate.yml deleted file mode 100644 index 7204016efdf..00000000000 --- a/changelogs/unreleased/62408-dropdown-truncate.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix job name in graph dropdown overflowing -merge_request: 28824 -author: -type: fixed diff --git a/changelogs/unreleased/62418-project-default-git-depth.yml b/changelogs/unreleased/62418-project-default-git-depth.yml deleted file mode 100644 index b5647cd0859..00000000000 --- a/changelogs/unreleased/62418-project-default-git-depth.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add project level git depth CI/CD setting -merge_request: 28919 -author: -type: added diff --git a/changelogs/unreleased/62432-fix-participants-wrapping.yml b/changelogs/unreleased/62432-fix-participants-wrapping.yml deleted file mode 100644 index a7e4bd372de..00000000000 --- a/changelogs/unreleased/62432-fix-participants-wrapping.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix participants list wrapping -merge_request: 28873 -author: -type: fixed diff --git a/changelogs/unreleased/62485-label-weights.yml b/changelogs/unreleased/62485-label-weights.yml deleted file mode 100644 index 354b18be11e..00000000000 --- a/changelogs/unreleased/62485-label-weights.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Give labels consistent weight -merge_request: 28895 -author: -type: fixed diff --git a/changelogs/unreleased/62487-external-policy-desc.yml b/changelogs/unreleased/62487-external-policy-desc.yml deleted file mode 100644 index 2e787b89db1..00000000000 --- a/changelogs/unreleased/62487-external-policy-desc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move text under p tag -merge_request: 28901 -author: -type: fixed diff --git a/changelogs/unreleased/62656-adjusted-dropdown-styles.yml b/changelogs/unreleased/62656-adjusted-dropdown-styles.yml deleted file mode 100644 index 36f14ae2741..00000000000 --- a/changelogs/unreleased/62656-adjusted-dropdown-styles.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "changed the styles on `Add List` dropdown to look more like the EE vesion" -merge_request: 29338 -author: Michel Engelen -type: changed diff --git a/changelogs/unreleased/62684-add-index-public-email-on-users.yml b/changelogs/unreleased/62684-add-index-public-email-on-users.yml deleted file mode 100644 index 56b5f91da21..00000000000 --- a/changelogs/unreleased/62684-add-index-public-email-on-users.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add index on public_email for users -merge_request: 29430 -author: -type: performance diff --git a/changelogs/unreleased/62685-add-index-invite-email-to-members.yml b/changelogs/unreleased/62685-add-index-invite-email-to-members.yml deleted file mode 100644 index 80bb05e1709..00000000000 --- a/changelogs/unreleased/62685-add-index-invite-email-to-members.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add index on invite_email for members -merge_request: 29768 -author: -type: performance diff --git a/changelogs/unreleased/62788-clean-up-pagination.yml b/changelogs/unreleased/62788-clean-up-pagination.yml deleted file mode 100644 index 05e3a4527b8..00000000000 --- a/changelogs/unreleased/62788-clean-up-pagination.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Moves the table pagination shared component -merge_request: -author: -type: other diff --git a/changelogs/unreleased/62788-graphql-pagination.yml b/changelogs/unreleased/62788-graphql-pagination.yml deleted file mode 100644 index a7bc317a08f..00000000000 --- a/changelogs/unreleased/62788-graphql-pagination.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds pagination component for graphql api -merge_request: 29277 -author: -type: added diff --git a/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml b/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml deleted file mode 100644 index fca92a0d4dc..00000000000 --- a/changelogs/unreleased/62847-url-for-the-next-request-with-pagination-is-missing-port.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Include the port in the URLs of the API Link headers -merge_request: 29267 -author: -type: fixed diff --git a/changelogs/unreleased/62910-task-completion-status-gql-pderichs.yml b/changelogs/unreleased/62910-task-completion-status-gql-pderichs.yml deleted file mode 100644 index dcbfa7c185d..00000000000 --- a/changelogs/unreleased/62910-task-completion-status-gql-pderichs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make task completion status available via GraphQL -merge_request: -author: -type: added diff --git a/changelogs/unreleased/62938-wcag-aa-edited-text-color.yml b/changelogs/unreleased/62938-wcag-aa-edited-text-color.yml new file mode 100644 index 00000000000..6652e495869 --- /dev/null +++ b/changelogs/unreleased/62938-wcag-aa-edited-text-color.yml @@ -0,0 +1,5 @@ +--- +title: Use darker gray color for system note metadata and edited text +merge_request: 30054 +author: +type: other diff --git a/changelogs/unreleased/62966-embed-zoom-call-in-issue-mvc.yml b/changelogs/unreleased/62966-embed-zoom-call-in-issue-mvc.yml deleted file mode 100644 index a41873f671e..00000000000 --- a/changelogs/unreleased/62966-embed-zoom-call-in-issue-mvc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Join meeting button to issues with Zoom links -merge_request: 29454 -author: -type: added diff --git a/changelogs/unreleased/62974-follow-up-from-wip-align-merge-request-icons-and-text.yml b/changelogs/unreleased/62974-follow-up-from-wip-align-merge-request-icons-and-text.yml deleted file mode 100644 index 811986e6857..00000000000 --- a/changelogs/unreleased/62974-follow-up-from-wip-align-merge-request-icons-and-text.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Make margin between buttons consistent' -merge_request: 29378 -author: -type: other diff --git a/changelogs/unreleased/63079-exclude-k8s-namespaces-with-no-service-account-token.yml b/changelogs/unreleased/63079-exclude-k8s-namespaces-with-no-service-account-token.yml deleted file mode 100644 index 9dc99c8a62f..00000000000 --- a/changelogs/unreleased/63079-exclude-k8s-namespaces-with-no-service-account-token.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Ensure a Kubernetes namespace is not used for deployments if there is no service - account token associated with it -merge_request: 29643 -author: -type: fixed diff --git a/changelogs/unreleased/63247-add-conf-toast-and-link.yml b/changelogs/unreleased/63247-add-conf-toast-and-link.yml new file mode 100644 index 00000000000..915cc20dcc8 --- /dev/null +++ b/changelogs/unreleased/63247-add-conf-toast-and-link.yml @@ -0,0 +1,5 @@ +--- +title: Include a link back to the MR for Visual Review feedback form +merge_request: 29719 +author: +type: changed diff --git a/changelogs/unreleased/63417-add-missing-class.yml b/changelogs/unreleased/63417-add-missing-class.yml deleted file mode 100644 index 3030f5c57e4..00000000000 --- a/changelogs/unreleased/63417-add-missing-class.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Indent collapsible sections -merge_request: 29804 -author: -type: other diff --git a/changelogs/unreleased/63559-remove-avatar-from-sign-in.yml b/changelogs/unreleased/63559-remove-avatar-from-sign-in.yml new file mode 100644 index 00000000000..3f7a8e19de5 --- /dev/null +++ b/changelogs/unreleased/63559-remove-avatar-from-sign-in.yml @@ -0,0 +1,5 @@ +--- +title: Resolve Avatar in Please sign in pattern too large +merge_request: 29944 +author: +type: fixed diff --git a/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml b/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml new file mode 100644 index 00000000000..08c415f4a1c --- /dev/null +++ b/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml @@ -0,0 +1,5 @@ +--- +title: Fix runner tags search dropdown being empty when there are tags +merge_request: 29985 +author: +type: fixed diff --git a/changelogs/unreleased/8723-geo-remove-gitlab-lfstoken-legacyredisdevisetoken-implementation-and-usage-geo.yml b/changelogs/unreleased/8723-geo-remove-gitlab-lfstoken-legacyredisdevisetoken-implementation-and-usage-geo.yml deleted file mode 100644 index 173c7d9383e..00000000000 --- a/changelogs/unreleased/8723-geo-remove-gitlab-lfstoken-legacyredisdevisetoken-implementation-and-usage-geo.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Geo: Remove Gitlab::LfsToken::LegacyRedisDeviseToken implementation and usage' -merge_request: 28546 -author: -type: changed diff --git a/changelogs/unreleased/9121-sort-relative-position.yml b/changelogs/unreleased/9121-sort-relative-position.yml deleted file mode 100644 index adc9e87e5bb..00000000000 --- a/changelogs/unreleased/9121-sort-relative-position.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow issue list to be sorted by relative order -merge_request: 28566 -author: -type: added diff --git a/changelogs/unreleased/9186-implement-atmtwps-state-to-mr-widget.yml b/changelogs/unreleased/9186-implement-atmtwps-state-to-mr-widget.yml deleted file mode 100644 index 2c1d56f197e..00000000000 --- a/changelogs/unreleased/9186-implement-atmtwps-state-to-mr-widget.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update the merge request widget's "Merge" button to support merge trains -merge_request: 27594 -author: -type: added diff --git a/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml b/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml deleted file mode 100644 index 0694e1462cf..00000000000 --- a/changelogs/unreleased/9578-adjust-milestone-completion-rate.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adjust milestone completion rate to be based on issues count. -merge_request: 28777 -author: -type: changed diff --git a/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml b/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml deleted file mode 100644 index 821e5d70dca..00000000000 --- a/changelogs/unreleased/9978-moved-code-differences-from-EE-to-CE.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Moved EE/CE code differences for `app/assets/javascripts/gl_dropdown.js` into CE" -merge_request: 28711 -author: Michel Engelen -type: other diff --git a/changelogs/unreleased/abstract-auto-merge.yml b/changelogs/unreleased/abstract-auto-merge.yml deleted file mode 100644 index d3069a3e500..00000000000 --- a/changelogs/unreleased/abstract-auto-merge.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Refactor and abstract Auto Merge Processes -merge_request: 28595 -author: -type: other diff --git a/changelogs/unreleased/ac-63020-typeerror-nil-can-t-be-coerced-into-integer.yml b/changelogs/unreleased/ac-63020-typeerror-nil-can-t-be-coerced-into-integer.yml deleted file mode 100644 index 51ac2358fba..00000000000 --- a/changelogs/unreleased/ac-63020-typeerror-nil-can-t-be-coerced-into-integer.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix nil coercion updating storage size on project statistics -merge_request: 29425 -author: -type: fixed diff --git a/changelogs/unreleased/ac-graphql-stats.yml b/changelogs/unreleased/ac-graphql-stats.yml deleted file mode 100644 index 8837dce4d89..00000000000 --- a/changelogs/unreleased/ac-graphql-stats.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Namespace and ProjectStatistics to GraphQL API -merge_request: 28277 -author: -type: added diff --git a/changelogs/unreleased/ac-graphql-wikisize.yml b/changelogs/unreleased/ac-graphql-wikisize.yml deleted file mode 100644 index be9c347ec21..00000000000 --- a/changelogs/unreleased/ac-graphql-wikisize.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose wiki_size on GraphQL API -merge_request: 29123 -author: -type: added diff --git a/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml b/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml deleted file mode 100644 index bd005206d4e..00000000000 --- a/changelogs/unreleased/ac-namespaces-stats-no-coalesce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Forbid NULL in project_statistics.packages_size -merge_request: 28400 -author: -type: other diff --git a/changelogs/unreleased/add-allow_failure-to-job-api.yml b/changelogs/unreleased/add-allow_failure-to-job-api.yml deleted file mode 100644 index 5dd2b9708c5..00000000000 --- a/changelogs/unreleased/add-allow_failure-to-job-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add allow_failure attribute to Job API -merge_request: 28406 -author: -type: added diff --git a/changelogs/unreleased/add-branch-to-project-search-api.yml b/changelogs/unreleased/add-branch-to-project-search-api.yml deleted file mode 100644 index 74cff94ab76..00000000000 --- a/changelogs/unreleased/add-branch-to-project-search-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added ref querystring parameter to project search API to allow searching on branches/tags other than the default -merge_request: 28069 -author: Lee Tickett -type: added diff --git a/changelogs/unreleased/add-constraint-for-milestone-dates.yml b/changelogs/unreleased/add-constraint-for-milestone-dates.yml deleted file mode 100644 index 485149cf62e..00000000000 --- a/changelogs/unreleased/add-constraint-for-milestone-dates.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Limit milestone dates to before year 9999 -merge_request: 28742 -author: Luke Picciau -type: fixed diff --git a/changelogs/unreleased/add-lfs-blob-ids-to-tree-type.yml b/changelogs/unreleased/add-lfs-blob-ids-to-tree-type.yml deleted file mode 100644 index 14a5ef1cef3..00000000000 --- a/changelogs/unreleased/add-lfs-blob-ids-to-tree-type.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add LFS oid to GraphQL blob type -merge_request: 28666 -author: -type: added diff --git a/changelogs/unreleased/add-warning-to-backup-rake-task.yml b/changelogs/unreleased/add-warning-to-backup-rake-task.yml deleted file mode 100644 index 7ddeae3f9fd..00000000000 --- a/changelogs/unreleased/add-warning-to-backup-rake-task.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add warning that gitlab-secrets isn't included in backup -merge_request: -author: -type: other diff --git a/changelogs/unreleased/add-wiki-size-to-statistics.yml b/changelogs/unreleased/add-wiki-size-to-statistics.yml deleted file mode 100644 index 85b6d7a1774..00000000000 --- a/changelogs/unreleased/add-wiki-size-to-statistics.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add wiki size to project statistics -merge_request: 25321 -author: Peter Marko -type: added diff --git a/changelogs/unreleased/allow-emoji-in-references.yml b/changelogs/unreleased/allow-emoji-in-references.yml deleted file mode 100644 index 3116b346c00..00000000000 --- a/changelogs/unreleased/allow-emoji-in-references.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow references to labels and milestones to contain emoji -merge_request: 29284 -author: -type: changed diff --git a/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml b/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml new file mode 100644 index 00000000000..acd944ea684 --- /dev/null +++ b/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml @@ -0,0 +1,5 @@ +--- +title: Always allow access to health endpoints from localhost in dev +merge_request: 29930 +author: +type: other diff --git a/changelogs/unreleased/always-display-environment-selector.yml b/changelogs/unreleased/always-display-environment-selector.yml new file mode 100644 index 00000000000..7a55e8f3e5d --- /dev/null +++ b/changelogs/unreleased/always-display-environment-selector.yml @@ -0,0 +1,5 @@ +--- +title: Fix broken environment selector and always display it on monitoring dashboard +merge_request: 29705 +author: +type: fixed diff --git a/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml b/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml deleted file mode 100644 index d60dd65be8a..00000000000 --- a/changelogs/unreleased/always-show-pipelines-must-succeed-checkbox.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Always show "Pipelines must succeed" checkbox -merge_request: 28651 -author: -type: fixed diff --git a/changelogs/unreleased/antonyliu-i18n-user-profile.yml b/changelogs/unreleased/antonyliu-i18n-user-profile.yml deleted file mode 100644 index f9065ee5697..00000000000 --- a/changelogs/unreleased/antonyliu-i18n-user-profile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'i18n: externalize strings from user profile settings' -merge_request: 28088 -author: Antony Liu -type: other diff --git a/changelogs/unreleased/api_make_protected_boolean_type.yml b/changelogs/unreleased/api_make_protected_boolean_type.yml deleted file mode 100644 index 765edea6427..00000000000 --- a/changelogs/unreleased/api_make_protected_boolean_type.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: change protected attribute type to Boolean' -merge_request: 28766 -author: -type: other diff --git a/changelogs/unreleased/api_masked_variables.yml b/changelogs/unreleased/api_masked_variables.yml deleted file mode 100644 index 3605339cb91..00000000000 --- a/changelogs/unreleased/api_masked_variables.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: Allow to get and set "masked" attribute for variables' -merge_request: 28381 -author: Mathieu Parent -type: added diff --git a/changelogs/unreleased/asciidoc-include-directive.yml b/changelogs/unreleased/asciidoc-include-directive.yml deleted file mode 100644 index 58fe3666727..00000000000 --- a/changelogs/unreleased/asciidoc-include-directive.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add support for AsciiDoc include directive -merge_request: 28417 -author: "Jakub Jirutka & Guillaume Grossetie" -type: added diff --git a/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml b/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml deleted file mode 100644 index 9ba55719bdf..00000000000 --- a/changelogs/unreleased/auto-devops-kubernestes-bump1-11-10.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bumps Kubernetes in Auto DevOps to 1.11.10 -merge_request: 28525 -author: -type: other diff --git a/changelogs/unreleased/backport-schema-changes.yml b/changelogs/unreleased/backport-schema-changes.yml deleted file mode 100644 index 58f3ca72b0b..00000000000 --- a/changelogs/unreleased/backport-schema-changes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Backport the EE schema and migrations to CE -merge_request: 26940 -author: Yorick Peterse -type: other diff --git a/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml b/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml deleted file mode 100644 index 2b5a3592775..00000000000 --- a/changelogs/unreleased/backstage-gb-improve-jobs-controller-performance.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve performance of jobs controller -merge_request: 28093 -author: -type: performance diff --git a/changelogs/unreleased/bug-63162-duplicate_path_in_links.yml b/changelogs/unreleased/bug-63162-duplicate_path_in_links.yml deleted file mode 100644 index d3f246492fb..00000000000 --- a/changelogs/unreleased/bug-63162-duplicate_path_in_links.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed 'diff version changes' link not working -merge_request: 29825 -author: -type: fixed diff --git a/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml b/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml deleted file mode 100644 index ecfbc97a8c5..00000000000 --- a/changelogs/unreleased/bump-auto-devops-helm-2-14-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Bump Helm version in Auto-DevOps.gitlab-ci.yml to 2.14.0 -merge_request: 28527 -author: -type: other diff --git a/changelogs/unreleased/bvl-comments-graphql.yml b/changelogs/unreleased/bvl-comments-graphql.yml deleted file mode 100644 index 9f510a910a3..00000000000 --- a/changelogs/unreleased/bvl-comments-graphql.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Expose notes and discussions in GraphQL -merge_request: 29212 -author: -type: added diff --git a/changelogs/unreleased/bvl-graphql-multiplex.yml b/changelogs/unreleased/bvl-graphql-multiplex.yml deleted file mode 100644 index 56d39e447a5..00000000000 --- a/changelogs/unreleased/bvl-graphql-multiplex.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Support multiplex GraphQL queries -merge_request: 28273 -author: -type: added diff --git a/changelogs/unreleased/bvl-use-global-ids-graphql.yml b/changelogs/unreleased/bvl-use-global-ids-graphql.yml deleted file mode 100644 index 34cb65e6001..00000000000 --- a/changelogs/unreleased/bvl-use-global-ids-graphql.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use global IDs when exposing GraphQL resources -merge_request: 29080 -author: -type: added diff --git a/changelogs/unreleased/cancel-auto-merge-when-branch-is-changed.yml b/changelogs/unreleased/cancel-auto-merge-when-branch-is-changed.yml deleted file mode 100644 index c3c6e4322a2..00000000000 --- a/changelogs/unreleased/cancel-auto-merge-when-branch-is-changed.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Cancel Auto Merge when target branch is changed -merge_request: 29416 -author: -type: fixed diff --git a/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml b/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml deleted file mode 100644 index d38046ebcbf..00000000000 --- a/changelogs/unreleased/cancel-auto-merge-when-merge-request-is-closed.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Cancel auto merge when merge request is closed -merge_request: 28782 -author: -type: fixed diff --git a/changelogs/unreleased/ce-57402-add-issues-statistics-api-endpoints.yml b/changelogs/unreleased/ce-57402-add-issues-statistics-api-endpoints.yml deleted file mode 100644 index a626193dc27..00000000000 --- a/changelogs/unreleased/ce-57402-add-issues-statistics-api-endpoints.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add issues_statistics api endpoints and extend issues search api -merge_request: 27366 -author: -type: added diff --git a/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml b/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml deleted file mode 100644 index a795e33b00d..00000000000 --- a/changelogs/unreleased/ce-jej-fix-git-http-with-sso-enforcement.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid setting Gitlab::Session on sessionless requests and Git HTTP -merge_request: 29146 -author: -type: fixed diff --git a/changelogs/unreleased/ce-quick-fix-58727-collapsed-sidebar-flyout-menu-items-don-t-appear-in-1200px-screen-size.yml b/changelogs/unreleased/ce-quick-fix-58727-collapsed-sidebar-flyout-menu-items-don-t-appear-in-1200px-screen-size.yml deleted file mode 100644 index 332105bb269..00000000000 --- a/changelogs/unreleased/ce-quick-fix-58727-collapsed-sidebar-flyout-menu-items-don-t-appear-in-1200px-screen-size.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix flyout nav on small viewports -merge_request: 25998 -author: -type: fixed diff --git a/changelogs/unreleased/chore-remove-circuit-breaker-api.yml b/changelogs/unreleased/chore-remove-circuit-breaker-api.yml deleted file mode 100644 index f9532be04c8..00000000000 --- a/changelogs/unreleased/chore-remove-circuit-breaker-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove the circuit breaker API -merge_request: 28669 -author: -type: removed diff --git a/changelogs/unreleased/ci-variable-conjunction.yml b/changelogs/unreleased/ci-variable-conjunction.yml deleted file mode 100644 index 839c4285f3a..00000000000 --- a/changelogs/unreleased/ci-variable-conjunction.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add support for && and || to CI Pipeline Expressions. Change CI variable expression matching for Lexeme::Pattern to eagerly return tokens. -merge_request: 27925 -author: Martin Manelli -type: added diff --git a/changelogs/unreleased/container-registry-api-perms-58271.yml b/changelogs/unreleased/container-registry-api-perms-58271.yml deleted file mode 100644 index 0d1036a7788..00000000000 --- a/changelogs/unreleased/container-registry-api-perms-58271.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow developer role to delete docker tags via container registry API -merge_request: 29512 -author: -type: fixed diff --git a/changelogs/unreleased/copy-button-in-modals.yml b/changelogs/unreleased/copy-button-in-modals.yml deleted file mode 100644 index bc18eb9ab26..00000000000 --- a/changelogs/unreleased/copy-button-in-modals.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add a New Copy Button That Works in Modals -merge_request: 28676 -author: -type: added diff --git a/changelogs/unreleased/dhiraj-fix-missing-deployment-rockets-in-monitoring-dashboard.yml b/changelogs/unreleased/dhiraj-fix-missing-deployment-rockets-in-monitoring-dashboard.yml deleted file mode 100644 index 12a21e818b4..00000000000 --- a/changelogs/unreleased/dhiraj-fix-missing-deployment-rockets-in-monitoring-dashboard.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix missing deployment rockets in monitor dashboard -merge_request: 29574 -author: -type: fixed diff --git a/changelogs/unreleased/diff-suggestions-popover.yml b/changelogs/unreleased/diff-suggestions-popover.yml deleted file mode 100644 index fdb94e6e2a7..00000000000 --- a/changelogs/unreleased/diff-suggestions-popover.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Added diff suggestion feature discovery popover -merge_request: -author: -type: added diff --git a/changelogs/unreleased/diff-whitespace-setting-changes.yml b/changelogs/unreleased/diff-whitespace-setting-changes.yml deleted file mode 100644 index 640e9e589df..00000000000 --- a/changelogs/unreleased/diff-whitespace-setting-changes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed show whitespace button not refetching diff content -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/display-junit-classname-in-modal.yml b/changelogs/unreleased/display-junit-classname-in-modal.yml deleted file mode 100644 index c5140456e4e..00000000000 --- a/changelogs/unreleased/display-junit-classname-in-modal.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Display classname JUnit attribute in report modal -merge_request: 28376 -author: -type: added diff --git a/changelogs/unreleased/dm-http-hostname-override.yml b/changelogs/unreleased/dm-http-hostname-override.yml deleted file mode 100644 index f84f36a0010..00000000000 --- a/changelogs/unreleased/dm-http-hostname-override.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Protect Gitlab::HTTP against DNS rebinding attack -merge_request: -author: -type: security diff --git a/changelogs/unreleased/docs-add-chatops-request-doc.yml b/changelogs/unreleased/docs-add-chatops-request-doc.yml deleted file mode 100644 index 85ba86a73af..00000000000 --- a/changelogs/unreleased/docs-add-chatops-request-doc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add section to dev docs on accessing chatops -merge_request: 28623 -author: -type: other diff --git a/changelogs/unreleased/dz-patch-58.yml b/changelogs/unreleased/dz-patch-58.yml deleted file mode 100644 index 97ceadd303d..00000000000 --- a/changelogs/unreleased/dz-patch-58.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Replace Oxygen-Sans font with Noto Sans -merge_request: 28322 -author: -type: changed diff --git a/changelogs/unreleased/dz-remove-deprecated-user-routes.yml b/changelogs/unreleased/dz-remove-deprecated-user-routes.yml new file mode 100644 index 00000000000..92c2e39dd20 --- /dev/null +++ b/changelogs/unreleased/dz-remove-deprecated-user-routes.yml @@ -0,0 +1,5 @@ +--- +title: Remove depreated /u/:username routing +merge_request: 30044 +author: +type: removed diff --git a/changelogs/unreleased/dz-scope-project-routes.yml b/changelogs/unreleased/dz-scope-project-routes.yml deleted file mode 100644 index 66eb5d928f0..00000000000 --- a/changelogs/unreleased/dz-scope-project-routes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move some project routes under /-/ scope -merge_request: 28435 -author: -type: changed diff --git a/changelogs/unreleased/ee-11040-added-conditional-rendering.yml b/changelogs/unreleased/ee-11040-added-conditional-rendering.yml deleted file mode 100644 index 7b06e43830f..00000000000 --- a/changelogs/unreleased/ee-11040-added-conditional-rendering.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Added conditional rendering to `app/views/search/_form.html.haml` for CE/EE code base consistency" -merge_request: 28883 -author: Michel Engelen -type: other diff --git a/changelogs/unreleased/ensure_namespace.yml b/changelogs/unreleased/ensure_namespace.yml deleted file mode 100644 index ce2a615af1f..00000000000 --- a/changelogs/unreleased/ensure_namespace.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: AutoDevops function ensure_namespace() now explicitly tests the namespace -merge_request: 29567 -author: Jack Lei -type: fixed diff --git a/changelogs/unreleased/error-pipelines-for-blocked-users.yml b/changelogs/unreleased/error-pipelines-for-blocked-users.yml deleted file mode 100644 index 3ace28b6cfd..00000000000 --- a/changelogs/unreleased/error-pipelines-for-blocked-users.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: preventing blocked users and their PipelineSchdules from creating new Pipelines -merge_request: 27318 -author: -type: fixed diff --git a/changelogs/unreleased/expose-project-git-depth-via-api.yml b/changelogs/unreleased/expose-project-git-depth-via-api.yml deleted file mode 100644 index e9d158fda16..00000000000 --- a/changelogs/unreleased/expose-project-git-depth-via-api.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Get and edit ci_default_git_depth via project API -merge_request: 29353 -author: -type: added diff --git a/changelogs/unreleased/fe-fix-gl-dropdown-scrolling-to-top.yml b/changelogs/unreleased/fe-fix-gl-dropdown-scrolling-to-top.yml deleted file mode 100644 index 4125b4241e6..00000000000 --- a/changelogs/unreleased/fe-fix-gl-dropdown-scrolling-to-top.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix scrolling to top on assignee change -merge_request: 29500 -author: -type: fixed diff --git a/changelogs/unreleased/fe-issue-reorder.yml b/changelogs/unreleased/fe-issue-reorder.yml new file mode 100644 index 00000000000..aca334b6149 --- /dev/null +++ b/changelogs/unreleased/fe-issue-reorder.yml @@ -0,0 +1,5 @@ +--- +title: Bring Manual Ordering on Issue List +merge_request: 29410 +author: +type: added diff --git a/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml b/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml deleted file mode 100644 index 443fff92f55..00000000000 --- a/changelogs/unreleased/feature-gb-use-gitlabktl-to-build-serverless-applications.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use to 'gitlabktl' build serverless applications -merge_request: 29258 -author: -type: added diff --git a/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml b/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml deleted file mode 100644 index 0abe777fb69..00000000000 --- a/changelogs/unreleased/feature-require-2fa-for-all-entities-in-group.yml +++ /dev/null @@ -1,4 +0,0 @@ -title: Apply the group setting "require 2FA" across all subgroup members as well when changing the group setting -merge_request: 24965 -author: rroger -type: changed diff --git a/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml b/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml deleted file mode 100644 index 46fa90ccda0..00000000000 --- a/changelogs/unreleased/fix-allow-lower-case-issue-ids.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow lowercase prefix for Youtrack issue ids -merge_request: 29057 -author: Matthias Baur -type: fixed diff --git a/changelogs/unreleased/fix-autodevops-postgres-versioning.yml b/changelogs/unreleased/fix-autodevops-postgres-versioning.yml deleted file mode 100644 index 8ddc70f6314..00000000000 --- a/changelogs/unreleased/fix-autodevops-postgres-versioning.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed ignored postgres version that occurs after the first autodevops deploy when specifying custom $POSTGRES_VERSION -merge_request: 28735 -author: Brandon Dimcheff -type: fixed diff --git a/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml b/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml deleted file mode 100644 index 63f134808e3..00000000000 --- a/changelogs/unreleased/fix-db-migrate-is-failed-on-mysql8.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix. `db:migrate` is failed on MySQL 8 -merge_request: 28351 -author: sue445 -type: fixed diff --git a/changelogs/unreleased/fix-diverged-branch-locals.yml b/changelogs/unreleased/fix-diverged-branch-locals.yml deleted file mode 100644 index 719d669fad3..00000000000 --- a/changelogs/unreleased/fix-diverged-branch-locals.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix diverged branch locals -merge_request: 29508 -author: -type: fixed diff --git a/changelogs/unreleased/fix-flyout-navs.yml b/changelogs/unreleased/fix-flyout-navs.yml deleted file mode 100644 index c21f1037f09..00000000000 --- a/changelogs/unreleased/fix-flyout-navs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix sidebar flyout navigation -merge_request: 29571 -author: -type: fixed diff --git a/changelogs/unreleased/fix-format-date-safari-ff.yml b/changelogs/unreleased/fix-format-date-safari-ff.yml deleted file mode 100644 index e71ea2867f3..00000000000 --- a/changelogs/unreleased/fix-format-date-safari-ff.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Throw an error when formatDate's input is invalid -merge_request: 28713 -author: -type: fixed diff --git a/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml b/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml deleted file mode 100644 index 88656b7ef4c..00000000000 --- a/changelogs/unreleased/fix-gb-fix-serverless-apps-deployment-template.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix serverless apps deployments by bumping 'tm' version -merge_request: 29254 -author: -type: fixed diff --git a/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml b/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml deleted file mode 100644 index f51ec273a57..00000000000 --- a/changelogs/unreleased/fix-gb-remove-serverless-app-build-policies-from-template.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove build policies from serverless app template -merge_request: 29253 -author: -type: fixed diff --git a/changelogs/unreleased/fix-import-member-access.yml b/changelogs/unreleased/fix-import-member-access.yml deleted file mode 100644 index 5dc4ae738f8..00000000000 --- a/changelogs/unreleased/fix-import-member-access.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix issue importing members with owner access -merge_request: 28636 -author: -type: fixed diff --git a/changelogs/unreleased/fix-import-param-ordering.yml b/changelogs/unreleased/fix-import-param-ordering.yml deleted file mode 100644 index 47e32cda977..00000000000 --- a/changelogs/unreleased/fix-import-param-ordering.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix order dependency with user params during imports -merge_request: 28719 -author: -type: fixed diff --git a/changelogs/unreleased/fix-issue-mr-badge.yml b/changelogs/unreleased/fix-issue-mr-badge.yml deleted file mode 100644 index e777f52f173..00000000000 --- a/changelogs/unreleased/fix-issue-mr-badge.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use grid and correct border radius for status badge -merge_request: -author: -type: other diff --git a/changelogs/unreleased/fix-jupyter-git-v3.yml b/changelogs/unreleased/fix-jupyter-git-v3.yml new file mode 100644 index 00000000000..8aaaaf249fb --- /dev/null +++ b/changelogs/unreleased/fix-jupyter-git-v3.yml @@ -0,0 +1,5 @@ +--- +title: Fix Jupyter-Git integration +merge_request: 30020 +author: Amit Rathi +type: fixed diff --git a/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml b/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml deleted file mode 100644 index 1041943f9c4..00000000000 --- a/changelogs/unreleased/fix-milestone-references-with-escaped-html-entities.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix milestone references containing &, <, or > -merge_request: 28667 -author: -type: fixed diff --git a/changelogs/unreleased/fix-pipeline-schedule-edge-case.yml b/changelogs/unreleased/fix-pipeline-schedule-edge-case.yml new file mode 100644 index 00000000000..2b7e3611567 --- /dev/null +++ b/changelogs/unreleased/fix-pipeline-schedule-edge-case.yml @@ -0,0 +1,6 @@ +--- +title: Fix pipeline schedule does not run correctly when it's scheduled at the same + time with the cron worker +merge_request: 29848 +author: +type: fixed diff --git a/changelogs/unreleased/fix-pipeline-schedule-owner-is-nil.yml b/changelogs/unreleased/fix-pipeline-schedule-owner-is-nil.yml deleted file mode 100644 index 5c8644d2860..00000000000 --- a/changelogs/unreleased/fix-pipeline-schedule-owner-is-nil.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix pipeline schedules when owner is nil -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/fix-search-dropdown-blur-close.yml b/changelogs/unreleased/fix-search-dropdown-blur-close.yml deleted file mode 100644 index 1ac9dc674fe..00000000000 --- a/changelogs/unreleased/fix-search-dropdown-blur-close.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix search dropdown not closing on blur if empty -merge_request: 28730 -author: -type: fixed diff --git a/changelogs/unreleased/fix-time-window-default.yml b/changelogs/unreleased/fix-time-window-default.yml deleted file mode 100644 index 147f82eb6c9..00000000000 --- a/changelogs/unreleased/fix-time-window-default.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Use the selected time window for metrics dashboard -merge_request: 29152 -author: -type: fixed diff --git a/changelogs/unreleased/fix-too-many-loops-cron-error.yml b/changelogs/unreleased/fix-too-many-loops-cron-error.yml deleted file mode 100644 index a9b5b761439..00000000000 --- a/changelogs/unreleased/fix-too-many-loops-cron-error.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix "too many loops" error by handling gracefully cron schedules for non existent days -merge_request: 28002 -author: -type: fixed diff --git a/changelogs/unreleased/generate-spans-for-sections.yml b/changelogs/unreleased/generate-spans-for-sections.yml deleted file mode 100644 index e167d66490f..00000000000 --- a/changelogs/unreleased/generate-spans-for-sections.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adds collapsible sections for job log -merge_request: 28642 -author: -type: added diff --git a/changelogs/unreleased/gitaly-version-v1.43.0.yml b/changelogs/unreleased/gitaly-version-v1.43.0.yml deleted file mode 100644 index 67acd2725e1..00000000000 --- a/changelogs/unreleased/gitaly-version-v1.43.0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade to Gitaly v1.43.0 -merge_request: 28867 -author: -type: changed diff --git a/changelogs/unreleased/gitaly-version-v1.47.0.yml b/changelogs/unreleased/gitaly-version-v1.47.0.yml deleted file mode 100644 index b369506b336..00000000000 --- a/changelogs/unreleased/gitaly-version-v1.47.0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade to Gitaly v1.47.0 -merge_request: 29789 -author: -type: changed diff --git a/changelogs/unreleased/gitaly-version-v1.49.0.yml b/changelogs/unreleased/gitaly-version-v1.49.0.yml new file mode 100644 index 00000000000..8795bab0209 --- /dev/null +++ b/changelogs/unreleased/gitaly-version-v1.49.0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade to Gitaly v1.49.0 +merge_request: 29990 +author: +type: changed diff --git a/changelogs/unreleased/gt-externalize-profiles-preferences.yml b/changelogs/unreleased/gt-externalize-profiles-preferences.yml deleted file mode 100644 index 1a72e92a241..00000000000 --- a/changelogs/unreleased/gt-externalize-profiles-preferences.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize profiles preferences -merge_request: 28470 -author: George Tsiolis -type: other diff --git a/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml b/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml deleted file mode 100644 index 35515c9d639..00000000000 --- a/changelogs/unreleased/gt-open-visibility-help-link-in-a-new-tab.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Open visibility help link in a new tab -merge_request: 28603 -author: George Tsiolis -type: fixed diff --git a/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml b/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml new file mode 100644 index 00000000000..d9ca20d9d4d --- /dev/null +++ b/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml @@ -0,0 +1,5 @@ +--- +title: Remove tooltip directive on project avatar image component +merge_request: 29631 +author: George Tsiolis +type: performance diff --git a/changelogs/unreleased/hashed-storage-enabled-default.yml b/changelogs/unreleased/hashed-storage-enabled-default.yml deleted file mode 100644 index 9a34505785c..00000000000 --- a/changelogs/unreleased/hashed-storage-enabled-default.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Hashed Storage is enabled by default on new installations -merge_request: 29586 -author: -type: changed diff --git a/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml b/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml deleted file mode 100644 index fe6eb3a2bf7..00000000000 --- a/changelogs/unreleased/i18n-active_sessions-in-user-profile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings of active sessions page in user profile -merge_request: 28590 -author: antony liu -type: other diff --git a/changelogs/unreleased/i18n-chat-of-user-profile.yml b/changelogs/unreleased/i18n-chat-of-user-profile.yml deleted file mode 100644 index 663b4ffc1a1..00000000000 --- a/changelogs/unreleased/i18n-chat-of-user-profile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings of chat page in user profile -merge_request: 28632 -author: -type: other diff --git a/changelogs/unreleased/i18n-email-of-user-profile.yml b/changelogs/unreleased/i18n-email-of-user-profile.yml deleted file mode 100644 index 6cb718843d5..00000000000 --- a/changelogs/unreleased/i18n-email-of-user-profile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings of email page in user profile -merge_request: 28587 -author: antony liu -type: other diff --git a/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml b/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml deleted file mode 100644 index 4dc45b35976..00000000000 --- a/changelogs/unreleased/i18n-pgp_ssh_keys-of-user-profile.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Externalize strings of PGP Keys and SSH Keys page in user profile -merge_request: 28653 -author: Antony Liu -type: other diff --git a/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml b/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml deleted file mode 100644 index eae2d5f9b2a..00000000000 --- a/changelogs/unreleased/id-bug-suggested-changes-remove-empty-line.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow removal of empty lines via suggestions -merge_request: 28703 -author: -type: fixed diff --git a/changelogs/unreleased/ignore-artifact-attirbutes-in-project-import-export.yml b/changelogs/unreleased/ignore-artifact-attirbutes-in-project-import-export.yml deleted file mode 100644 index 536aae03f59..00000000000 --- a/changelogs/unreleased/ignore-artifact-attirbutes-in-project-import-export.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Ignore legacy artifact columns in Project Import/Export -merge_request: 29427 -author: -type: fixed diff --git a/changelogs/unreleased/improve-email-text-part.yml b/changelogs/unreleased/improve-email-text-part.yml deleted file mode 100644 index ce506cb1507..00000000000 --- a/changelogs/unreleased/improve-email-text-part.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve new user email markup unconsistency between text and html parts -merge_request: 29111 -author: Haunui Saint-sevin -type: fixed diff --git a/changelogs/unreleased/increase-move-issue-dropdown-height.yml b/changelogs/unreleased/increase-move-issue-dropdown-height.yml deleted file mode 100644 index bb67e9341b2..00000000000 --- a/changelogs/unreleased/increase-move-issue-dropdown-height.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Increase height of move issue dropdown -merge_request: -author: -type: other diff --git a/changelogs/unreleased/issue-58747.yml b/changelogs/unreleased/issue-58747.yml deleted file mode 100644 index 01b610576f7..00000000000 --- a/changelogs/unreleased/issue-58747.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix GPG signature verification with recent GnuPG versions -merge_request: 29388 -author: David Palubin -type: fixed diff --git a/changelogs/unreleased/issue_49897.yml b/changelogs/unreleased/issue_49897.yml deleted file mode 100644 index b630b5143c6..00000000000 --- a/changelogs/unreleased/issue_49897.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Delete unauthorized Todos when project is made private -merge_request: 28560 -author: -type: fixed diff --git a/changelogs/unreleased/jc-migration-for-source-project-id.yml b/changelogs/unreleased/jc-migration-for-source-project-id.yml deleted file mode 100644 index 3e2e8ebfcc5..00000000000 --- a/changelogs/unreleased/jc-migration-for-source-project-id.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix null source_project_id in pool_repositories -merge_request: 29157 -author: -type: other diff --git a/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml b/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml deleted file mode 100644 index 23235060a98..00000000000 --- a/changelogs/unreleased/jc-omit-count-diverging-commits-max.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Omit max-count for diverging_commit_counts behind feature flag -merge_request: 28157 -author: -type: other diff --git a/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml b/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml deleted file mode 100644 index dc4edbc058f..00000000000 --- a/changelogs/unreleased/jivl-migrate-dashboard-store-vuex.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Migrate the monitoring dashboard store to vuex -merge_request: 28555 -author: -type: other diff --git a/changelogs/unreleased/jupyter_pre_spawn_hook_v2.yml b/changelogs/unreleased/jupyter_pre_spawn_hook_v2.yml deleted file mode 100644 index c5918df8193..00000000000 --- a/changelogs/unreleased/jupyter_pre_spawn_hook_v2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Pass user's identity and token from JupyterHub to user's Jupyter environment -merge_request: 27314 -author: Amit Rathi -type: added diff --git a/changelogs/unreleased/kinolaev-master-patch-13154.yml b/changelogs/unreleased/kinolaev-master-patch-13154.yml deleted file mode 100644 index 3292ff797e2..00000000000 --- a/changelogs/unreleased/kinolaev-master-patch-13154.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Auto-DevOps: allow to disable rollout status check' -merge_request: 28130 -author: Sergej Nikolaev <kinolaev@gmail.com> -type: fixed diff --git a/changelogs/unreleased/leipert-node-12-compatibility.yml b/changelogs/unreleased/leipert-node-12-compatibility.yml deleted file mode 100644 index 18025d33a6d..00000000000 --- a/changelogs/unreleased/leipert-node-12-compatibility.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade dependencies for node 12 compatibility -merge_request: 28323 -author: -type: fixed diff --git a/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml b/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml deleted file mode 100644 index daeefd3ffd7..00000000000 --- a/changelogs/unreleased/make-autocomplete-faster-with-lots-of-results.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Improve performance of users autocomplete when there are lots of results -merge_request: -author: -type: performance diff --git a/changelogs/unreleased/mark-comment-resolved.yml b/changelogs/unreleased/mark-comment-resolved.yml deleted file mode 100644 index 3343d18d16f..00000000000 --- a/changelogs/unreleased/mark-comment-resolved.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change resolve button text to mark comment as resolved -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/mh-colon-autocomplete.yml b/changelogs/unreleased/mh-colon-autocomplete.yml new file mode 100644 index 00000000000..8b169c22588 --- /dev/null +++ b/changelogs/unreleased/mh-colon-autocomplete.yml @@ -0,0 +1,5 @@ +--- +title: Allow auto-completing scoped labels +merge_request: 29749 +author: +type: added diff --git a/changelogs/unreleased/migrate_k8s_service_integration.yml b/changelogs/unreleased/migrate_k8s_service_integration.yml deleted file mode 100644 index 57f03e6bdab..00000000000 --- a/changelogs/unreleased/migrate_k8s_service_integration.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Migrate Kubernetes service integration templates to clusters -merge_request: 28534 -author: -type: added diff --git a/changelogs/unreleased/mm12935.yml b/changelogs/unreleased/mm12935.yml deleted file mode 100644 index 782586c514e..00000000000 --- a/changelogs/unreleased/mm12935.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove Content-Type override for Mattermost OAuth login -merge_request: -author: Harrison Healey -type: removed diff --git a/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml b/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml deleted file mode 100644 index d0a09385d4c..00000000000 --- a/changelogs/unreleased/osw-avoid-500-on-suggestions-check.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid 500 when rendering users ATOM data -merge_request: 25408 -author: -type: fixed diff --git a/changelogs/unreleased/osw-avoid-encoding-errors-on-merge-to-ref-service.yml b/changelogs/unreleased/osw-avoid-encoding-errors-on-merge-to-ref-service.yml deleted file mode 100644 index 176c8bf36f7..00000000000 --- a/changelogs/unreleased/osw-avoid-encoding-errors-on-merge-to-ref-service.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Handle encoding errors for MergeToRefService -merge_request: 29440 -author: -type: fixed diff --git a/changelogs/unreleased/osw-fix-post-dep-migration-with-timeout.yml b/changelogs/unreleased/osw-fix-post-dep-migration-with-timeout.yml deleted file mode 100644 index 6b37bb11ad5..00000000000 --- a/changelogs/unreleased/osw-fix-post-dep-migration-with-timeout.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Avoid DB timeouts when scheduling migrations -merge_request: 29437 -author: -type: fixed diff --git a/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml b/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml deleted file mode 100644 index 6b5f97f24b3..00000000000 --- a/changelogs/unreleased/osw-reset-merge-status-from-mergeable-mrs.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Reset merge status from mergeable MRs -merge_request: 28843 -author: -type: other diff --git a/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml b/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml new file mode 100644 index 00000000000..d2744cddebd --- /dev/null +++ b/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml @@ -0,0 +1,5 @@ +--- +title: Sync merge ref upon mergeability check +merge_request: 29569 +author: +type: added diff --git a/changelogs/unreleased/patch-55.yml b/changelogs/unreleased/patch-55.yml deleted file mode 100644 index 7a6d6bfed2f..00000000000 --- a/changelogs/unreleased/patch-55.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Better isolated `Docker.gitlab-ci.yml` to avoid interference with other job configurations. -merge_request: 28213 -author: lrkwz -type: changed diff --git a/changelogs/unreleased/patch-65.yml b/changelogs/unreleased/patch-65.yml deleted file mode 100644 index 9ce628a6541..00000000000 --- a/changelogs/unreleased/patch-65.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show tooltip on truncated commit title -merge_request: 28865 -author: Timofey Trofimov -type: other diff --git a/changelogs/unreleased/patch-issue--56683.yml b/changelogs/unreleased/patch-issue--56683.yml deleted file mode 100644 index 9b82c6c1459..00000000000 --- a/changelogs/unreleased/patch-issue--56683.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Process up to 100 commit messages for references when pushing to a new default - branch -merge_request: 29511 -author: Fabio Papa -type: fixed diff --git a/changelogs/unreleased/pb-update-gitaly-1-45-0.yml b/changelogs/unreleased/pb-update-gitaly-1-45-0.yml deleted file mode 100644 index eaad7a8378b..00000000000 --- a/changelogs/unreleased/pb-update-gitaly-1-45-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update GITALY_SERVER_VERSION to 1.45.0 -merge_request: 29109 -author: -type: changed diff --git a/changelogs/unreleased/pipelines-email-default-branch-filter.yml b/changelogs/unreleased/pipelines-email-default-branch-filter.yml deleted file mode 100644 index 4c2a54af0bf..00000000000 --- a/changelogs/unreleased/pipelines-email-default-branch-filter.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add notify_only_default_branch option to PipelinesEmailService -merge_request: 28271 -author: Peter Marko -type: added diff --git a/changelogs/unreleased/readonly_k8s_integration.yml b/changelogs/unreleased/readonly_k8s_integration.yml deleted file mode 100644 index 718705e8750..00000000000 --- a/changelogs/unreleased/readonly_k8s_integration.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make Kubernetes service templates readonly -merge_request: 29044 -author: -type: removed diff --git a/changelogs/unreleased/refactor-sentry.yml b/changelogs/unreleased/refactor-sentry.yml new file mode 100644 index 00000000000..25c5534fae0 --- /dev/null +++ b/changelogs/unreleased/refactor-sentry.yml @@ -0,0 +1,5 @@ +--- +title: Remove Sentry from application settings +merge_request: 28447 +author: Roger Meier +type: added diff --git a/changelogs/unreleased/referenced-labels.yml b/changelogs/unreleased/referenced-labels.yml deleted file mode 100644 index c39ef4c2478..00000000000 --- a/changelogs/unreleased/referenced-labels.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add referenced-commands in no overflow list -merge_request: 28858 -author: -type: fixed diff --git a/changelogs/unreleased/relative-urls-for-system-notes.yml b/changelogs/unreleased/relative-urls-for-system-notes.yml deleted file mode 100644 index ec834e9f277..00000000000 --- a/changelogs/unreleased/relative-urls-for-system-notes.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Change links in system notes to use relative paths -merge_request: 28588 -author: Luke Picciau -type: fixed diff --git a/changelogs/unreleased/remove-grafana-dashboard-link-feature-flag.yml b/changelogs/unreleased/remove-grafana-dashboard-link-feature-flag.yml deleted file mode 100644 index 24b5fb50e40..00000000000 --- a/changelogs/unreleased/remove-grafana-dashboard-link-feature-flag.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Link to an external dashboard from metrics dashboard -merge_request: 29369 -author: -type: added diff --git a/changelogs/unreleased/remove-legacy-artifacts-related-code.yml b/changelogs/unreleased/remove-legacy-artifacts-related-code.yml deleted file mode 100644 index acde65af2d4..00000000000 --- a/changelogs/unreleased/remove-legacy-artifacts-related-code.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove legacy artifact related code -merge_request: 26475 -author: -type: other diff --git a/changelogs/unreleased/remove-mr-diff-header-height.yml b/changelogs/unreleased/remove-mr-diff-header-height.yml deleted file mode 100644 index c06c7281c58..00000000000 --- a/changelogs/unreleased/remove-mr-diff-header-height.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Remove fixed height from MR diff headers -merge_request: -author: -type: other diff --git a/changelogs/unreleased/require-pipeline-when-enabling-only-allow-merge-if-pipeline-succeeds.yml b/changelogs/unreleased/require-pipeline-when-enabling-only-allow-merge-if-pipeline-succeeds.yml new file mode 100644 index 00000000000..c105287532b --- /dev/null +++ b/changelogs/unreleased/require-pipeline-when-enabling-only-allow-merge-if-pipeline-succeeds.yml @@ -0,0 +1,5 @@ +--- +title: Enforce presence of pipeline when "Pipeline must succeed" project setting is enabled +merge_request: 29926 +author: +type: fixed diff --git a/changelogs/unreleased/revert-concurrent-pipeline-schedule-creation.yml b/changelogs/unreleased/revert-concurrent-pipeline-schedule-creation.yml deleted file mode 100644 index 77423463d22..00000000000 --- a/changelogs/unreleased/revert-concurrent-pipeline-schedule-creation.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Revert concurrent pipeline creation for pipeline schedules -merge_request: 29794 -author: -type: fixed diff --git a/changelogs/unreleased/search-blob-basenames.yml b/changelogs/unreleased/search-blob-basenames.yml new file mode 100644 index 00000000000..48ad1130e3f --- /dev/null +++ b/changelogs/unreleased/search-blob-basenames.yml @@ -0,0 +1,5 @@ +--- +title: Build correct basenames for title search results +merge_request: 29898 +author: +type: fixed diff --git a/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml b/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml deleted file mode 100644 index d9ad5af256a..00000000000 --- a/changelogs/unreleased/security-58856-persistent-xss-in-note-objects.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent XSS injection in note imports -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-60039.yml b/changelogs/unreleased/security-60039.yml deleted file mode 100644 index 5edbf32ec97..00000000000 --- a/changelogs/unreleased/security-60039.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent invalid branch for merge request -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml b/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml deleted file mode 100644 index 5b79258af54..00000000000 --- a/changelogs/unreleased/security-60143-address-xss-issue-in-wiki-links.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Filter relative links in wiki for XSS -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml b/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml deleted file mode 100644 index adfd8e1298f..00000000000 --- a/changelogs/unreleased/security-fix-confidential-issue-label-visibility-master.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix confidential issue label disclosure on milestone view -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml b/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml deleted file mode 100644 index 084439c71d9..00000000000 --- a/changelogs/unreleased/security-fix-project-existence-disclosure-master.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix url redaction for issue links -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-fix_milestones_search_api_leak.yml b/changelogs/unreleased/security-fix_milestones_search_api_leak.yml deleted file mode 100644 index 5691550b602..00000000000 --- a/changelogs/unreleased/security-fix_milestones_search_api_leak.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'Resolve: Milestones leaked via search API' -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml b/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml deleted file mode 100644 index df636ec37fb..00000000000 --- a/changelogs/unreleased/security-id-leaked-password-in-import-url-frontend.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add extra fields for handling basic auth on import by url page -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml b/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml deleted file mode 100644 index 02773fa1d7c..00000000000 --- a/changelogs/unreleased/security-jej-prevent-web-sign-in-bypass.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Prevent bypass of restriction disabling web password sign in -merge_request: -author: -type: security diff --git a/changelogs/unreleased/security-unsubscribing-from-issue.yml b/changelogs/unreleased/security-unsubscribing-from-issue.yml deleted file mode 100644 index 3a33a457c69..00000000000 --- a/changelogs/unreleased/security-unsubscribing-from-issue.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Hide confidential issue title on unsubscribe for anonymous users -merge_request: -author: -type: security diff --git a/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml b/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml deleted file mode 100644 index 04eb035b157..00000000000 --- a/changelogs/unreleased/set-real-next-run-at-for-preventing-duplciate-pipeline-creations.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Make pipeline schedule worker resilient -merge_request: 28407 -author: -type: performance diff --git a/changelogs/unreleased/sh-add-backtrace-to-sql-queries.yml b/changelogs/unreleased/sh-add-backtrace-to-sql-queries.yml deleted file mode 100644 index d4ca027d1b9..00000000000 --- a/changelogs/unreleased/sh-add-backtrace-to-sql-queries.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add backtraces to Peek performance bar for SQL calls -merge_request: -author: -type: added diff --git a/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml b/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml deleted file mode 100644 index b089e6e4f37..00000000000 --- a/changelogs/unreleased/sh-add-header-to-jobs-admin-page.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add a column header to admin/jobs page -merge_request: 28837 -author: -type: other diff --git a/changelogs/unreleased/sh-cache-negative-entries-find-commit.yml b/changelogs/unreleased/sh-cache-negative-entries-find-commit.yml new file mode 100644 index 00000000000..98eb13ee620 --- /dev/null +++ b/changelogs/unreleased/sh-cache-negative-entries-find-commit.yml @@ -0,0 +1,5 @@ +--- +title: Allow caching of negative FindCommit matches +merge_request: 29952 +author: +type: performance diff --git a/changelogs/unreleased/sh-default-visibility-fix.yml b/changelogs/unreleased/sh-default-visibility-fix.yml deleted file mode 100644 index 8308f310150..00000000000 --- a/changelogs/unreleased/sh-default-visibility-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Set project default visibility to max allowed -merge_request: 28754 -author: -type: fixed diff --git a/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml b/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml new file mode 100644 index 00000000000..12f4a5a499d --- /dev/null +++ b/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml @@ -0,0 +1,5 @@ +--- +title: Enable Gitaly ref name caching for discussions.json +merge_request: 29951 +author: +type: performance diff --git a/changelogs/unreleased/sh-fix-fogbugz-import.yml b/changelogs/unreleased/sh-fix-fogbugz-import.yml deleted file mode 100644 index 1ac730fca24..00000000000 --- a/changelogs/unreleased/sh-fix-fogbugz-import.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix Fogbugz Importer not working -merge_request: 29383 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-issue-55869.yml b/changelogs/unreleased/sh-fix-issue-55869.yml deleted file mode 100644 index 7935cffc13b..00000000000 --- a/changelogs/unreleased/sh-fix-issue-55869.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix alignment of resend button in members page -merge_request: 28202 -author: -type: other diff --git a/changelogs/unreleased/sh-fix-issue-63158.yml b/changelogs/unreleased/sh-fix-issue-63158.yml deleted file mode 100644 index 1a79166b6a2..00000000000 --- a/changelogs/unreleased/sh-fix-issue-63158.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix inability to set visibility_level on project via API -merge_request: 29578 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-rebase-error-clearing.yml b/changelogs/unreleased/sh-fix-rebase-error-clearing.yml deleted file mode 100644 index 4f5f2779e7f..00000000000 --- a/changelogs/unreleased/sh-fix-rebase-error-clearing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Properly clear the merge error upon rebase failure -merge_request: 28319 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-resolve-button-not-available.yml b/changelogs/unreleased/sh-fix-resolve-button-not-available.yml deleted file mode 100644 index 85a9007f570..00000000000 --- a/changelogs/unreleased/sh-fix-resolve-button-not-available.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix "Resolve conflicts" button not appearing for some users -merge_request: 29535 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-tag-push-remote-mirror.yml b/changelogs/unreleased/sh-fix-tag-push-remote-mirror.yml deleted file mode 100644 index 7f33ab28e3d..00000000000 --- a/changelogs/unreleased/sh-fix-tag-push-remote-mirror.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix remote mirrors not updating after tag push -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-utf-8-encoding-resolve-conflicts.yml b/changelogs/unreleased/sh-fix-utf-8-encoding-resolve-conflicts.yml deleted file mode 100644 index 31039099788..00000000000 --- a/changelogs/unreleased/sh-fix-utf-8-encoding-resolve-conflicts.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix UTF-8 conversion issues when resolving conflicts -merge_request: 29453 -author: -type: fixed diff --git a/changelogs/unreleased/sh-omit-blocked-admins-from-notification.yml b/changelogs/unreleased/sh-omit-blocked-admins-from-notification.yml deleted file mode 100644 index 82c5505892f..00000000000 --- a/changelogs/unreleased/sh-omit-blocked-admins-from-notification.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Omit blocked admins from repository check e-mails -merge_request: 29507 -author: -type: fixed diff --git a/changelogs/unreleased/sh-omit-issues-links-on-poll.yml b/changelogs/unreleased/sh-omit-issues-links-on-poll.yml deleted file mode 100644 index 21e51d3534f..00000000000 --- a/changelogs/unreleased/sh-omit-issues-links-on-poll.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Omit issues links in merge request entity API response -merge_request: 29917 -author: -type: performance diff --git a/changelogs/unreleased/sh-optimize-todos-controller.yml b/changelogs/unreleased/sh-optimize-todos-controller.yml new file mode 100644 index 00000000000..181ddd1b3bc --- /dev/null +++ b/changelogs/unreleased/sh-optimize-todos-controller.yml @@ -0,0 +1,5 @@ +--- +title: Eliminate N+1 queries in Dashboard::TodosController +merge_request: 29954 +author: +type: performance diff --git a/changelogs/unreleased/sh-service-template-bug.yml b/changelogs/unreleased/sh-service-template-bug.yml new file mode 100644 index 00000000000..1ea5ac84f26 --- /dev/null +++ b/changelogs/unreleased/sh-service-template-bug.yml @@ -0,0 +1,5 @@ +--- +title: Disable Rails SQL query cache when applying service templates +merge_request: 30060 +author: +type: fixed diff --git a/changelogs/unreleased/sh-speed-up-commit-loading.yml b/changelogs/unreleased/sh-speed-up-commit-loading.yml deleted file mode 100644 index db408708385..00000000000 --- a/changelogs/unreleased/sh-speed-up-commit-loading.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Speed up commit loads by disabling BatchLoader replace_methods -merge_request: 29633 -author: -type: performance diff --git a/changelogs/unreleased/sh-strong-memoize-appearances.yml b/changelogs/unreleased/sh-strong-memoize-appearances.yml new file mode 100644 index 00000000000..dc4fe1c4d8e --- /dev/null +++ b/changelogs/unreleased/sh-strong-memoize-appearances.yml @@ -0,0 +1,5 @@ +--- +title: Memoize non-existent custom appearances +merge_request: 29957 +author: +type: performance diff --git a/changelogs/unreleased/sh-update-mermaid.yml b/changelogs/unreleased/sh-update-mermaid.yml new file mode 100644 index 00000000000..9a7726cf716 --- /dev/null +++ b/changelogs/unreleased/sh-update-mermaid.yml @@ -0,0 +1,5 @@ +--- +title: Update Mermaid to 8.1.0 +merge_request: 30036 +author: +type: fixed diff --git a/changelogs/unreleased/sh-update-process-mem.yml b/changelogs/unreleased/sh-update-process-mem.yml deleted file mode 100644 index 51b22fb0f00..00000000000 --- a/changelogs/unreleased/sh-update-process-mem.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update get_process_mem to 0.2.3 -merge_request: 28248 -author: -type: other diff --git a/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml b/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml deleted file mode 100644 index 9ad5c9ebb64..00000000000 --- a/changelogs/unreleased/sh-upgrade-ruby-2-6-3-ce.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade Ruby version to 2.6.3 -merge_request: 28117 -author: -type: performance diff --git a/changelogs/unreleased/t1.yml b/changelogs/unreleased/t1.yml deleted file mode 100644 index 6740375485d..00000000000 --- a/changelogs/unreleased/t1.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Set up git client in Jupyter installtion -merge_request: 28783 -author: Amit Rathi -type: added diff --git a/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml b/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml deleted file mode 100644 index 3391fcc9537..00000000000 --- a/changelogs/unreleased/thomas-nilsson-irfu-gitlab-ce-thomas-nilsson-irfu-master-patch-13137.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Allow masking if 8 or more characters in base64. -merge_request: 29143 -author: thomas-nilsson-irfu -type: changed diff --git a/changelogs/unreleased/update-babel-to-7-4-4.yml b/changelogs/unreleased/update-babel-to-7-4-4.yml deleted file mode 100644 index 0d4b4700bb2..00000000000 --- a/changelogs/unreleased/update-babel-to-7-4-4.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Upgrade babel to 7.4.4 -merge_request: 28437 -author: Takuya Noguchi -type: other diff --git a/changelogs/unreleased/update-gitaly-to-v1-42-1.yml b/changelogs/unreleased/update-gitaly-to-v1-42-1.yml deleted file mode 100644 index ff42bdd9c0b..00000000000 --- a/changelogs/unreleased/update-gitaly-to-v1-42-1.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: "Update Gitaly to v1.42.1" -merge_request: 28425 -author: -type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-0.yml deleted file mode 100644 index 494331158fb..00000000000 --- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update GitLab Runner Helm Chart to 0.5.0 -merge_request: 28497 -author: -type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml deleted file mode 100644 index dd71aa7d461..00000000000 --- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-1.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update GitLab Runner Helm Chart to 0.5.1 -merge_request: 28720 -author: -type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml deleted file mode 100644 index 9ca6d18c2a8..00000000000 --- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-5-2.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update GitLab Runner Helm Chart to 0.5.2 -merge_request: 29050 -author: -type: other diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml new file mode 100644 index 00000000000..6719fa94b19 --- /dev/null +++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml @@ -0,0 +1,5 @@ +--- +title: Update GitLab Runner Helm Chart to 0.6.0 +merge_request: 29982 +author: +type: other diff --git a/changelogs/unreleased/update-gitlab-shell-9-3-0.yml b/changelogs/unreleased/update-gitlab-shell-9-3-0.yml deleted file mode 100644 index 781ff31c7d8..00000000000 --- a/changelogs/unreleased/update-gitlab-shell-9-3-0.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update to GitLab Shell v9.3.0 -merge_request: 29283 -author: -type: other diff --git a/changelogs/unreleased/update-pages.yml b/changelogs/unreleased/update-pages.yml deleted file mode 100644 index 97a20b6b8fa..00000000000 --- a/changelogs/unreleased/update-pages.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update GitLab Pages to v1.6.0 -merge_request: 29048 -author: -type: other diff --git a/changelogs/unreleased/update-pagination-texts.yml b/changelogs/unreleased/update-pagination-texts.yml new file mode 100644 index 00000000000..6a398e26242 --- /dev/null +++ b/changelogs/unreleased/update-pagination-texts.yml @@ -0,0 +1,5 @@ +--- +title: Update pagination prev and next texts +merge_request: 29911 +author: +type: other diff --git a/changelogs/unreleased/update-psd-doc.yml b/changelogs/unreleased/update-psd-doc.yml deleted file mode 100644 index 38c8d1c0d68..00000000000 --- a/changelogs/unreleased/update-psd-doc.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update project security dashboard documentation -merge_request: 28681 -author: -type: changed diff --git a/changelogs/unreleased/update-smileys-new.yml b/changelogs/unreleased/update-smileys-new.yml deleted file mode 100644 index 0e3f606c81b..00000000000 --- a/changelogs/unreleased/update-smileys-new.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update new smiley icons, find n replace old names with new ones -merge_request: 28338 -author: Jarek Ostrowski -type: changed diff --git a/changelogs/unreleased/update-tar-to-2-2-2.yml b/changelogs/unreleased/update-tar-to-2-2-2.yml new file mode 100644 index 00000000000..f142fe59448 --- /dev/null +++ b/changelogs/unreleased/update-tar-to-2-2-2.yml @@ -0,0 +1,5 @@ +--- +title: Update tar to 2.2.2 +merge_request: 29949 +author: Takuya Noguchi +type: security diff --git a/changelogs/unreleased/upgrade-pages-to-v1-6-1.yml b/changelogs/unreleased/upgrade-pages-to-v1-6-1.yml deleted file mode 100644 index 0ab58bb50d8..00000000000 --- a/changelogs/unreleased/upgrade-pages-to-v1-6-1.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Update GitLab Pages to v1.6.1 -merge_request: 29559 -author: -type: other diff --git a/changelogs/unreleased/variables-boolean-type.yml b/changelogs/unreleased/variables-boolean-type.yml deleted file mode 100644 index 7776657f60a..00000000000 --- a/changelogs/unreleased/variables-boolean-type.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: 'API: change masked attribute type to Boolean' -merge_request: 28758 -author: -type: other diff --git a/changelogs/unreleased/visual-review-apps-fix-dropdown.yml b/changelogs/unreleased/visual-review-apps-fix-dropdown.yml deleted file mode 100644 index ade68c26df9..00000000000 --- a/changelogs/unreleased/visual-review-apps-fix-dropdown.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Move Dropdown to Stick to MR View App Button -merge_request: 29767 -author: -type: fixed diff --git a/changelogs/unreleased/weimeng-email-routing.yml b/changelogs/unreleased/weimeng-email-routing.yml deleted file mode 100644 index 6536433bd03..00000000000 --- a/changelogs/unreleased/weimeng-email-routing.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add ability to define notification email addresses for groups you belong to. -merge_request: 25299 -author: -type: added diff --git a/changelogs/unreleased/winh-notes-error-handling.yml b/changelogs/unreleased/winh-notes-error-handling.yml deleted file mode 100644 index 6f23dd459d4..00000000000 --- a/changelogs/unreleased/winh-notes-error-handling.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Handle errors in successful notes reply -merge_request: 28082 -author: -type: fixed diff --git a/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml b/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml deleted file mode 100644 index e752e01b701..00000000000 --- a/changelogs/unreleased/zj-remove-delta-island-feature-flag.yml +++ /dev/null @@ -1,3 +0,0 @@ -merge_request: 28871 -title: Improve clone performance by using delta islands -type: performance diff --git a/changelogs/unreleased/zj-usage-ping-pool-repository.yml b/changelogs/unreleased/zj-usage-ping-pool-repository.yml deleted file mode 100644 index 62044a933d0..00000000000 --- a/changelogs/unreleased/zj-usage-ping-pool-repository.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Add Pool repository to the usage ping -merge_request: 28267 -author: -type: other diff --git a/config/initializers/0_inflections.rb b/config/initializers/0_inflections.rb index 1ad9ddca877..4d1f4917275 100644 --- a/config/initializers/0_inflections.rb +++ b/config/initializers/0_inflections.rb @@ -14,6 +14,14 @@ ActiveSupport::Inflector.inflections do |inflect| award_emoji project_statistics system_note_metadata + event_log project_auto_devops + project_registry + file_registry + job_artifact_registry + vulnerability_feedback + vulnerabilities_feedback + group_view ) + inflect.acronym 'EE' end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 6cca7a3b75f..9e74a67b73f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -367,6 +367,10 @@ Settings.cron_jobs['pages_domain_removal_cron_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['pages_domain_removal_cron_worker']['cron'] ||= '47 0 * * *' Settings.cron_jobs['pages_domain_removal_cron_worker']['job_class'] = 'PagesDomainRemovalCronWorker' +Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker']['cron'] ||= '*/10 * * * *' +Settings.cron_jobs['pages_domain_ssl_renewal_cron_worker']['job_class'] = 'PagesDomainSslRenewalCronWorker' + Settings.cron_jobs['issue_due_scheduler_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['issue_due_scheduler_worker']['cron'] ||= '50 00 * * *' Settings.cron_jobs['issue_due_scheduler_worker']['job_class'] = 'IssueDueSchedulerWorker' diff --git a/config/initializers/forbid_sidekiq_in_transactions.rb b/config/initializers/forbid_sidekiq_in_transactions.rb index deb94d7dbce..a69f1ba090e 100644 --- a/config/initializers/forbid_sidekiq_in_transactions.rb +++ b/config/initializers/forbid_sidekiq_in_transactions.rb @@ -17,7 +17,7 @@ module Sidekiq module NoEnqueueingFromTransactions %i(perform_async perform_at perform_in).each do |name| define_method(name) do |*args| - if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction? + if !Sidekiq::Worker.skip_transaction_check && Gitlab::Database.inside_transaction? begin raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG `#{self}.#{name}` cannot be called inside a transaction as this can lead to diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index e5589ce0ad1..fcc6bfa5c92 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -3,18 +3,11 @@ require 'gitlab/current_settings' def configure_sentry - # allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done - begin - sentry_enabled = Gitlab::CurrentSettings.current_application_settings.sentry_enabled - rescue - sentry_enabled = false - end - - if sentry_enabled + if Gitlab::Sentry.enabled? Raven.configure do |config| - config.dsn = Gitlab::CurrentSettings.current_application_settings.sentry_dsn + config.dsn = Gitlab.config.sentry.dsn config.release = Gitlab.revision - config.current_environment = Gitlab.config.sentry.environment.presence + config.current_environment = Gitlab.config.sentry.environment # Sanitize fields based on those sanitized from Rails. config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s) diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb index 81bc890d86b..ba6da3ac57e 100644 --- a/config/routes/snippets.rb +++ b/config/routes/snippets.rb @@ -17,5 +17,5 @@ resources :snippets, concerns: :awardable do end end -get '/s/:username', to: redirect('u/%{username}/snippets'), +get '/s/:username', to: redirect('users/%{username}/snippets'), constraints: { username: /[a-zA-Z.0-9_\-]+(?<!\.atom)/ } diff --git a/config/routes/user.rb b/config/routes/user.rb index e0ae264e2c0..f93a3fc6161 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -48,15 +48,6 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d get :activity get '/', to: redirect('%{username}'), as: nil end - - # Compatibility with old routing - # TODO (dzaporozhets): remove in 10.0 - get '/u/:username', to: redirect('%{username}') - # TODO (dzaporozhets): remove in 9.0 - get '/u/:username/groups', to: redirect('users/%{username}/groups') - get '/u/:username/projects', to: redirect('users/%{username}/projects') - get '/u/:username/snippets', to: redirect('users/%{username}/snippets') - get '/u/:username/contributed', to: redirect('users/%{username}/contributed') end constraints(::Constraints::UserUrlConstrainer.new) do diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 4fda9d69077..25fd65d8644 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -72,6 +72,7 @@ - [project_rollback_hashed_storage, 1] - [hashed_storage, 1] - [pages_domain_verification, 1] + - [pages_domain_ssl_renewal, 1] - [object_storage_upload, 1] - [object_storage, 1] - [plugin, 1] diff --git a/db/migrate/20190402150158_backport_enterprise_schema.rb b/db/migrate/20190402150158_backport_enterprise_schema.rb index 610a8808383..8762cc53ed7 100644 --- a/db/migrate/20190402150158_backport_enterprise_schema.rb +++ b/db/migrate/20190402150158_backport_enterprise_schema.rb @@ -117,6 +117,8 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0] end def up + check_schema! + create_missing_tables update_appearances @@ -868,6 +870,52 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0] remove_column_if_exists(:geo_nodes, :internal_url) end + # Some users may have upgraded to EE at some point but downgraded to + # CE v11.11.3. As a result, their EE tables may not be in the right + # state. Here we check for these such cases and attempt to guide the + # user into recovering from this state by upgrading to v11.11.3 EE + # before installing v12.0.0 CE. + def check_schema! + # The following cases will fail later when this migration attempts + # to add a foreign key for non-existent columns. + columns_to_check = [ + [:epics, :parent_id], # Added in GitLab 11.7 + [:geo_event_log, :cache_invalidation_event_id], # Added in GitLab 11.4 + [:vulnerability_feedback, :merge_request_id] # Added in GitLab 11.9 + ].freeze + + columns_to_check.each do |table, column| + check_ee_columns!(table, column) + end + end + + def check_ee_columns!(table, column) + return unless table_exists?(table) + return if column_exists?(table, column) + + raise_ee_migration_error!(table, column) + end + + def raise_ee_migration_error!(table, column) + message = "Your database is missing the '#{column}' column from the '#{table}' table that is present for GitLab EE." + + message += + if ::Gitlab.ee? + "\nUpgrade your GitLab instance to 11.11.3 EE first!" + else + <<~MSG + + Even though it looks like you're running a CE installation, it appears + you may have installed GitLab EE at some point. To migrate to GitLab 12.0: + + 1. Install GitLab 11.11.3 EE + 2. Install GitLab 12.0.x CE + MSG + end + + raise Exception.new(message) + end + def create_missing_tables create_table_if_not_exists "approval_merge_request_rule_sources", id: :bigserial do |t| t.bigint "approval_merge_request_rule_id", null: false diff --git a/db/migrate/20190513174947_enable_create_incident_issues_by_default.rb b/db/migrate/20190513174947_enable_create_incident_issues_by_default.rb new file mode 100644 index 00000000000..ecd466627fe --- /dev/null +++ b/db/migrate/20190513174947_enable_create_incident_issues_by_default.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class EnableCreateIncidentIssuesByDefault < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + change_default_for :create_issue, from: false, to: true + change_default_for :send_email, from: true, to: false + end + + private + + def change_default_for(column, from:, to:) + change_column_default :project_incident_management_settings, + column, from: from, to: to + end +end diff --git a/db/migrate/20190531153110_create_namespace_root_storage_statistics.rb b/db/migrate/20190531153110_create_namespace_root_storage_statistics.rb new file mode 100644 index 00000000000..702560d05cc --- /dev/null +++ b/db/migrate/20190531153110_create_namespace_root_storage_statistics.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class CreateNamespaceRootStorageStatistics < ActiveRecord::Migration[5.1] + DOWNTIME = false + + def change + create_table :namespace_root_storage_statistics, id: false, primary_key: :namespace_id do |t| + t.integer :namespace_id, null: false, primary_key: true + t.datetime_with_timezone :updated_at, null: false + + t.bigint :repository_size, null: false, default: 0 + t.bigint :lfs_objects_size, null: false, default: 0 + t.bigint :wiki_size, null: false, default: 0 + t.bigint :build_artifacts_size, null: false, default: 0 + t.bigint :storage_size, null: false, default: 0 + t.bigint :packages_size, null: false, default: 0 + + t.index :namespace_id, unique: true + t.foreign_key :namespaces, column: :namespace_id, on_delete: :cascade + end + end +end diff --git a/db/migrate/20190605184422_create_namespace_aggregation_schedules.rb b/db/migrate/20190605184422_create_namespace_aggregation_schedules.rb new file mode 100644 index 00000000000..5e8cb616cc1 --- /dev/null +++ b/db/migrate/20190605184422_create_namespace_aggregation_schedules.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class CreateNamespaceAggregationSchedules < ActiveRecord::Migration[5.1] + DOWNTIME = false + + def change + create_table :namespace_aggregation_schedules, id: false, primary_key: :namespace_id do |t| + t.integer :namespace_id, null: false, primary_key: true + + t.index :namespace_id, unique: true + t.foreign_key :namespaces, column: :namespace_id, on_delete: :cascade + end + end +end diff --git a/db/migrate/20190607145325_add_pages_domains_ssl_renew_index.rb b/db/migrate/20190607145325_add_pages_domains_ssl_renew_index.rb new file mode 100644 index 00000000000..7167accbf1e --- /dev/null +++ b/db/migrate/20190607145325_add_pages_domains_ssl_renew_index.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddPagesDomainsSslRenewIndex < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + INDEX_NAME = 'index_pages_domains_need_auto_ssl_renewal' + + disable_ddl_transaction! + + def up + add_concurrent_index(:pages_domains, [:certificate_source, :certificate_valid_not_after], + where: "auto_ssl_enabled = #{::Gitlab::Database.true_value}", name: INDEX_NAME) + end + + def down + remove_concurrent_index(:pages_domains, [:certificate_source, :certificate_valid_not_after], + where: "auto_ssl_enabled = #{::Gitlab::Database.true_value}", name: INDEX_NAME) + end +end diff --git a/db/migrate/20190611090827_add_time_tracking_limit_to_hours_to_application_settings.rb b/db/migrate/20190611090827_add_time_tracking_limit_to_hours_to_application_settings.rb new file mode 100644 index 00000000000..a5f8925c1db --- /dev/null +++ b/db/migrate/20190611090827_add_time_tracking_limit_to_hours_to_application_settings.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddTimeTrackingLimitToHoursToApplicationSettings < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default :application_settings, :time_tracking_limit_to_hours, :boolean, default: false, allow_null: false + end + + def down + remove_column :application_settings, :time_tracking_limit_to_hours + end +end diff --git a/db/post_migrate/20190620112608_enqueue_reset_merge_status_second_run.rb b/db/post_migrate/20190620112608_enqueue_reset_merge_status_second_run.rb new file mode 100644 index 00000000000..2d096a2a39c --- /dev/null +++ b/db/post_migrate/20190620112608_enqueue_reset_merge_status_second_run.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class EnqueueResetMergeStatusSecondRun < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 10_000 + MIGRATION = 'ResetMergeStatus' + DELAY_INTERVAL = 5.minutes.to_i + + disable_ddl_transaction! + + def up + say 'Scheduling `ResetMergeStatus` jobs' + + # We currently have more than ~5_000_000 merge request records on GitLab.com. + # This means it'll schedule ~500 jobs (10k MRs each) with a 5 minutes gap, + # so this should take ~41 hours for all background migrations to complete. + # ((5_000_000 / 10_000) * 5) / 60 => 41.6666.. + queue_background_migration_jobs_by_range_at_intervals(MergeRequest, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) + end +end diff --git a/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb b/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb new file mode 100644 index 00000000000..427df343193 --- /dev/null +++ b/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveSentryFromApplicationSettings < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + SENTRY_ENABLED_COLUMNS = [ + :sentry_enabled, + :clientside_sentry_enabled + ].freeze + + SENTRY_DSN_COLUMNS = [ + :sentry_dsn, + :clientside_sentry_dsn + ].freeze + + disable_ddl_transaction! + + def up + (SENTRY_ENABLED_COLUMNS + SENTRY_DSN_COLUMNS).each do |column| + remove_column(:application_settings, column) if column_exists?(:application_settings, column) + end + end + + def down + SENTRY_ENABLED_COLUMNS.each do |column| + add_column_with_default(:application_settings, column, :boolean, default: false, allow_null: false) unless column_exists?(:application_settings, column) + end + + SENTRY_DSN_COLUMNS.each do |column| + add_column(:application_settings, column, :string) unless column_exists?(:application_settings, column) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index c1c67e012e9..054dbc7201f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190619175843) do +ActiveRecord::Schema.define(version: 20190625184066) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -93,8 +93,6 @@ ActiveRecord::Schema.define(version: 20190619175843) do t.boolean "akismet_enabled", default: false t.string "akismet_api_key" t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false - t.string "sentry_dsn" t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" t.boolean "repository_checks_enabled", default: false @@ -135,8 +133,6 @@ ActiveRecord::Schema.define(version: 20190619175843) do t.string "uuid" t.decimal "polling_interval_multiplier", default: "1.0", null: false t.integer "cached_markdown_version" - t.boolean "clientside_sentry_enabled", default: false, null: false - t.string "clientside_sentry_dsn" t.boolean "prometheus_metrics_enabled", default: true, null: false t.boolean "help_page_hide_commercial_content", default: false t.string "help_page_support_url" @@ -229,6 +225,7 @@ ActiveRecord::Schema.define(version: 20190619175843) do t.integer "custom_project_templates_group_id" t.boolean "elasticsearch_limit_indexing", default: false, null: false t.string "geo_node_allowed_ips", default: "0.0.0.0/0, ::/0" + t.boolean "time_tracking_limit_to_hours", default: false, null: false t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id", using: :btree t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id", using: :btree t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree @@ -2054,6 +2051,21 @@ ActiveRecord::Schema.define(version: 20190619175843) do t.index ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} end + create_table "namespace_aggregation_schedules", primary_key: "namespace_id", id: :integer, default: nil, force: :cascade do |t| + t.index ["namespace_id"], name: "index_namespace_aggregation_schedules_on_namespace_id", unique: true, using: :btree + end + + create_table "namespace_root_storage_statistics", primary_key: "namespace_id", id: :integer, default: nil, force: :cascade do |t| + t.datetime_with_timezone "updated_at", null: false + t.bigint "repository_size", default: 0, null: false + t.bigint "lfs_objects_size", default: 0, null: false + t.bigint "wiki_size", default: 0, null: false + t.bigint "build_artifacts_size", default: 0, null: false + t.bigint "storage_size", default: 0, null: false + t.bigint "packages_size", default: 0, null: false + t.index ["namespace_id"], name: "index_namespace_root_storage_statistics_on_namespace_id", unique: true, using: :btree + end + create_table "namespace_statistics", id: :serial, force: :cascade do |t| t.integer "namespace_id", null: false t.integer "shared_runners_seconds", default: 0, null: false @@ -2334,6 +2346,7 @@ ActiveRecord::Schema.define(version: 20190619175843) do t.datetime_with_timezone "certificate_valid_not_before" t.datetime_with_timezone "certificate_valid_not_after" t.integer "certificate_source", limit: 2, default: 0, null: false + t.index ["certificate_source", "certificate_valid_not_after"], name: "index_pages_domains_need_auto_ssl_renewal", where: "(auto_ssl_enabled = true)", using: :btree t.index ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree t.index ["project_id", "enabled_until"], name: "index_pages_domains_on_project_id_and_enabled_until", using: :btree t.index ["project_id"], name: "index_pages_domains_on_project_id", using: :btree @@ -2501,9 +2514,9 @@ ActiveRecord::Schema.define(version: 20190619175843) do t.index ["project_id"], name: "index_project_import_data_on_project_id", using: :btree end - create_table "project_incident_management_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t| - t.boolean "create_issue", default: false, null: false - t.boolean "send_email", default: true, null: false + create_table "project_incident_management_settings", primary_key: "project_id", id: :serial, force: :cascade do |t| + t.boolean "create_issue", default: true, null: false + t.boolean "send_email", default: false, null: false t.text "issue_template_key" end @@ -3755,6 +3768,8 @@ ActiveRecord::Schema.define(version: 20190619175843) do add_foreign_key "merge_trains", "users", on_delete: :cascade add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade + add_foreign_key "namespace_aggregation_schedules", "namespaces", on_delete: :cascade + add_foreign_key "namespace_root_storage_statistics", "namespaces", on_delete: :cascade add_foreign_key "namespace_statistics", "namespaces", on_delete: :cascade add_foreign_key "namespaces", "namespaces", column: "custom_project_templates_group_id", name: "fk_e7a0b20a6b", on_delete: :nullify add_foreign_key "namespaces", "plans", name: "fk_fdd12e5b80", on_delete: :nullify diff --git a/doc/README.md b/doc/README.md index 3863e17c268..cfda2c9293d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -54,7 +54,7 @@ GitLab provides solutions for [all the stages of the DevOps lifecycle](https://a ![DevOps Stages](img/devops-stages.png) GitLab is like a top-of-the-line kitchen for making software. As the executive -chef, you decide what software you want serve. Using your recipe, GitLab handles +chef, you decide what software you want to serve. Using your recipe, GitLab handles all the prep work, cooking, and delivery, so you can turn around orders faster than ever. diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index 54279897e04..1f2961ea39a 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -396,21 +396,34 @@ omniauth-ldap. ### Escaping special characters -If the `user_filter` DN contains special characters. For example, a comma: +The `user_filter` DN can contain special characters. For example: -``` -OU=GitLab, Inc,DC=gitlab,DC=com -``` +- A comma: -This character needs to be escaped as documented in [RFC 4515](https://tools.ietf.org/search/rfc4515). + ``` + OU=GitLab, Inc,DC=gitlab,DC=com + ``` -Due to the way the string is parsed, the special character needs to be converted -to hex and `\\5C\\` (`5C` = `\` in hex) added before it. -As an example the above DN would look like +- Open and close brackets: -``` -OU=GitLab\\5C\\2C Inc,DC=gitlab,DC=com -``` + ``` + OU=Gitlab (Inc),DC=gitlab,DC=com + ``` + + These characters must be escaped as documented in + [RFC 4515](https://tools.ietf.org/search/rfc4515). + +- Escape commas with `\2C`. For example: + + ``` + OU=GitLab\2C Inc,DC=gitlab,DC=com + ``` + +- Escape open and close brackets with `\28` and `\29`, respectively. For example: + + ``` + OU=Gitlab \28Inc\29,DC=gitlab,DC=com + ``` ## Enabling LDAP sign-in for existing GitLab users diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md index 00422ec347c..6e48add6930 100644 --- a/doc/administration/auth/oidc.md +++ b/doc/administration/auth/oidc.md @@ -144,20 +144,20 @@ for more details: If you're having trouble, here are some tips: 1. Ensure `discovery` is set to `true`. Setting it to `false` requires -specifying all the URLs and keys required to make OpenID work. + specifying all the URLs and keys required to make OpenID work. 1. Check your system clock to ensure the time is synchronized properly. 1. As mentioned in [the -documentation](https://github.com/m0n9oose/omniauth_openid_connect), -make sure `issuer` corresponds to the base URL of the Discovery URL. For -example, `https://accounts.google.com` is used for the URL -`https://accounts.google.com/.well-known/openid-configuration`. + documentation](https://github.com/m0n9oose/omniauth_openid_connect), + make sure `issuer` corresponds to the base URL of the Discovery URL. For + example, `https://accounts.google.com` is used for the URL + `https://accounts.google.com/.well-known/openid-configuration`. 1. The OpenID Connect client uses HTTP Basic Authentication to send the -OAuth2 access token. For example, if you are seeing 401 errors upon -retrieving the `userinfo` endpoint, you may want to check your OpenID -Web server configuration. For example, for -[oauth2-server-php](https://github.com/bshaffer/oauth2-server-php), you -may need to [add a configuration parameter to -Apache](https://github.com/bshaffer/oauth2-server-php/issues/926#issuecomment-387502778). + OAuth2 access token. For example, if you are seeing 401 errors upon + retrieving the `userinfo` endpoint, you may want to check your OpenID + Web server configuration. For example, for + [oauth2-server-php](https://github.com/bshaffer/oauth2-server-php), you + may need to [add a configuration parameter to + Apache](https://github.com/bshaffer/oauth2-server-php/issues/926#issuecomment-387502778). diff --git a/doc/administration/geo/replication/faq.md b/doc/administration/geo/replication/faq.md index dd1af0dbf9c..c527248bc72 100644 --- a/doc/administration/geo/replication/faq.md +++ b/doc/administration/geo/replication/faq.md @@ -6,8 +6,8 @@ The requirements are listed [on the index page](index.md#requirements-for-runnin ## How does Geo know which projects to sync? -On each **secondary** node, there is a read-only replicated copy of the GitLab database. -A **secondary** node also has a tracking database where it stores which projects have been synced. +On each **secondary** node, there is a read-only replicated copy of the GitLab database. +A **secondary** node also has a tracking database where it stores which projects have been synced. Geo compares the two databases to find projects that are not yet tracked. At the start, this tracking database is empty, so Geo will start trying to update from every project that it can see in the GitLab database. @@ -15,19 +15,19 @@ At the start, this tracking database is empty, so Geo will start trying to updat For each project to sync: 1. Geo will issue a `git fetch geo --mirror` to get the latest information from the **primary** node. -If there are no changes, the sync will be fast and end quickly. Otherwise, it will pull the latest commits. + If there are no changes, the sync will be fast and end quickly. Otherwise, it will pull the latest commits. 1. The **secondary** node will update the tracking database to store the fact that it has synced projects A, B, C, etc. 1. Repeat until all projects are synced. -When someone pushes a commit to the **primary** node, it generates an event in the GitLab database that the repository has changed. +When someone pushes a commit to the **primary** node, it generates an event in the GitLab database that the repository has changed. The **secondary** node sees this event, marks the project in question as dirty, and schedules the project to be resynced. To ensure that problems with pipelines (for example, syncs failing too many times or jobs being lost) don't permanently stop projects syncing, Geo also periodically checks the tracking database for projects that are marked as dirty. This check happens when -the number of concurrent syncs falls below `repos_max_capacity` and there are no new projects waiting to be synced. +the number of concurrent syncs falls below `repos_max_capacity` and there are no new projects waiting to be synced. -Geo also has a checksum feature which runs a SHA256 sum across all the Git references to the SHA values. -If the refs don't match between the **primary** node and the **secondary** node, then the **secondary** node will mark that project as dirty and try to resync it. -So even if we have an outdated tracking database, the validation should activate and find discrepancies in the repository state and resync. +Geo also has a checksum feature which runs a SHA256 sum across all the Git references to the SHA values. +If the refs don't match between the **primary** node and the **secondary** node, then the **secondary** node will mark that project as dirty and try to resync it. +So even if we have an outdated tracking database, the validation should activate and find discrepancies in the repository state and resync. ## Can I use Geo in a disaster recovery situation? diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md index 846afd8f5f4..5394e6dd763 100644 --- a/doc/administration/geo/replication/troubleshooting.md +++ b/doc/administration/geo/replication/troubleshooting.md @@ -331,7 +331,7 @@ There are a few key points to remember: 1. The FDW settings are configured on the Geo **tracking** database. 1. The configured foreign server enables a login to the Geo -**secondary**, read-only database. + **secondary**, read-only database. By default, the Geo secondary and tracking database are running on the same host on different ports. That is, 5432 and 5431 respectively. @@ -350,7 +350,7 @@ To check the configuration: ``` 1. Check whether any tables are present. If everything is working, you -should see something like this: + should see something like this: ```sql gitlabhq_geo_production=# SELECT * from information_schema.foreign_tables; diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md index 4db53353218..20bbfdb2603 100644 --- a/doc/administration/high_availability/database.md +++ b/doc/administration/high_availability/database.md @@ -83,7 +83,7 @@ deploy the bundled PostgreSQL. plain text password. These will be necessary when configuring the GitLab application servers later. 1. [Enable monitoring](#enable-monitoring) - + Advanced configuration options are supported and can be added if needed. @@ -204,9 +204,9 @@ Few notes on the service itself: - The service runs under a system account, by default `gitlab-consul`. - If you are using a different username, you will have to specify it. We -will refer to it with `CONSUL_USERNAME`, + will refer to it with `CONSUL_USERNAME`, - There will be a database user created with read only access to the repmgr -database + database - Passwords will be stored in the following locations: - `/etc/gitlab/gitlab.rb`: hashed - `/var/opt/gitlab/pgbouncer/pg_auth`: hashed diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index e3e90a9dc34..3045be616a6 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -160,12 +160,11 @@ If you enable Monitoring, it must be enabled on **all** GitLab servers. sidekiq['listen_address'] = "0.0.0.0" unicorn['listen'] = '0.0.0.0' - # Add the monitoring node's IP address to the monitoring whitelist and allow it to scrape the NGINX metrics - # Replace placeholder - # monitoring.gitlab.example.com - # with the addresses gathered for the monitoring node - gitlab_rails['monitoring_whitelist'] = ['monitoring.gitlab.example.com'] - nginx['status']['options']['allow'] = ['monitoring.gitlab.example.com'] + # Add the monitoring node's IP address to the monitoring whitelist and allow it to + # scrape the NGINX metrics. Replace placeholder `monitoring.gitlab.example.com` with + # the address and/or subnets gathered from the monitoring node(s). + gitlab_rails['monitoring_whitelist'] = ['monitoring.gitlab.example.com', '127.0.0.0/8'] + nginx['status']['options']['allow'] = ['monitoring.gitlab.example.com', '127.0.0.0/8'] ``` 1. Run `sudo gitlab-ctl reconfigure` to compile the configuration. diff --git a/doc/administration/high_availability/pgbouncer.md b/doc/administration/high_availability/pgbouncer.md index 762179cf756..053dae25823 100644 --- a/doc/administration/high_availability/pgbouncer.md +++ b/doc/administration/high_availability/pgbouncer.md @@ -62,6 +62,33 @@ See our [HA documentation for PostgreSQL](database.md) for information on runnin 1. At this point, your instance should connect to the database through pgbouncer. If you are having issues, see the [Troubleshooting](#troubleshooting) section +## Enable Monitoring + +> [Introduced](https://gitlab.com/gitlab-org/omnibus-gitlab/issues/3786) in GitLab 12.0. + + If you enable Monitoring, it must be enabled on **all** pgbouncer servers. + + 1. Create/edit `/etc/gitlab/gitlab.rb` and add the following configuration: + + ```ruby + # Enable service discovery for Prometheus + consul['enable'] = true + consul['monitoring_service_discovery'] = true + + # Replace placeholders + # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z + # with the addresses of the Consul server nodes + consul['configuration'] = { + retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z), + } + + # Set the network addresses that the exporters will listen on + node_exporter['listen_address'] = '0.0.0.0:9100' + pgbouncer_exporter['listen_address'] = '0.0.0.0:9188' + ``` + + 1. Run `sudo gitlab-ctl reconfigure` to compile the configuration. + ### Interacting with pgbouncer #### Administrative console diff --git a/doc/administration/operations/extra_sidekiq_processes.md b/doc/administration/operations/extra_sidekiq_processes.md index 286b99aceb5..7297507f599 100644 --- a/doc/administration/operations/extra_sidekiq_processes.md +++ b/doc/administration/operations/extra_sidekiq_processes.md @@ -1,70 +1,132 @@ # Extra Sidekiq processes **[STARTER ONLY]** -GitLab Enterprise Edition allows one to start an extra set of Sidekiq processes +NOTE: **Note:** +The information in this page applies only to Omnibus GitLab. + +GitLab Starter allows one to start an extra set of Sidekiq processes besides the default one. These processes can be used to consume a dedicated set of queues. This can be used to ensure certain queues always have dedicated workers, no matter the number of jobs that need to be processed. -## Starting extra processes via Omnibus GitLab +## Available Sidekiq queues -To enable `sidekiq-cluster`, you must apply the `sidekiq_cluster['enable'] = true` -setting `/etc/gitlab/gitlab.rb`: +For a list of the existing Sidekiq queues, check the following files: -```ruby -sidekiq_cluster['enable'] = true -``` +- [Queues for both GitLab Community and Enterprise Editions](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/workers/all_queues.yml) +- [Queues for GitLab Enterprise Editions only](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/app/workers/all_queues.yml) -You will then specify how many additional processes to create via `sidekiq-cluster` -as well as which queues for them to handle. This is done via the -`sidekiq_cluster['queue_groups']` setting. This is an array whose items contain -which queues to process. Each item in the array will equate to one additional -sidekiq process. +Each entry in the above files represents a queue on which extra Sidekiq processes +can be started. -As an example, to make additional sidekiq processes that process the -`elastic_indexer` and `mailers` queues, you would apply the following: +## Starting extra processes -```ruby -sidekiq_cluster['queue_groups'] = [ - "elastic_indexer", - "mailers" -] -``` +To start extra Sidekiq processes, you must enable `sidekiq-cluster`: -To have an additional sidekiq process handle multiple queues, you simply put a -comma after the first queue name and then put the next queue name: +1. Edit `/etc/gitlab/gitlab.rb` and add: -```ruby -sidekiq_cluster['queue_groups'] = [ - "elastic_indexer,elastic_commit_indexer", - "mailers" -] -``` + ```ruby + sidekiq_cluster['enable'] = true + ``` -Keep in mind, all changes must be followed by reconfiguring your GitLab -application via `sudo gitlab-ctl reconfigure`. +1. You will then need to specify how many additional processes to create via `sidekiq-cluster` + and which queue they should handle via the `sidekiq_cluster['queue_groups']` + array setting. Each item in the array equates to one additional Sidekiq + process, and values in each item determine the queues it works on. -### Monitoring + For example, the following setting adds additional Sidekiq processes to two + queues, one to `elastic_indexer` and one to `mailers`: -Once the Sidekiq processes are added, you can visit the "Background Jobs" + ```ruby + sidekiq_cluster['queue_groups'] = [ + "elastic_indexer", + "mailers" + ] + ``` + + To have an additional Sidekiq process handle multiple queues, add multiple + queue names to its item delimited by commas. For example: + + ```ruby + sidekiq_cluster['queue_groups'] = [ + "elastic_indexer, elastic_commit_indexer", + "mailers" + ] + ``` + +1. Save the file and reconfigure GitLab for the changes to take effect: + + ```sh + sudo gitlab-ctl reconfigure + ``` + +Once the extra Sidekiq processes are added, you can visit the "Background Jobs" section under the admin area in GitLab (`/admin/background_jobs`). -![Extra sidekiq processes](img/sidekiq-cluster.png) +![Extra Sidekiq processes](img/sidekiq-cluster.png) -### All queues with exceptions +## Negating settings -To have the additional sidekiq processes work on every queue EXCEPT the ones +To have the additional Sidekiq processes work on every queue **except** the ones you list: +1. After you follow the steps for [starting extra processes](#starting-extra-processes), + edit `/etc/gitlab/gitlab.rb` and add: + + ```ruby + sidekiq_cluster['negate'] = true + ``` + +1. Save the file and reconfigure GitLab for the changes to take effect: + + ```sh + sudo gitlab-ctl reconfigure + ``` + +## Ignore all GitHub import queues + +When [importing from GitHub](../../user/project/import/github.md), Sidekiq might +use all of its resources to perform those operations. To set up a separate +`sidekiq-cluster` process to ignore all GitHub import-related queues: + 1. Edit `/etc/gitlab/gitlab.rb` and add: ```ruby + sidekiq_cluster['enable'] = true sidekiq_cluster['negate'] = true + sidekiq_cluster['queue_groups'] = [ + "github_import_advance_stage", + "github_importer:github_import_import_diff_note", + "github_importer:github_import_import_issue", + "github_importer:github_import_import_note", + "github_importer:github_import_import_lfs_object", + "github_importer:github_import_import_pull_request", + "github_importer:github_import_refresh_import_jid", + "github_importer:github_import_stage_finish_import", + "github_importer:github_import_stage_import_base_data", + "github_importer:github_import_stage_import_issues_and_diff_notes", + "github_importer:github_import_stage_import_notes", + "github_importer:github_import_stage_import_lfs_objects", + "github_importer:github_import_stage_import_pull_requests", + "github_importer:github_import_stage_import_repository" + ] ``` -1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. +1. Save the file and reconfigure GitLab for the changes to take effect: + ```sh + sudo gitlab-ctl reconfigure + ``` -### Limiting concurrency +## Number of threads + +Each process defined under `sidekiq_cluster` starts with a +number of threads that equals the number of queues, plus one spare thread. +For example, a process that handles the `process_commit` and `post_receive` +queues will use three threads in total. + +## Limiting concurrency + +To limit the concurrency of the Sidekiq processes: 1. Edit `/etc/gitlab/gitlab.rb` and add: @@ -72,11 +134,22 @@ you list: sidekiq_cluster['concurrency'] = 25 ``` -1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect. +1. Save the file and reconfigure GitLab for the changes to take effect: -Keep in mind, this normally would not exceed the number of CPU cores available. + ```sh + sudo gitlab-ctl reconfigure + ``` -### Modifying the check interval +For each queue group, the concurrency factor will be set to `min(number of queues, N)`. +Setting the value to 0 will disable the limit. Keep in mind this normally would +not exceed the number of CPU cores available. + +Each thread requires a Redis connection, so adding threads may +increase Redis latency and potentially cause client timeouts. See the [Sidekiq +documentation about Redis](https://github.com/mperham/sidekiq/wiki/Using-Redis) +for more details. + +## Modifying the check interval To modify the check interval for the additional Sidekiq processes: @@ -90,9 +163,14 @@ To modify the check interval for the additional Sidekiq processes: This tells the additional processes how often to check for enqueued jobs. -## Starting extra processes via command line +## Troubleshooting using the CLI -Starting extra Sidekiq processes can be done using the command +CAUTION: **Warning:** +It's recommended to use `/etc/gitlab/gitlab.rb` to configure the Sidekiq processes. +If you experience a problem, you should contact GitLab support. Use the command +line at your own risk. + +For debugging purposes, you can start extra Sidekiq processes by using the command `/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster`. This command takes arguments using the following syntax: @@ -111,29 +189,29 @@ see the relevant section in the [Sidekiq style guide](../../development/sidekiq_style_guide.md#queue-namespaces). For example, say you want to start 2 extra processes: one to process the -"process_commit" queue, and one to process the "post_receive" queue. This can be +`process_commit` queue, and one to process the `post_receive` queue. This can be done as follows: ```bash /opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit post_receive ``` -If you instead want to start one process processing both queues you'd use the +If you instead want to start one process processing both queues, you'd use the following syntax: ```bash /opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit,post_receive ``` -If you want to have one Sidekiq process process the "process_commit" and -"post_receive" queues, and one process to process the "gitlab_shell" queue, +If you want to have one Sidekiq process dealing with the `process_commit` and +`post_receive` queues, and one process to process the `gitlab_shell` queue, you'd use the following: ```bash /opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit,post_receive gitlab_shell ``` -### Monitoring +### Monitoring the `sidekiq-cluster` command The `sidekiq-cluster` command will not terminate once it has started the desired amount of Sidekiq processes. Instead, the process will continue running and @@ -172,24 +250,24 @@ command and not the PID(s) of the started Sidekiq processes. The Rails environment can be set by passing the `--environment` flag to the `sidekiq-cluster` command, or by setting `RAILS_ENV` to a non-empty value. The -default value is "development". +default value can be found in `/opt/gitlab/etc/gitlab-rails/env/RAILS_ENV`. -### All queues with exceptions +### Using negation You're able to run all queues in `sidekiq_queues.yml` file on a single or multiple processes with exceptions using the `--negate` flag. For example, say you want to run a single process for all queues, -except "process_commit" and "post_receive". You can do so by executing: +except `process_commit` and `post_receive`: ```bash -sidekiq-cluster process_commit,post_receive --negate +/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit,post_receive --negate ``` -For multiple processes of all queues (except "process_commit" and "post_receive"): +For multiple processes of all queues (except `process_commit` and `post_receive`): ```bash -sidekiq-cluster process_commit,post_receive process_commit,post_receive --negate +/opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit,post_receive process_commit,post_receive --negate ``` ### Limiting concurrency @@ -201,18 +279,3 @@ the `-m N` option. For example, this would cap the maximum number of threads to ```bash /opt/gitlab/embedded/service/gitlab-rails/ee/bin/sidekiq-cluster process_commit,post_receive -m 1 ``` - -For each queue group, the concurrency factor will be set to min(number of -queues, N). Setting the value to 0 will disable the limit. - -Note that each thread requires a Redis connection, so adding threads may -increase Redis latency and potentially cause client timeouts. See the [Sidekiq -documentation about Redis](https://github.com/mperham/sidekiq/wiki/Using-Redis) -for more details. - -## Number of threads - -Each process started using `sidekiq-cluster` (whether it be via command line or -via the gitlab.rb file) starts with a number of threads that equals the number -of queues, plus one spare thread. For example, a process that handles the -"process_commit" and "post_receive" queues will use 3 threads in total. diff --git a/doc/api/environments.md b/doc/api/environments.md index ebcdc546d08..44f86861ff7 100644 --- a/doc/api/environments.md +++ b/doc/api/environments.md @@ -11,9 +11,11 @@ GET /projects/:id/environments | Attribute | Type | Required | Description | | --------- | ------- | -------- | --------------------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `name` | string | no | Return the environment with this name. Mutually exclusive with `search` | +| `search` | string | no | Return list of environments matching the search criteria. Mutually exclusive with `name` | ```bash -curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/environments +curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/environments?name=review%2Ffix-foo ``` Example response: diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md index ddac81328b9..49aaac06b46 100644 --- a/doc/api/merge_request_approvals.md +++ b/doc/api/merge_request_approvals.md @@ -222,7 +222,7 @@ GET /projects/:id/merge_requests/:merge_request_iid/approvals "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" } } ], @@ -314,7 +314,7 @@ PUT /projects/:id/merge_requests/:merge_request_iid/approvers "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" } } ], @@ -387,7 +387,7 @@ does not match, the response code will be `409`. "id": 1, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon", - "web_url": "http://localhost:3000/u/root" + "web_url": "http://localhost:3000/root" } }, { @@ -397,7 +397,7 @@ does not match, the response code will be `409`. "id": 2, "state": "active", "avatar_url": "http://www.gravatar.com/avatar/cf7ad14b34162a76d593e3affca2adca?s=80\u0026d=identicon", - "web_url": "http://localhost:3000/u/ryley" + "web_url": "http://localhost:3000/ryley" } } ], diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index dd7810c3403..7b58aa3100e 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -1191,33 +1191,29 @@ Parameters: } ``` -## Merge to default merge ref path +## Returns the up to date merge-ref HEAD commit Merge the changes between the merge request source and target branches into `refs/merge-requests/:iid/merge` -ref, of the target project repository. This ref will have the state the target branch would have if +ref, of the target project repository, if possible. This ref will have the state the target branch would have if a regular merge action was taken. -This is not a regular merge action given it doesn't change the merge request state in any manner. +This is not a regular merge action given it doesn't change the merge request target branch state in any manner. -This ref (`refs/merge-requests/:iid/merge`) is **always** overwritten when submitting -requests to this API, so none of its state is kept or used in the process. +This ref (`refs/merge-requests/:iid/merge`) isn't necessarily overwritten when submitting +requests to this API, though it'll make sure the ref has the latest possible state. -If the merge request has conflicts, is empty or already merged, -you'll get a `400` and a descriptive error message. If you don't have permissions to do so, -you'll get a `403`. +If the merge request has conflicts, is empty or already merged, you'll get a `400` and a descriptive error message. -It returns the HEAD commit of `refs/merge-requests/:iid/merge` in the response body in -case of `200`. +It returns the HEAD commit of `refs/merge-requests/:iid/merge` in the response body in case of `200`. ``` -PUT /projects/:id/merge_requests/:merge_request_iid/merge_to_ref +GET /projects/:id/merge_requests/:merge_request_iid/merge_ref ``` Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user - `merge_request_iid` (required) - Internal ID of MR -- `merge_commit_message` (optional) - Custom merge commit message ```json { diff --git a/doc/api/search.md b/doc/api/search.md index da81c8321c9..abb77ae05dc 100644 --- a/doc/api/search.md +++ b/doc/api/search.md @@ -285,7 +285,7 @@ Example response: ### Scope: wiki_blobs **[STARTER]** -This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled. +This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled. ```bash curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=wiki_blobs&search=bye @@ -346,6 +346,7 @@ Example response: This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled. Filters are available for this scope: + - filename - path - extension @@ -679,6 +680,7 @@ Example response: This scope is available only if [Elasticsearch](../integration/elasticsearch.md) is enabled. Filters are available for this scope: + - filename - path - extension diff --git a/doc/api/settings.md b/doc/api/settings.md index c2a1f7feefd..b01cec64837 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -142,8 +142,6 @@ are listed in the descriptions of the relevant settings. | `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. | | `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. | | `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. | -| `clientside_sentry_dsn` | string | required by: `clientside_sentry_enabled` | Clientside Sentry Data Source Name. | -| `clientside_sentry_enabled` | boolean | no | (**If enabled, requires:** `clientside_sentry_dsn`) Enable Sentry error reporting for the client side. | | `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes. | | `default_artifacts_expire_in` | string | no | Set the default expiration time for each job's artifacts. | | `default_branch_protection` | integer | no | Determine if developers can push to master. Can take: `0` _(not protected, both developers and maintainers can push new commits, force push, or delete the branch)_, `1` _(partially protected, developers and maintainers can push new commits, but cannot force push or delete the branch)_ or `2` _(fully protected, developers cannot push new commits, but maintainers can; no-one can force push or delete the branch)_ as a parameter. Default is `2`. | @@ -212,8 +210,6 @@ are listed in the descriptions of the relevant settings. | `restricted_visibility_levels` | array of strings | no | Selected levels cannot be used by non-admin users for groups, projects or snippets. Can take `private`, `internal` and `public` as a parameter. Default is `null` which means there is no restriction. | | `rsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded RSA key. Default is `0` (no restriction). `-1` disables RSA keys. | | `send_user_confirmation_email` | boolean | no | Send confirmation email on sign-up. | -| `sentry_dsn` | string | required by: `sentry_enabled` | Sentry Data Source Name. | -| `sentry_enabled` | boolean | no | (**If enabled, requires:** `sentry_dsn`) Sentry is an error reporting and logging tool which is currently not shipped with GitLab, available at <https://sentry.io>. | | `session_expire_delay` | integer | no | Session duration in minutes. GitLab restart is required to apply changes | | `shared_runners_enabled` | boolean | no | (**If enabled, requires:** `shared_runners_text`) Enable shared runners for new projects. | | `shared_runners_text` | string | required by: `shared_runners_enabled` | Shared runners text. | @@ -231,6 +227,7 @@ are listed in the descriptions of the relevant settings. | `throttle_unauthenticated_enabled` | boolean | no | (**If enabled, requires:** `throttle_unauthenticated_period_in_seconds` and `throttle_unauthenticated_requests_per_period`) Enable unauthenticated request rate limit. Helps reduce request volume (e.g. from crawlers or abusive bots). | | `throttle_unauthenticated_period_in_seconds` | integer | required by: `throttle_unauthenticated_enabled` | Rate limit period in seconds. | | `throttle_unauthenticated_requests_per_period` | integer | required by: `throttle_unauthenticated_enabled` | Max requests per period per IP. | +| `time_tracking_limit_to_hours` | boolean | no | Limit display of time tracking units to hours. Default is `false`. | | `two_factor_grace_period` | integer | required by: `require_two_factor_authentication` | Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication. | | `unique_ips_limit_enabled` | boolean | no | (**If enabled, requires:** `unique_ips_limit_per_user` and `unique_ips_limit_time_window`) Limit sign in from multiple ips. | | `unique_ips_limit_per_user` | integer | required by: `unique_ips_limit_enabled` | Maximum number of ips per user. | diff --git a/doc/ci/README.md b/doc/ci/README.md index 1743c38eb46..da864a0b3cc 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -63,7 +63,7 @@ Once you're familiar with how GitLab CI/CD works, see the for all the attributes you can set and use. NOTE: **Note:** -GitLab CI/CD and [shared runners](runners/README.md#shared-specific-and-group-runners) are enabled in GitLab.com and available for all users, limited only to the [user's pipelines quota](../user/admin_area/settings/continuous_integration.md#extra-shared-runners-pipeline-minutes-quota). +GitLab CI/CD and [shared runners](runners/README.md#shared-specific-and-group-runners) are enabled in GitLab.com and available for all users, limited only to the [user's pipelines quota](../user/admin_area/settings/continuous_integration.md#extra-shared-runners-pipeline-minutes-quota-free-only). ## Configuration diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index b4c4bea6447..efdcaf5a6f5 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -205,7 +205,14 @@ An example project using this approach can be found here: <https://gitlab.com/gi ### Use Docker socket binding -The third approach is to bind-mount `/var/run/docker.sock` into the container so that docker is available in the context of that image. +The third approach is to bind-mount `/var/run/docker.sock` into the +container so that Docker is available in the context of that image. + +NOTE: **Note:** +If you bind the Docker socket [when using GitLab Runner 11.11 or +newer](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests/1261), +you can no longer use `docker:dind` as a service because volume bindings +are done to the services as well, making these incompatible. In order to do that, follow the steps: diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index e4dc289dbdb..f752f942e24 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -489,6 +489,7 @@ it's provided as an environment variable. This is because GitLab Runnner uses ** runtime. ### Using statically-defined credentials + As an example, let's assume that you want to use the `registry.example.com:5000/private/image:latest` image which is private and requires you to login into a private container registry. @@ -566,7 +567,6 @@ for the Runner to match the `DOCKER_AUTH_CONFIG`. For example, if then the `DOCKER_AUTH_CONFIG` must also specify `registry.example.com:5000`. Specifying only `registry.example.com` will not work. - ### Using Credentials Store > Support for using Credentials Store was added in GitLab Runner 9.5. @@ -574,7 +574,7 @@ Specifying only `registry.example.com` will not work. To configure credentials store, follow these steps: 1. To use a credentials store, you need an external helper program to interact with a specific keychain or external store. -Make sure helper program is available in GitLab Runner `$PATH`. + Make sure helper program is available in GitLab Runner `$PATH`. 1. Make GitLab Runner use it. There are two ways to accomplish this. Either: - Create a diff --git a/doc/ci/examples/test-scala-application.md b/doc/ci/examples/test-scala-application.md index 0e33a1ba060..bd899240307 100644 --- a/doc/ci/examples/test-scala-application.md +++ b/doc/ci/examples/test-scala-application.md @@ -47,10 +47,10 @@ deploy: In the above configuration: - The `before_script` installs [SBT](http://www.scala-sbt.org/) and -displays the version that is being used. + displays the version that is being used. - The `test` stage executes SBT to compile and test the project. - - [sbt-scoverage](https://github.com/scoverage/sbt-scoverage) is used as an SBT -plugin to measure test coverage. + - [sbt-scoverage](https://github.com/scoverage/sbt-scoverage) is used as an SBT + plugin to measure test coverage. - The `deploy` stage automatically deploys the project to Heroku using dpl. You can use other versions of Scala and SBT by defining them in diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index df455857dee..1b50273eca2 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -27,8 +27,7 @@ CI/CD's pipelines. Using variables means no hardcoded values. ### Predefined environment variables -GitLab CI/CD has a default set of -[predefined variables](predefined_variables.md) +GitLab CI/CD has a [default set of predefined variables](predefined_variables.md) which can be used without any specification needed. You can call issues numbers, user names, branch names, pipeline and commit IDs, and much more. @@ -36,7 +35,7 @@ pipeline and commit IDs, and much more. Predefined environment variables are the ones that GitLab provides out of the box for the local environment of the Runner. -GitLab reads the .gitlab-ci.yml file, sends the information +GitLab reads the `.gitlab-ci.yml` file, sends the information to the Runner (which runs the script commands), under which the variables are exposed. @@ -44,6 +43,9 @@ For example, two jobs under the same pipeline can share the same `CI_PIPELINE_ID` variable, but each one has its own `CI_JOB_ID` variable. +NOTE: **Note:** +Find here the full [**predefined variables reference table**](predefined_variables.md). + ### Custom environment variables When your use case requires a specific variable, you can @@ -337,7 +339,7 @@ Group-level variables can be added by: 1. Navigating to your group's **Settings > CI/CD** page. 1. Inputing variable types, keys, and values in the **Variables** section. -Any variables of [subgroups](../../user/group/subgroups/index.md) will be inherited recursively. + Any variables of [subgroups](../../user/group/subgroups/index.md) will be inherited recursively. Once you set them, they will be available for all subsequent pipelines. @@ -480,7 +482,7 @@ Below you can find supported syntax reference: > Example: `$VARIABLE == "some value"` - > Example: `$VARIABLE != "some value"` _(added in 11.11)_ + > Example: `$VARIABLE != "some value"` (introduced in GitLab 11.11) You can use equality operator `==` or `!=` to compare a variable content to a string. We support both, double quotes and single quotes to define a string @@ -491,7 +493,7 @@ Below you can find supported syntax reference: > Example: `$VARIABLE == null` - > Example: `$VARIABLE != null` _(added in 11.11)_ + > Example: `$VARIABLE != null` (introduced in GitLab 11.11) It sometimes happens that you want to check whether a variable is defined or not. To do that, you can compare a variable to `null` keyword, like @@ -502,7 +504,7 @@ Below you can find supported syntax reference: > Example: `$VARIABLE == ""` - > Example: `$VARIABLE != ""` _(added in 11.11)_ + > Example: `$VARIABLE != ""` (introduced in GitLab 11.11) If you want to check whether a variable is defined, but is empty, you can simply compare it against an empty string, like `$VAR == ''` or non-empty @@ -512,7 +514,7 @@ Below you can find supported syntax reference: > Example: `$VARIABLE_1 == $VARIABLE_2` - > Example: `$VARIABLE_1 != $VARIABLE_2` _(added in 11.11)_ + > Example: `$VARIABLE_1 != $VARIABLE_2` (introduced in GitLab 11.11) It is possible to compare two variables. This is going to compare values of these variables. @@ -528,11 +530,11 @@ Below you can find supported syntax reference: `$STAGING` value needs to a string, with length higher than zero. Variable that contains only whitespace characters is not an empty variable. -1. Pattern matching _(added in 11.0)_ +1. Pattern matching (introduced in GitLab 11.0) > Example: `$VARIABLE =~ /^content.*/` - > Example: `$VARIABLE_1 !~ /^content.*/` _(added in 11.11)_ + > Example: `$VARIABLE_1 !~ /^content.*/` (introduced in GitLab 11.11) It is possible perform pattern matching against a variable and regular expression. Expression like this evaluates to truth if matches are found @@ -541,7 +543,7 @@ Below you can find supported syntax reference: Pattern matching is case-sensitive by default. Use `i` flag modifier, like `/pattern/i` to make a pattern case-insensitive. -1. Conjunction / Disjunction +1. Conjunction / Disjunction ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27925) in GitLab 12.0) > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"` diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md index 4655eec51de..7dbb9af2869 100644 --- a/doc/ci/variables/predefined_variables.md +++ b/doc/ci/variables/predefined_variables.md @@ -30,7 +30,7 @@ future GitLab releases.** | `CI_BUILDS_DIR` | all | 11.10 | Top-level directory where builds are executed. | | `CI_CONCURRENT_ID` | all | 11.10 | Unique ID of build execution within a single executor. | | `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | Unique ID of build execution within a single executor and project. | -| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch before a push request. | +| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch before a push request. Only populated when there is a merge request associated with the pipeline. | | `CI_COMMIT_DESCRIPTION` | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. | | `CI_COMMIT_MESSAGE` | 10.8 | all | The full commit message. | | `CI_COMMIT_REF_NAME` | 9.0 | all | The branch or tag name for which project is built | diff --git a/doc/development/README.md b/doc/development/README.md index af3207671e6..5df6ec5fd56 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -108,6 +108,7 @@ description: 'Learn how to contribute to GitLab.' - [Database Debugging and Troubleshooting](database_debugging.md) - [Query Count Limits](query_count_limits.md) - [Database helper modules](database_helpers.md) +- [Code comments](code_comments.md) ## Integration guides diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md index f7f36901fe1..aeddad14995 100644 --- a/doc/development/api_graphql_styleguide.md +++ b/doc/development/api_graphql_styleguide.md @@ -198,9 +198,9 @@ abilities as in the Rails app. If the: - Currently authenticated user fails the authorization, the authorized -resource will be returned as `null`. + resource will be returned as `null`. - Resource is part of a collection, the collection will be filtered to -exclude the objects that the user's authorization checks failed against. + exclude the objects that the user's authorization checks failed against. TIP: **Tip:** Try to load only what the currently authenticated user is allowed to @@ -447,7 +447,7 @@ want to validate the abilities for. Alternatively, we can add a `find_object` method that will load the object on the mutation. This would allow you to use the -`authorized_find!` and `authorized_find!` helper methods. +`authorized_find!` helper method. When a user is not allowed to perform the action, or an object is not found, we should raise a @@ -496,4 +496,4 @@ it 'returns a successful response' do expect(response).to have_gitlab_http_status(:success) expect(graphql_mutation_response(:merge_request_set_wip)['errors']).to be_empty end -```
\ No newline at end of file +``` diff --git a/doc/development/code_comments.md b/doc/development/code_comments.md new file mode 100644 index 00000000000..36962eb46d4 --- /dev/null +++ b/doc/development/code_comments.md @@ -0,0 +1,14 @@ +# Code comments + +Whenever you add comment to the code that is expected to be addressed at any time +in future, please create a technical debt issue for it. Then put a link to it +to the code comment you've created. This will allow other developers to quickly +check if a comment is still relevant and what needs to be done to address it. + +Examples: + +```rb +# Deprecated scope until code_owner column has been migrated to rule_type. +# To be removed with https://gitlab.com/gitlab-org/gitlab-ee/issues/11834. +scope :code_owner, -> { where(code_owner: true).or(where(rule_type: :code_owner)) } +``` diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 29e2aa1a581..6123f9f845a 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -60,6 +60,8 @@ from teams other than your own. 1. If your merge request includes backend changes [^1], it must be **approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**. + 1. If your merge request includes database migrations or changes to expensive queries [^2], it must be + **approved by a [database maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_database)**. 1. If your merge request includes frontend changes [^1], it must be **approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_frontend)**. 1. If your merge request includes UX changes [^1], it must be @@ -377,3 +379,4 @@ Largely based on the [thoughtbot code review guide]. [team]: https://about.gitlab.com/team/ [build handbook]: https://about.gitlab.com/handbook/build/handbook/build#how-to-work-with-build [^1]: Please note that specs other than JavaScript specs are considered backend code. +[^2]: We encourage you to seek guidance from a database maintainer if your merge request is potentially introducing expensive queries. It is most efficient to comment on the line of code in question with the SQL queries so they can give their advice. diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md index 8a4aa5dfa7f..6064f59ed10 100644 --- a/doc/development/contributing/merge_request_workflow.md +++ b/doc/development/contributing/merge_request_workflow.md @@ -126,16 +126,16 @@ When writing commit messages, please follow the guidelines below: - The commit subject must contain at least 3 words. - The commit subject should ideally contain up to 50 characters, -and must not be longer than 72 characters. + and must not be longer than 72 characters. - The commit subject must start with a capital letter. - The commit subject must not end with a period. - The commit subject and body must be separated by a blank line. - The commit body must not contain more than 72 characters per line. - Commits that change 30 or more lines across at least 3 files must -describe these changes in the commit body. + describe these changes in the commit body. - The commit subject or body must not contain Emojis. - Use issues and merge requests' full URLs instead of short references, -as they are displayed as plain text outside of GitLab. + as they are displayed as plain text outside of GitLab. - The merge request must not contain more than 10 commit messages. If the guidelines are not met, the MR will not pass the diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index c7fa40af930..418e58b22d5 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -76,10 +76,10 @@ After a given documentation path is aligned across CE and EE, all merge requests affecting that path must be submitted to CE, regardless of the content it has. This means that: -* For **EE-only docs changes**, you only have to submit a CE MR. -* For **EE-only features** that touch both the code and the docs, you have to submit -an EE MR containing all changes, and a CE MR containing only the docs changes -and without a changelog entry. +- For **EE-only docs changes**, you only have to submit a CE MR. +- For **EE-only features** that touch both the code and the docs, you have to submit + an EE MR containing all changes, and a CE MR containing only the docs changes + and without a changelog entry. This might seem like a duplicate effort, but it's only for the short term. A list of the already aligned docs can be found in diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index b8506a72666..ff6dc16d1a0 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -165,8 +165,8 @@ The table below shows what kind of documentation goes where. `doc/topics/topic-name/subtopic-name/index.md` when subtopics become necessary. General user- and admin- related documentation, should be placed accordingly. 1. The directories `/workflow/`, `/university/`, and `/articles/` have -been **deprecated** and the majority their docs have been moved to their correct location -in small iterations. + been **deprecated** and the majority their docs have been moved to their correct location + in small iterations. If you are unsure where a document or a content addition should live, this should not stop you from authoring and contributing. You can use your best judgment and @@ -409,11 +409,20 @@ To indicate the steps of navigation through the UI: ## Images - Place images in a separate directory named `img/` in the same directory where - the `.md` document that you're working on is located. Always prepend their - names with the name of the document that they will be included in. For - example, if there is a document called `twitter.md`, then a valid image name - could be `twitter_login_screen.png`. -- Images should have a specific, non-generic name that will differentiate and describe them properly. + the `.md` document that you're working on is located. +- Images should have a specific, non-generic name that will + differentiate and describe them properly. +- Always add to the end of the file name the GitLab release version + number corresponding to the release milestone the image was added to, + or corresponding to the release the screenshot was taken from, using the + format `image_name_vX_Y.png`. + ([Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/61027) in GitLab 12.1.) +- For example, for a screenshot taken from the pipelines page of + GitLab 11.1, a valid name is `pipelines_v11_1.png`. If you're + adding an illustration that does not include parts of the UI, + add the release number corresponding to the release the image + was added to. Example, for an MR added to 11.1's milestone, + a valid name for an illustration is `devops_diagram_v11_1.png`. - Keep all file names in lower case. - Consider using PNG images instead of JPEG. - Compress all images with <https://tinypng.com/> or similar tool. @@ -426,7 +435,7 @@ To indicate the steps of navigation through the UI: Inside the document: - The Markdown way of using an image inside a document is: - `![Proper description what the image is about](img/document_image_title.png)` + `![Proper description what the image is about](img/document_image_title_vX_Y.png)` - Always use a proper description for what the image is about. That way, when a browser fails to show the image, this text will be used as an alternative description. diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index cca52706ddc..6b416cf588c 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -909,11 +909,12 @@ import bundle from 'ee_else_ce/protected_branches/protected_branches_bundle.js'; See the frontend guide [performance section](fe_guide/performance.md) for information on managing page-specific javascript within EE. - ## Vue code in `assets/javascript` + ### script tag #### Child Component only used in EE + To separate Vue template differences we should [async import the components](https://vuejs.org/v2/guide/components-dynamic-async.html#Async-Components). Doing this allows for us to load the correct component in EE whilst in CE @@ -937,10 +938,12 @@ export default { ``` #### For JS code that is EE only, like props, computed properties, methods, etc, we will keep the current approach - - Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) alias we already have for webpack. + +- Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) alias we already have for webpack. - This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin ##### Example: + ```javascript import mixin from 'ee_else_ce/path/mixin'; @@ -955,6 +958,7 @@ import mixin from 'ee_else_ce/path/mixin'; - You can see an MR with an example [here](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/9762) #### `template` tag + * **EE Child components** - Since we are using the async loading to check which component to load, we'd still use the component's name, check [this example](#child-component-only-used-in-ee). @@ -962,11 +966,12 @@ import mixin from 'ee_else_ce/path/mixin'; - For the templates that have extra HTML in EE we should move it into a new component and use the `ee_else_ce` dynamic import ### Non Vue Files + For regular JS files, the approach is similar. 1. We will keep using the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) helper, this means that EE only code should be inside the `ee/` folder. - 1. An EE file should be created with the EE only code, and it should extend the CE counterpart. - 1. For code inside functions that can't be extended, the code should be moved into a new file and we should use `ee_else_ce` helper: + 1. An EE file should be created with the EE only code, and it should extend the CE counterpart. + 1. For code inside functions that can't be extended, the code should be moved into a new file and we should use `ee_else_ce` helper: ##### Example: @@ -996,6 +1001,7 @@ to isolate such ruleset from rest of CE rules (along with adding comment describ to avoid conflicts during CE to EE merge. #### Bad + ```scss .section-body { .section-title { @@ -1011,6 +1017,7 @@ to avoid conflicts during CE to EE merge. ``` #### Good + ```scss .section-body { .section-title { diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md index c8c70fa7216..94b3796f6e9 100644 --- a/doc/development/elasticsearch.md +++ b/doc/development/elasticsearch.md @@ -64,20 +64,25 @@ All indexing after the initial one is done via `ElasticIndexerWorker` (sidekiq j Search queries are generated by the concerns found in [ee/app/models/concerns/elastic](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/models/concerns/elastic). These concerns are also in charge of access control, and have been a historic source of security bugs so please pay close attention to them! ## Existing Analyzers/Tokenizers/Filters + These are all defined in https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/elasticsearch/git/model.rb ### Analyzers + #### `path_analyzer` + Used when indexing blobs' paths. Uses the `path_tokenizer` and the `lowercase` and `asciifolding` filters. Please see the `path_tokenizer` explanation below for an example. #### `sha_analyzer` + Used in blobs and commits. Uses the `sha_tokenizer` and the `lowercase` and `asciifolding` filters. Please see the `sha_tokenizer` explanation later below for an example. #### `code_analyzer` + Used when indexing a blob's filename and content. Uses the `whitespace` tokenizer and the filters: `code`, `edgeNGram_filter`, `lowercase`, and `asciifolding` The `whitespace` tokenizer was selected in order to have more control over how tokens are split. For example the string `Foo::bar(4)` needs to generate tokens like `Foo` and `bar(4)` in order to be properly searched. @@ -85,15 +90,19 @@ The `whitespace` tokenizer was selected in order to have more control over how t Please see the `code` filter for an explanation on how tokens are split. #### `code_search_analyzer` + Not directly used for indexing, but rather used to transform a search input. Uses the `whitespace` tokenizer and the `lowercase` and `asciifolding` filters. ### Tokenizers + #### `sha_tokenizer` + This is a custom tokenizer that uses the [`edgeNGram` tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-edgengram-tokenizer.html) to allow SHAs to be searcheable by any sub-set of it (minimum of 5 chars). -example: +Example: `240c29dc7e` becomes: + - `240c2` - `240c29` - `240c29d` @@ -102,21 +111,26 @@ example: - `240c29dc7e` #### `path_tokenizer` + This is a custom tokenizer that uses the [`path_hierarchy` tokenizer](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-pathhierarchy-tokenizer.html) with `reverse: true` in order to allow searches to find paths no matter how much or how little of the path is given as input. -example: +Example: `'/some/path/application.js'` becomes: + - `'/some/path/application.js'` - `'some/path/application.js'` - `'path/application.js'` - `'application.js'` ### Filters + #### `code` -Uses a [Pattern Capture token filter](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-pattern-capture-tokenfilter.html) to split tokens into more easily searched versions of themselves. + +Uses a [Pattern Capture token filter](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-pattern-capture-tokenfilter.html) to split tokens into more easily searched versions of themselves. Patterns: + - `"(\\p{Ll}+|\\p{Lu}\\p{Ll}+|\\p{Lu}+)"`: captures CamelCased and lowedCameCased strings as separate tokens - `"(\\d+)"`: extracts digits - `"(?=([\\p{Lu}]+[\\p{L}]+))"`: captures CamelCased strings recursively. Ex: `ThisIsATest` => `[ThisIsATest, IsATest, ATest, Test]` @@ -126,6 +140,7 @@ Patterns: - `'\/?([^\/]+)(?=\/|\b)'`: separate path terms `like/this/one` #### `edgeNGram_filter` + Uses an [Edge NGram token filter](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/analysis-edgengram-tokenfilter.html) to allow inputs with only parts of a token to find the token. For example it would turn `glasses` into permutations starting with `gl` and ending with `glasses`, which would allow a search for "`glass`" to find the original token `glasses` ## Gotchas @@ -140,13 +155,13 @@ Uses an [Edge NGram token filter](https://www.elastic.co/guide/en/elasticsearch/ You might get an error such as ``` -[2018-10-31T15:54:19,762][WARN ][o.e.c.r.a.DiskThresholdMonitor] [pval5Ct] - flood stage disk watermark [95%] exceeded on - [pval5Ct7SieH90t5MykM5w][pval5Ct][/usr/local/var/lib/elasticsearch/nodes/0] free: 56.2gb[3%], +[2018-10-31T15:54:19,762][WARN ][o.e.c.r.a.DiskThresholdMonitor] [pval5Ct] + flood stage disk watermark [95%] exceeded on + [pval5Ct7SieH90t5MykM5w][pval5Ct][/usr/local/var/lib/elasticsearch/nodes/0] free: 56.2gb[3%], all indices on this node will be marked read-only ``` -This is because you've exceeded the disk space threshold - it thinks you don't have enough disk space left, based on the default 95% threshold. +This is because you've exceeded the disk space threshold - it thinks you don't have enough disk space left, based on the default 95% threshold. In addition, the `read_only_allow_delete` setting will be set to `true`. It will block indexing, `forcemerge`, etc @@ -158,16 +173,16 @@ Add this to your `elasticsearch.yml` file: ``` # turn off the disk allocator -cluster.routing.allocation.disk.threshold_enabled: false +cluster.routing.allocation.disk.threshold_enabled: false ``` _or_ ``` # set your own limits -cluster.routing.allocation.disk.threshold_enabled: true +cluster.routing.allocation.disk.threshold_enabled: true cluster.routing.allocation.disk.watermark.flood_stage: 5gb # ES 6.x only -cluster.routing.allocation.disk.watermark.low: 15gb +cluster.routing.allocation.disk.watermark.low: 15gb cluster.routing.allocation.disk.watermark.high: 10gb ``` diff --git a/doc/development/geo.md b/doc/development/geo.md index 6e59fab34c7..a10f13b069f 100644 --- a/doc/development/geo.md +++ b/doc/development/geo.md @@ -14,10 +14,10 @@ Geo handles replication for different components: - [Database](#database-replication): includes the entire application, except cache and jobs. - [Git repositories](#repository-replication): includes both projects and wikis. - [Uploaded blobs](#uploads-replication): includes anything from images attached on issues -to raw logs and assets from CI. + to raw logs and assets from CI. With the exception of the Database replication, on a *secondary* node, everything is coordinated -by the [Geo Log Cursor](#geo-log-cursor). +by the [Geo Log Cursor](#geo-log-cursor). ### Geo Log Cursor daemon @@ -31,8 +31,8 @@ picks the event up and schedules a `Geo::ProjectSyncWorker` job which will use the `Geo::RepositorySyncService` and `Geo::WikiSyncService` classes to update the repository and the wiki respectively. -The Geo Log Cursor daemon can operate in High Availability mode automatically. -The daemon will try to acquire a lock from time to time and once acquired, it +The Geo Log Cursor daemon can operate in High Availability mode automatically. +The daemon will try to acquire a lock from time to time and once acquired, it will behave as the *active* daemon. Any additional running daemons on the same node, will be in standby @@ -164,20 +164,20 @@ The Git Push Proxy exists as a functionality built inside the `gitlab-shell` com It is active on a **secondary** node only. It allows the user that has cloned a repository from the secondary node to push to the same URL. -Git `push` requests directed to a **secondary** node will be sent over to the **primary** node, +Git `push` requests directed to a **secondary** node will be sent over to the **primary** node, while `pull` requests will continue to be served by the **secondary** node for maximum efficiency. HTTPS and SSH requests are handled differently: - With HTTPS, we will give the user a `HTTP 302 Redirect` pointing to the project on the **primary** node. -The git client is wise enough to understand that status code and process the redirection. + The git client is wise enough to understand that status code and process the redirection. - With SSH, because there is no equivalent way to perform a redirect, we have to proxy the request. -This is done inside [`gitlab-shell`](https://gitlab.com/gitlab-org/gitlab-shell), by first translating the request -to the HTTP protocol, and then proxying it to the **primary** node. + This is done inside [`gitlab-shell`](https://gitlab.com/gitlab-org/gitlab-shell), by first translating the request + to the HTTP protocol, and then proxying it to the **primary** node. -The [`gitlab-shell`](https://gitlab.com/gitlab-org/gitlab-shell) daemon knows when to proxy based on the response -from `/api/v4/allowed`. A special `HTTP 300` status code is returned and we execute a "custom action", -specified in the response body. The response contains additional data that allows the proxied `push` operation +The [`gitlab-shell`](https://gitlab.com/gitlab-org/gitlab-shell) daemon knows when to proxy based on the response +from `/api/v4/allowed`. A special `HTTP 300` status code is returned and we execute a "custom action", +specified in the response body. The response contains additional data that allows the proxied `push` operation to happen on the **primary** node. ## Using the Tracking Database @@ -229,17 +229,17 @@ named `gitlab_secondary`. This configuration exists within the database's user context only. To access the `gitlab_secondary`, GitLab needs to use the same database user that had previously been configured. -The Geo Tracking Database accesses the readonly database replica via FDW as a regular user, -limited by its own restrictions. The credentials are configured as a -`USER MAPPING` associated with the `SERVER` mapped previously +The Geo Tracking Database accesses the readonly database replica via FDW as a regular user, +limited by its own restrictions. The credentials are configured as a +`USER MAPPING` associated with the `SERVER` mapped previously (`gitlab_secondary`). FDW configuration and credentials definition are managed automatically by the -Omnibus GitLab `gitlab-ctl reconfigure` command. +Omnibus GitLab `gitlab-ctl reconfigure` command. #### Refeshing the Foreign Tables -Whenever a new Geo node is configured or the database schema changes on the +Whenever a new Geo node is configured or the database schema changes on the **primary** node, you must refresh the foreign tables on the **secondary** node by running the following: @@ -279,11 +279,11 @@ on the Tracking Database: SELECT project_registry.* FROM project_registry JOIN gitlab_secondary.projects - ON (project_registry.project_id = gitlab_secondary.projects.id + ON (project_registry.project_id = gitlab_secondary.projects.id AND gitlab_secondary.projects.archived IS FALSE) ``` -At the ActiveRecord level, we have additional Models that represent the +At the ActiveRecord level, we have additional Models that represent the foreign tables. They must be mapped in a slightly different way, and they are read-only. Check the existing FDW models in `ee/app/models/geo/fdw` for reference. diff --git a/doc/development/profiling.md b/doc/development/profiling.md index b2f3a105b23..795523b82aa 100644 --- a/doc/development/profiling.md +++ b/doc/development/profiling.md @@ -38,10 +38,6 @@ For routes that require authorization you will need to provide a user to Gitlab::Profiler.profile('/gitlab-org/gitlab-test', user: User.first) ``` -The user you provide will need to have a [personal access -token](https://docs.gitlab.com/ce/user/profile/personal_access_tokens.html) in -the GitLab instance. - Passing a `logger:` keyword argument to `Gitlab::Profiler.profile` will send ActiveRecord and ActionController log output to that logger. Further options are documented with the method source. diff --git a/doc/development/reusing_abstractions.md b/doc/development/reusing_abstractions.md index 01cedf734fb..59da02ed6fd 100644 --- a/doc/development/reusing_abstractions.md +++ b/doc/development/reusing_abstractions.md @@ -127,6 +127,42 @@ Everything in `lib/api`. Everything that resides in `app/services`. +#### ServiceResponse + +Service classes usually have an `execute` method, which can return a +`ServiceResponse`. You can use `ServiceResponse.success` and +`ServiceResponse.error` to return a response in `execute` method. + +In a successful case: + +``` ruby +response = ServiceResponse.success(message: 'Branch was deleted') + +response.success? # => true +response.error? # => false +response.status # => :success +response.message # => 'Branch was deleted' +``` + +In a failed case: + +``` ruby +response = ServiceResponse.error(message: 'Unsupported operation') + +response.success? # => false +response.error? # => true +response.status # => :error +response.message # => 'Unsupported operation' +``` + +An additional payload can also be attached: + +``` ruby +response = ServiceResponse.success(payload: { issue: issue }) + +response.payload[:issue] # => issue +``` + ### Finders Everything in `app/finders`, typically used for retrieving data from a database. diff --git a/doc/development/testing_guide/end_to_end/dynamic_element_validation.md b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md index f7b3ca8bc89..aec0a3ede5a 100644 --- a/doc/development/testing_guide/end_to_end/dynamic_element_validation.md +++ b/doc/development/testing_guide/end_to_end/dynamic_element_validation.md @@ -5,8 +5,8 @@ We devised a solution to solve common test automation problems such as the dread Other problems that dynamic element validations solve are... - When we perform an action with the mouse, we expect something to occur. -- When our test is navigating to (or from) a page, we ensure that we are on the page we expect before -test continuation. +- When our test is navigating to (or from) a page, we ensure that we are on the page we expect before + test continuation. ## How it works @@ -19,7 +19,7 @@ We interpret user actions on the page to have some sort of effect. These actions When a page is navigated to, there are elements that will always appear on the page unconditionally. -Dynamic element validation is instituted when using +Dynamic element validation is instituted when using ```ruby Runtime::Browser.visit(:gitlab, Some::Page) @@ -27,7 +27,7 @@ Runtime::Browser.visit(:gitlab, Some::Page) ### Clicks -When we perform a click within our tests, we expect something to occur. That something could be a component to now +When we perform a click within our tests, we expect something to occur. That something could be a component to now appear on the webpage, or the test to navigate away from the page entirely. Dynamic element validation is instituted when using @@ -71,7 +71,7 @@ class MyPage < Page::Base element :another_element, required: true element :conditional_element end - + def open_layer click_element :my_element, Layer::MyLayer end @@ -95,7 +95,7 @@ execute_stuff ``` will invoke GitLab QA to scan `MyPage` for `my_element` and `another_element` to be on the page before continuing to -`execute_stuff` +`execute_stuff` ### Clicking diff --git a/doc/development/testing_guide/end_to_end/page_objects.md b/doc/development/testing_guide/end_to_end/page_objects.md index 73e1fd862c1..05cb03eb4bd 100644 --- a/doc/development/testing_guide/end_to_end/page_objects.md +++ b/doc/development/testing_guide/end_to_end/page_objects.md @@ -82,7 +82,7 @@ module Page end # ... - end + end end end ``` @@ -134,7 +134,7 @@ for each element defined. In our case, `qa-login-field`, `qa-password-field` and `qa-sign-in-button` -**app/views/my/view.html.haml** +**app/views/my/view.html.haml** ```haml = f.text_field :login, class: "form-control top qa-login-field", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." @@ -146,7 +146,7 @@ Things to note: - The CSS class must be `kebab-cased` (separated with hyphens "`-`") - If the element appears on the page unconditionally, add `required: true` to the element. See -[Dynamic element validation](dynamic_element_validation.md) + [Dynamic element validation](dynamic_element_validation.md) ## Running the test locally diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index 4c9d1684c00..28ebb6f0f64 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -27,14 +27,30 @@ we need to solve before being able to use Jest for all our needs. ### Differences to Karma - Jest runs in a Node.js environment, not in a browser. Support for running Jest tests in a browser [is planned](https://gitlab.com/gitlab-org/gitlab-ce/issues/58205). -- Because Jest runs in a Node.js environment, it uses [jsdom](https://github.com/jsdom/jsdom) by default. +- Because Jest runs in a Node.js environment, it uses [jsdom](https://github.com/jsdom/jsdom) by default. See also its [limitations](#limitations-of-jsdom) below. +- Jest does not have access to Webpack loaders or aliases. + The aliases used by Jest are defined in its [own config](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/jest.config.js). - All calls to `setTimeout` and `setInterval` are mocked away. See also [Jest Timer Mocks](https://jestjs.io/docs/en/timer-mocks). - `rewire` is not required because Jest supports mocking modules. See also [Manual Mocks](https://jestjs.io/docs/en/manual-mocks). +- No [context object](https://jasmine.github.io/tutorials/your_first_suite#section-The_%3Ccode%3Ethis%3C/code%3E_keyword) is passed to tests in Jest. + This means sharing `this.something` between `beforeEach()` and `it()` for example does not work. + Instead you should declare shared variables in the context that they are needed (via `const` / `let`). - The following will cause tests to fail in Jest: - Unmocked requests. - Unhandled Promise rejections. - Calls to `console.warn`, including warnings from libraries like Vue. +### Limitations of jsdom + +As mentioned [above](#differences-to-karma), Jest uses jsdom instead of a browser for running tests. +This comes with a number of limitations, namely: + +- [No scrolling support](https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/browser/Window.js#L623-L625) +- [No element sizes or positions](https://github.com/jsdom/jsdom/blob/15.1.1/lib/jsdom/living/nodes/Element-impl.js#L334-L371) +- [No layout engine](https://github.com/jsdom/jsdom/issues/1322) in general + +See also the issue for [support running Jest tests in browsers](https://gitlab.com/gitlab-org/gitlab-ce/issues/58205). + ### Debugging Jest tests Running `yarn jest-debug` will run Jest in debug mode, allowing you to debug/inspect as described in the [Jest docs](https://jestjs.io/docs/en/troubleshooting#tests-are-failing-and-you-don-t-know-why). diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 92122fca7dd..68c1bcbc801 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -9,7 +9,7 @@ as the hardware requirements that are needed to install and use GitLab. ## Operating Systems -### Supported Unix distributions +### Supported Linux distributions - Ubuntu - Debian @@ -21,7 +21,7 @@ as the hardware requirements that are needed to install and use GitLab. For the installations options, see [the main installation page](README.md). -### Unsupported Unix distributions +### Unsupported Linux distributions and Unix-like operating systems - Arch Linux - Fedora @@ -29,13 +29,13 @@ For the installations options, see [the main installation page](README.md). - Gentoo - macOS -On the above unsupported distributions is still possible to install GitLab yourself. +Installation of GitLab on these operating systems is possible, but not supported. Please see the [installation from source guide](installation.md) and the [installation guides](https://about.gitlab.com/installation/) for more information. -### Non-Unix operating systems such as Windows +### Microsoft Windows -GitLab is developed for Unix operating systems. -It does **not** run on Windows, and we have no plans to support it in the near future. For the latest development status view this [issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/46567). +GitLab is developed for Linux-based operating systems. +It does **not** run on Microsoft Windows, and we have no plans to support it in the near future. For the latest development status view this [issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/46567). Please consider using a virtual machine to run GitLab. ## Ruby versions diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md index 5950737b964..e6496ae3a2e 100644 --- a/doc/integration/jenkins.md +++ b/doc/integration/jenkins.md @@ -25,22 +25,22 @@ and [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF ## Use cases - Suppose you are new to GitLab, and want to keep using Jenkins until you prepare -your projects to build with [GitLab CI/CD](../ci/README.md). You set up the -integration between GitLab and Jenkins, then you migrate to GitLab CI later. While -you organize yourself and your team to onboard GitLab, you keep your pipelines -running with Jenkins, but view the results in your project's repository in GitLab. + your projects to build with [GitLab CI/CD](../ci/README.md). You set up the + integration between GitLab and Jenkins, then you migrate to GitLab CI later. While + you organize yourself and your team to onboard GitLab, you keep your pipelines + running with Jenkins, but view the results in your project's repository in GitLab. - Your team uses [Jenkins Plugins](https://plugins.jenkins.io/) for other proceedings, -therefore, you opt for keep using Jenkins to build your apps. Show the results of your -pipelines directly in GitLab. + therefore, you opt for keep using Jenkins to build your apps. Show the results of your + pipelines directly in GitLab. For a real use case, read the blog post [Continuous integration: From Jenkins to GitLab using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/). ## Requirements -* [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin) -* [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin) -* Git clone access for Jenkins from the GitLab repository -* GitLab API access to report build status +- [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin) +- [Jenkins Git Plugin](https://wiki.jenkins.io/display/JENKINS/Git+Plugin) +- Git clone access for Jenkins from the GitLab repository +- GitLab API access to report build status ## Configure GitLab users @@ -65,7 +65,7 @@ Go to Manage Jenkins -> Configure System and scroll down to the 'GitLab' section Enter the GitLab server URL in the 'GitLab host URL' field and paste the API token copied earlier in the 'API Token' field. -For more information, see GitLab Plugin documentation about +For more information, see GitLab Plugin documentation about [Jenkins-to-GitLab authentication](https://github.com/jenkinsci/gitlab-plugin#jenkins-to-gitlab-authentication) ![Jenkins GitLab plugin configuration](img/jenkins_gitlab_plugin_config.png) @@ -76,8 +76,8 @@ Follow the GitLab Plugin documentation about [Jenkins Job Configuration](https:/ NOTE: **Note:** Be sure to include the steps about [Build status configuration](https://github.com/jenkinsci/gitlab-plugin#build-status-configuration). -The 'Publish build status to GitLab' post-build step is required to view -Jenkins build status in GitLab Merge Requests. +The 'Publish build status to GitLab' post-build step is required to view +Jenkins build status in GitLab Merge Requests. ## Configure a GitLab project @@ -114,21 +114,21 @@ and storing build status for Commits and Merge Requests. All steps are implemented using AJAX requests on the merge request page. 1. In order to display the build status in a merge request you must create a project service in GitLab. -2. Your project service will do a (JSON) query to a URL of the CI tool with the SHA1 of the commit. -3. The project service builds this URL and payload based on project service settings and knowledge of the CI tool. -4. The response is parsed to give a response in GitLab (success/failed/pending). +1. Your project service will do a (JSON) query to a URL of the CI tool with the SHA1 of the commit. +1. The project service builds this URL and payload based on project service settings and knowledge of the CI tool. +1. The response is parsed to give a response in GitLab (success/failed/pending). ## Troubleshooting ### Error in merge requests - "Could not connect to the CI server" This integration relies on Jenkins reporting the build status back to GitLab via -the [Commit Status API](../api/commits.md#commit-status). +the [Commit Status API](../api/commits.md#commit-status). The error 'Could not connect to the CI server' usually means that GitLab did not receive a build status update via the API. Either Jenkins was not properly -configured or there was an error reporting the status via the API. +configured or there was an error reporting the status via the API. 1. [Configure the Jenkins server](#configure-the-jenkins-server) for GitLab API access -2. [Configure a Jenkins project](#configure-a-jenkins-project), including the - 'Publish build status to GitLab' post-build action. +1. [Configure a Jenkins project](#configure-a-jenkins-project), including the + 'Publish build status to GitLab' post-build action. diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md index 37051f6b10f..1e9c53b9811 100644 --- a/doc/subscriptions/index.md +++ b/doc/subscriptions/index.md @@ -14,7 +14,7 @@ Learn how GitLab helps you in the stages of the DevOps lifecycle by learning mor ### Self-managed: Install GitLab -Take a look at [installing GitLab](https://about.gitlab.com/install/) and our [administrator documentation](../administration/index.md). Then, follow the instructions below under [Your subscription](#your-subscription) to apply your license file. +Take a look at [installing GitLab](https://about.gitlab.com/install/) and our [administrator documentation](../administration/index.md). Then, follow the instructions below under [Your subscription](#your-subscription) to apply your license file. ### GitLab.com: Create a user and group @@ -74,11 +74,11 @@ Please note that you need to be a group owner to associate a group to your subsc To see the status of your GitLab.com subscription, you can click on the Billings section of the relevant namespace: -* For individuals, this is located at https://gitlab.com/profile/billings under -in your Settings, -* For groups, this is located under the group's Settings dropdown, under Billing. +- For individuals, this is located at https://gitlab.com/profile/billings under + in your Settings, +- For groups, this is located under the group's Settings dropdown, under Billing. -For groups, you can see details of your subscription - including your current +For groups, you can see details of your subscription - including your current plan - in the included table: ![Billing table](billing_table.png) @@ -86,11 +86,11 @@ plan - in the included table: | Field | Description | | ------ | ------ | | Seats in subscription | If this is a paid plan, this represents the number of seats you've paid to support in your group. | -| Seats currently in use | The number of active seats currently in use. | -| Max seats used | The highest number of seats you've used. If this exceeds the seats in subscription, you may owe an additional fee for the additional users. | -| Seats owed | If your max seats used exceeds the seats in your subscription, you'll owe an additional fee for the users you've added. | -| Subscription start date | The date your subscription started. If this is for a Free plan, this is the date you transitioned off your group's paid plan. | -| Subscription end date | The date your current subscription will end. This does not apply to Free plans. | +| Seats currently in use | The number of active seats currently in use. | +| Max seats used | The highest number of seats you've used. If this exceeds the seats in subscription, you may owe an additional fee for the additional users. | +| Seats owed | If your max seats used exceeds the seats in your subscription, you'll owe an additional fee for the users you've added. | +| Subscription start date | The date your subscription started. If this is for a Free plan, this is the date you transitioned off your group's paid plan. | +| Subscription end date | The date your current subscription will end. This does not apply to Free plans. | ### Subscription changes and your data diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 702245b22a0..41d12128e51 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -723,7 +723,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From Gitlab 11.11, this variable can be used to set a username to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD) | | `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From Gitlab 11.11, this variable can be used to set a password to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_USERNAME) | | `REPLICAS` | The number of replicas to deploy; defaults to 1. | -| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1. | +| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. Takes precedence over `REPLICAS` and defaults to 1. For zero downtime upgrades, set to 2 or greater. | | `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md); defaults to 1 | | `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 | | `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. | diff --git a/doc/university/process/README.md b/doc/university/process/README.md index fdf6224f7f6..b278e02ccd5 100644 --- a/doc/university/process/README.md +++ b/doc/university/process/README.md @@ -9,11 +9,11 @@ title: University | Process # Suggesting improvements If you would like to teach a class or participate or help in any way please -submit a merge request and assign it to [Job](https://gitlab.com/u/JobV). +submit a merge request and assign it to [Job](https://gitlab.com/JobV). If you have suggestions for additional courses you would like to see, please submit a merge request to add an upcoming class, assign to -[Chad](https://gitlab.com/u/chadmalchow) and /cc [Job](https://gitlab.com/u/JobV). +[Chad](https://gitlab.com/chadmalchow) and /cc [Job](https://gitlab.com/JobV). ## Adding classes @@ -31,4 +31,4 @@ please submit a merge request to add an upcoming class, assign to 1. Please upload any video recordings to our Youtube channel. We prefer them to be public, if needed they can be unlisted but if so they should be linked from this page. -1. Please create a merge request and assign to [Erica](https://gitlab.com/u/Erica). +1. Please create a merge request and assign to [Erica](https://gitlab.com/Erica). diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index add5971e01c..fa60ee96cf7 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -186,7 +186,7 @@ the sort order to *Last Contacted* from the dropdown beside the search field. To search Runners' descriptions: 1. In the **Search or filter results...** field, type the description of the Runner you want to -find. + find. 1. Press Enter. You can also filter Runners by status, type, and tag. To filter: diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md index fde7d1aeaf7..84596ff6a2c 100644 --- a/doc/user/admin_area/settings/continuous_integration.md +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -94,14 +94,11 @@ a group in the **Usage Quotas** page available to the group page settings list. ![Group pipelines quota](img/group_pipelines_quota.png) +## Extra Shared Runners pipeline minutes quota **[FREE ONLY]** -## Extra Shared Runners pipeline minutes quota - -NOTE: **Note:** -Only available on GitLab.com. - -You can purchase additional CI minutes so your pipelines will not be blocked after you have -used all your CI minutes from your main quota. +If you're using GitLab.com, you can purchase additional CI minutes so your +pipelines will not be blocked after you have used all your CI minutes from your +main quota. In order to purchase additional minutes, you should follow these steps: @@ -110,27 +107,27 @@ In order to purchase additional minutes, you should follow these steps: ![Buy additional minutes](img/buy_btn.png) 1. Locate the subscription card that is linked to your group on GitLab.com, -click on **Buy more CI minutes**, and complete the details about the transaction. + click on **Buy more CI minutes**, and complete the details about the transaction. ![Buy additional minutes](img/buy_minutes_card.png) 1. Once we have processed your payment, the extra CI minutes -will be synced to your Group and you can visualize it from the -**Group > Settings > Pipelines quota** page: + will be synced to your Group and you can visualize it from the + **Group > Settings > Pipelines quota** page: ![Additional minutes](img/additional_minutes.png) Be aware that: 1. If you have purchased extra CI minutes before the purchase of a paid plan, -we will calculate a pro-rated charge for your paid plan. That means you may -be charged for less than one year since your subscription was previously -created with the extra CI minutes. + we will calculate a pro-rated charge for your paid plan. That means you may + be charged for less than one year since your subscription was previously + created with the extra CI minutes. 1. Once the extra CI minutes has been assigned to a Group they cannot be transferred -to a different Group. + to a different Group. 1. If you have some minutes used over your default quota, these minutes will -be deducted from your Additional Minutes quota immediately after your purchase of additional -minutes. + be deducted from your Additional Minutes quota immediately after your purchase of additional + minutes. ## What happens when my CI minutes quota run out diff --git a/doc/user/admin_area/settings/terms.md b/doc/user/admin_area/settings/terms.md index a5f8d05f662..a1bce5a6c69 100644 --- a/doc/user/admin_area/settings/terms.md +++ b/doc/user/admin_area/settings/terms.md @@ -17,7 +17,7 @@ To enforce acceptance of a Terms of Service and Privacy Policy: 1. Go to **Admin Area > Settings > General**. 1. Expand the **Terms of Service and Privacy Policy** section. 1. Check the **Require all users to accept Terms of Service and Privacy Policy when they access -GitLab.** checkbox. + GitLab.** checkbox. 1. Input the text of the **Terms of Service and Privacy Policy**. Markdown formatting can be used in this input box. 1. Click **Save changes**. 1. When you are presented with the **Terms of Service** statement, click **Accept terms**. diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md index a1229484388..63879935fd8 100644 --- a/doc/user/admin_area/settings/visibility_and_access_controls.md +++ b/doc/user/admin_area/settings/visibility_and_access_controls.md @@ -4,12 +4,15 @@ type: reference # Visibility and access controls -GitLab allows admins to: +GitLab allows administrators to: - Control access and visibility to GitLab resources including branches and projects. - Select from which hosting sites code can be imported into GitLab. - Select the protocols permitted to access GitLab. - Enable or disable repository mirroring. +- Prevent non-administrators from deleting projects + ([introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5615) in GitLab 12.0). + **[PREMIUM ONLY]** To access the visibility and access control options: diff --git a/doc/user/application_security/dependency_scanning/analyzers.md b/doc/user/application_security/dependency_scanning/analyzers.md new file mode 100644 index 00000000000..937ded287e5 --- /dev/null +++ b/doc/user/application_security/dependency_scanning/analyzers.md @@ -0,0 +1,133 @@ +# Dependency Scanning Analyzers **[ULTIMATE]** + +Dependency Scanning relies on underlying third party tools that are wrapped into +what we call "Analyzers". An analyzer is a +[dedicated project](https://gitlab.com/gitlab-org/security-products/analyzers) +that wraps a particular tool to: + +- Expose its detection logic. +- Handle its execution. +- Convert its output to the common format. + +This is achieved by implementing the [common API](https://gitlab.com/gitlab-org/security-products/analyzers/common). + +Dependency Scanning supports the following official analyzers: + +- [`bundler-audit`](https://gitlab.com/gitlab-org/security-products/analyzers/bundler-audit) +- [`gemnasium`](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium) +- [`gemnasium-maven`](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-maven) +- [`gemnasium-python`](https://gitlab.com/gitlab-org/security-products/analyzers/gemnasium-python) +- [`retire.js`](https://gitlab.com/gitlab-org/security-products/analyzers/retire.js) + +The analyzers are published as Docker images that Dependency Scanning will use +to launch dedicated containers for each analysis. + +Dependency Scanning is pre-configured with a set of **default images** that are +maintained by GitLab, but users can also integrate their own **custom images**. + +## Official default analyzers + +Any custom change to the official analyzers can be achieved by using an +[environment variable in your `.gitlab-ci.yml`](index.md#customizing-the-dependency-scanning-settings). + +### Using a custom Docker mirror + +You can switch to a custom Docker registry that provides the official analyzer +images under a different prefix. For instance, the following instructs Dependency +Scanning to pull `my-docker-registry/gl-images/gemnasium` +instead of `registry.gitlab.com/gitlab-org/security-products/analyzers/gemnasium`. +In `.gitlab-ci.yml` define: + +```yaml +include: + template: Dependency-Scanning.gitlab-ci.yml + +variables: + DS_ANALYZER_IMAGE_PREFIX: my-docker-registry/gl-images +``` + +This configuration requires that your custom registry provides images for all +the official analyzers. + +### Selecting specific analyzers + +You can select the official analyzers you want to run. Here's how to enable +`bundler-audit` and `gemnasium` while disabling all the other default ones. +In `.gitlab-ci.yml` define: + +```yaml +include: + template: Dependency-Scanning.gitlab-ci.yml + +variables: + DS_DEFAULT_ANALYZERS: "bundler-audit,gemnasium" +``` + +`bundler-audit` runs first. When merging the reports, Dependency Scanning will +remove the duplicates and will keep the `bundler-audit` entries. + +### Disabling default analyzers + +Setting `DS_DEFAULT_ANALYZERS` to an empty string will disable all the official +default analyzers. In `.gitlab-ci.yml` define: + +```yaml +include: + template: Dependency-Scanning.gitlab-ci.yml + +variables: + DS_DEFAULT_ANALYZERS: "" +``` + +That's needed when one totally relies on [custom analyzers](#custom-analyzers). + +## Custom analyzers + +You can provide your own analyzers as a comma separated list of Docker images. +Here's how to add `analyzers/nugget` and `analyzers/perl` to the default images. +In `.gitlab-ci.yml` define: + +```yaml +include: + template: Dependency-Scanning.gitlab-ci.yml + +variables: + DS_ANALYZER_IMAGES: "my-docker-registry/analyzers/nugget,amy-docker-registry/nalyzers/perl" +``` + +The values must be the full path to the container registry images, +like what you would feed to the `docker pull` command. + +NOTE: **Note:** +This configuration doesn't benefit from the integrated detection step. Dependency +Scanning has to fetch and spawn each Docker image to establish whether the +custom analyzer can scan the source code. + +## Analyzers data + +The following table lists the data available for each official analyzer. + +| Property \ Tool | Gemnasium | bundler-audit | Retire.js | +|---------------------------------------|:------------------:|:------------------:|:------------------:| +| Severity | 𐄂 | ✓ | ✓ | +| Title | ✓ | ✓ | ✓ | +| File | ✓ | ⚠ | ✓ | +| Start line | 𐄂 | 𐄂 | 𐄂 | +| End line | 𐄂 | 𐄂 | 𐄂 | +| External ID (e.g., CVE) | ✓ | ✓ | ⚠ | +| URLs | ✓ | ✓ | ✓ | +| Internal doc/explanation | ✓ | 𐄂 | 𐄂 | +| Solution | ✓ | ✓ | 𐄂 | +| Confidence | 𐄂 | 𐄂 | 𐄂 | +| Affected item (e.g. class or package) | ✓ | ✓ | ✓ | +| Source code extract | 𐄂 | 𐄂 | 𐄂 | +| Internal ID | ✓ | 𐄂 | 𐄂 | +| Date | ✓ | 𐄂 | 𐄂 | +| Credits | ✓ | 𐄂 | 𐄂 | + +- ✓ => we have that data +- ⚠ => we have that data but it's partially reliable, or we need to extract that data from unstructured content +- 𐄂 => we don't have that data or it would need to develop specific or inefficient/unreliable logic to obtain it. + +The values provided by these tools are heterogeneous so they are sometimes +normalized into common values (e.g., `severity`, `confidence`, etc). diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index a4e5b19bdc7..ea8b96eb24d 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -46,17 +46,33 @@ this is enabled by default. The following languages and dependency managers are supported. -| Language (package managers) | Scan tool | -|-----------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| -| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general), [Retire.js](https://retirejs.github.io/retire.js) | -| Python ([pip](https://pip.pypa.io/en/stable/)) (only `requirements.txt` supported) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) | -| Ruby ([gem](https://rubygems.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general), [bundler-audit](https://github.com/rubysec/bundler-audit) | -| Java ([Maven](https://maven.apache.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) | -| PHP ([Composer](https://getcomposer.org/)) | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium/general) | - -Some scanners require to send a list of project dependencies to GitLab's central -servers to check for vulnerabilities. To learn more about this or to disable it, -refer to the [GitLab Dependency Scanning tool documentation](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks). +| Language (package managers) | Supported | Scan tool(s) | +|----------------------------- | --------- | ------------ | +| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js) | +| Python ([pip](https://pip.pypa.io/en/stable/)) (only `requirements.txt` supported) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | +| Ruby ([gem](https://rubygems.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [bundler-audit](https://github.com/rubysec/bundler-audit) | +| Java ([Maven](https://maven.apache.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | +| PHP ([Composer](https://getcomposer.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) | +| Python ([poetry](https://poetry.eustace.io/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7006 "Support Poetry in Dependency Scanning")) | not available | +| Python ([Pipfile](https://docs.pipenv.org/en/latest/basics/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/11756 "Pipfile.lock support for Dependency Scanning"))| not available | +| Go ([Golang](https://golang.org/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7132 "Dependency Scanning for Go")) | not available | + +## Remote checks + +While some tools pull a local database to check vulnerabilities, some others +like Gemnasium require sending data to GitLab central servers to analyze them: + +1. Gemnasium scans the dependencies of your project locally and sends a list of + packages to GitLab central servers. +1. The servers return the list of known vulnerabilities for all versions of + these packages. +1. The client picks up the relevant vulnerabilities by comparing with the versions + of the packages that are used by the project. + +The Gemnasium client does **NOT** send the exact package versions your project relies on. + +You can disable the remote checks by [using](#customizing-the-dependency-scanning-settings) +the `DS_DISABLE_REMOTE_CHECKS` environment variable and setting it to `true`. ## Configuring Dependency Scanning @@ -97,17 +113,10 @@ The report will be saved as a that you can later download and analyze. Due to implementation limitations, we always take the latest Dependency Scanning artifact available. -Some security scanners require to send a list of project dependencies to GitLab -central servers to check for vulnerabilities. To learn more about this or to -disable it, check the -[GitLab Dependency Scanning tool documentation](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks). - #### Customizing the Dependency Scanning settings -The Dependency Scanning settings can be changed through environment variables by using the +The Dependency Scanning settings can be changed through [environment variables](#available-variables) by using the [`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. -These variables are documented in the -[Dependency Scanning tool documentation](https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings). For example: @@ -116,7 +125,7 @@ include: template: Dependency-Scanning.gitlab-ci.yml variables: - DEP_SCAN_DISABLE_REMOTE_CHECKS: true + DS_DISABLE_REMOTE_CHECKS: true ``` Because template is [evaluated before](../../../ci/yaml/README.md#include) the pipeline @@ -137,6 +146,24 @@ dependency_scanning: CI_DEBUG_TRACE: "true" ``` +#### Available variables + +Dependency Scanning can be [configured](#customizing-the-dependency-scanning-settings) +using environment variables. + +| Environment variable | Function | +|-------------------------------- |----------| +| `DS_ANALYZER_IMAGES` | Comma separated list of custom images. The official default images are still enabled. Read more about [customizing analyzers](analyzers.md). | +| `DS_ANALYZER_IMAGE_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). | +| `DS_ANALYZER_IMAGE_TAG` | Override the Docker tag of the official default images. Read more about [customizing analyzers](analyzers.md). | +| `DS_DEFAULT_ANALYZERS` | Override the names of the official default images. Read more about [customizing analyzers](analyzers.md). | +| `DS_DISABLE_REMOTE_CHECKS` | Do not send any data to GitLab. Used in the [Gemnasium analyzer](#remote-checks). | +| `DS_PULL_ANALYZER_IMAGES` | Pull the images from the Docker registry (set to `0` to disable). | +| `DS_EXCLUDED_PATHS` | Exclude vulnerabilities from output based on the paths. A comma-separated list of patterns. Patterns can be globs, file or folder paths. Parent directories will also match patterns. | +| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | +| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | +| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. | + ### Manual job definition for GitLab 11.5 and later For GitLab 11.5 and GitLab Runner 11.5 and later, the following `dependency_scanning` @@ -171,7 +198,7 @@ dependency_scanning: dependency_scanning: gl-dependency-scanning-report.json ``` -You can supply many other [settings variables](https://gitlab.com/gitlab-org/security-products/dependency-scanning#settings) +You can supply many other [settings variables](#available-variables) via `docker run --env` to customize your job execution. ### Manual job definition for GitLab 11.4 and earlier (deprecated) @@ -388,6 +415,10 @@ supported by Gemnasium. To see the generated dependency list, navigate to your project's **Project > Dependency List**. +## Versioning and release process + +Please check the [Release Process documentation](https://gitlab.com/gitlab-org/security-products/release/blob/master/docs/release_process.md). + ## Contributing to the vulnerability database You can search the [gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) project diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index 26d764fa2cf..8d4ffd93f59 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -138,14 +138,6 @@ The result will then be: - The Staging cluster will be used for the `deploy to staging` job. - The Production cluster will be used for the `deploy to production` job. -## Unavailable features - -The following features are not currently available for group-level clusters: - -1. Terminals (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55487)). -1. Pod logs (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)). -1. Deployment boards (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55489)). - <!-- ## Troubleshooting Include any troubleshooting steps that you can foresee. If you know beforehand what issues diff --git a/doc/user/group/contribution_analytics/index.md b/doc/user/group/contribution_analytics/index.md index 7e6cb24a51e..0da131ab7bd 100644 --- a/doc/user/group/contribution_analytics/index.md +++ b/doc/user/group/contribution_analytics/index.md @@ -21,9 +21,9 @@ page. ## Use cases - Analyze your team's contributions over a period of time, and offer a bonus for the top -contributors. + contributors. - Identify opportunities for improvement with group members who may benefit from additional -support. + support. ## Using Contribution Analytics diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md index 2e4106f55e5..f53c1dd95d7 100644 --- a/doc/user/group/epics/index.md +++ b/doc/user/group/epics/index.md @@ -202,8 +202,8 @@ You may also consult the [group permissions table][permissions]. ## Thread - Comments: collaborate on that epic by posting comments in its thread. -These text fields also fully support -[GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm). + These text fields also fully support + [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm). ## Comment, or start a discussion @@ -216,7 +216,7 @@ Once you wrote your comment, you can either: - You can [award an emoji](../../award_emojis.md) to that epic or its comments. -## Notifications +## Notifications - [Receive notifications](../../../workflow/notifications.md) for epic events. diff --git a/doc/user/group/index.md b/doc/user/group/index.md index abd95eddf63..183b12b1c73 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -41,12 +41,13 @@ You can create groups for numerous reasons. To name a couple: - Make it easier to `@mention` all of your team at once in issues and merge requests by creating a group and including the appropriate members. For example, you could create a group for your company members, and create a [subgroup](subgroups/index.md) for each individual team. Let's say you create a group called `company-team`, and you create subgroups in this group for the individual teams `backend-team`, `frontend-team`, and `production-team`. - - When you start a new implementation from an issue, you add a comment: - _"`@company-team`, let's do it! `@company-team/backend-team` you're good to go!"_ - - When your backend team needs help from frontend, they add a comment: - _"`@company-team/frontend-team` could you help us here please?"_ - - When the frontend team completes their implementation, they comment: - _"`@company-team/backend-team`, it's done! Let's ship it `@company-team/production-team`!"_ + +- When you start a new implementation from an issue, you add a comment: + _"`@company-team`, let's do it! `@company-team/backend-team` you're good to go!"_ +- When your backend team needs help from frontend, they add a comment: + _"`@company-team/frontend-team` could you help us here please?"_ +- When the frontend team completes their implementation, they comment: + _"`@company-team/backend-team`, it's done! Let's ship it `@company-team/production-team`!"_ ## Namespaces @@ -268,9 +269,10 @@ be unique. To change your group path: -1. Navigate to your group's **Settings > General**. -1. Enter a new name under **Group path**. -1. Click **Save group**. +1. Navigate to your group's **Settings > General** page. +1. Expand the **Path, transfer, remove** section. +1. Enter a new name under **Change group path**. +1. Click **Change group path**. CAUTION: **Caution:** It is currently not possible to rename a namespace if it contains a diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md index 96cc523f4ec..5aef463d782 100644 --- a/doc/user/group/saml_sso/scim_setup.md +++ b/doc/user/group/saml_sso/scim_setup.md @@ -24,27 +24,27 @@ The following identity providers are supported: ## Requirements -- [Group SSO](index.md) needs to be configured. +- [Group SSO](index.md) needs to be configured. - The `scim_group` feature flag must be enabled: Run the following commands in a Rails console: - + ```sh # Omnibus GitLab gitlab-rails console - + # Installation from source cd /home/git/gitlab sudo -u git -H bin/rails console RAILS_ENV=production ``` - + To enable SCIM for a group named `group_name`: - + ```ruby group = Group.find_by_full_path('group_name') Feature.enable(:group_scim, group) ``` - + ### GitLab configuration Once [Single sign-on](index.md) has been configured, we can: @@ -53,7 +53,7 @@ Once [Single sign-on](index.md) has been configured, we can: 1. Click on the **Generate a SCIM token** button. 1. Save the token and URL so they can be used in the next step. -![SCIM token configuration](img/scim_token.png) +![SCIM token configuration](img/scim_token.png) ## SCIM IdP configuration @@ -63,15 +63,15 @@ In the [Single sign-on](index.md) configuration for the group, make sure that the **Name identifier value** (NameID) points to a unique identifier, such as the `user.objectid`. This will match the `extern_uid` used on GitLab. -The GitLab app in Azure needs to be configured following +The GitLab app in Azure needs to be configured following [Azure's SCIM setup](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#getting-started). Note the following: - The `Tenant URL` and `secret token` are the ones retrieved in the -[previous step](#gitlab-configuration). + [previous step](#gitlab-configuration). - Should there be any problems with the availability of GitLab or similar -errors, the notification email set will get those. + errors, the notification email set will get those. - For mappings, we will only leave `Synchronize Azure Active Directory Users to AppName` enabled. You can then test the connection clicking on `Test Connection`. @@ -79,14 +79,14 @@ You can then test the connection clicking on `Test Connection`. ### Synchronize Azure Active Directory users 1. Click on `Synchronize Azure Active Directory Users to AppName`, to configure -the attribute mapping. + the attribute mapping. 1. Select the unique identifier (in the example `objectId`) as the `id` and `externalId`, -and enable the `Create`, `Update`, and `Delete` actions. + and enable the `Create`, `Update`, and `Delete` actions. 1. Map the `userPricipalName` to `emails[type eq "work"].value` and `mailNickname` to -`userName`. + `userName`. Example configuration: - + ![Azure's attribute mapping configuration](img/scim_attribute_mapping.png) 1. Click on **Show advanced options > Edit attribute list for AppName**. @@ -95,11 +95,11 @@ and enable the `Create`, `Update`, and `Delete` actions. NOTE: **Note:** `username` should neither be primary nor required as we don't support that field on GitLab SCIM yet. - + ![Azure's attribute advanced configuration](img/scim_advanced.png) 1. Save all the screens and, in the **Provisioning** step, set -the `Provisioning Status` to `ON`. + the `Provisioning Status` to `ON`. NOTE: **Note:** You can control what is actually synced by selecting the `Scope`. For example, diff --git a/doc/user/img/color_inline_colorchip_render_gfm.png b/doc/user/img/color_inline_colorchip_render_gfm.png Binary files differdeleted file mode 100644 index 6f93dbeeb10..00000000000 --- a/doc/user/img/color_inline_colorchip_render_gfm.png +++ /dev/null diff --git a/doc/user/img/markdown_inline_diffs_tags_rendered.png b/doc/user/img/markdown_inline_diffs_tags_rendered.png Binary files differdeleted file mode 100644 index 4279a20b5a0..00000000000 --- a/doc/user/img/markdown_inline_diffs_tags_rendered.png +++ /dev/null diff --git a/doc/user/img/math_inline_sup_render_gfm.png b/doc/user/img/math_inline_sup_render_gfm.png Binary files differdeleted file mode 100644 index 3ee2abb14df..00000000000 --- a/doc/user/img/math_inline_sup_render_gfm.png +++ /dev/null diff --git a/doc/user/img/task_list_ordered_render_gfm.png b/doc/user/img/task_list_ordered_render_gfm.png Binary files differdeleted file mode 100644 index 98ec791e958..00000000000 --- a/doc/user/img/task_list_ordered_render_gfm.png +++ /dev/null diff --git a/doc/user/markdown.md b/doc/user/markdown.md index 31c8093ced7..16df6d93277 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1,15 +1,20 @@ # GitLab Markdown -This markdown guide is **valid for GitLab's system markdown entries and files**. -It is not valid for the [GitLab documentation website](https://docs.gitlab.com) -nor [GitLab's main website](https://about.gitlab.com), as they both use -[Kramdown](https://kramdown.gettalong.org) as their markdown engine. -The documentation website uses an extended Kramdown gem, [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown). -Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference. +This markdown guide is **valid only for GitLab's internal markdown rendering system for entries and files**. +It is **not** valid for the [GitLab documentation website](https://docs.gitlab.com) +or [GitLab's main website](https://about.gitlab.com), as they both use +[Kramdown](https://kramdown.gettalong.org) as their markdown engine. The documentation +website uses an extended Kramdown gem, [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown). +Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) +for a complete Kramdown reference. + +NOTE: **Note:** We encourage you to view this document as [rendered by GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md). ## GitLab Flavored Markdown (GFM) -GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add additional useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). +GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification](https://spec.commonmark.org/current/) +(which is based on standard Markdown) in several ways to add additional useful functionality. +It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). You can use GFM in the following areas: @@ -22,35 +27,29 @@ You can use GFM in the following areas: - Markdown documents inside repositories - Epics **[ULTIMATE]** -You can also use other rich text files in GitLab. You might have to install a -dependency to do so. Please see the [`github-markup` gem readme](https://github.com/gitlabhq/markup#markups) for more information. - -> **Notes:** -> -> We encourage you to view this document as [rendered by GitLab itself](markdown.md). -> -> As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown -processing of all new issues, merge requests, comments, and other Markdown content -in the GitLab system. As of 11.3, wiki pages and Markdown files (`.md`) in the -repositories are also processed with CommonMark. As of 11.8, the [Redcarpet -Ruby library][redcarpet] has been removed and all issues/comments, including -those from pre-11.1, are now processed using [CommonMark Ruby -Library][commonmarker]. -> -> The documentation website had its [markdown engine migrated from Redcarpet to Kramdown](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/108) -in October 2018. -> -> _Where there are significant differences, we will try to call them out in this document._ +You can also use other rich text files in GitLab. You might have to install a dependency +to do so. Please see the [`gitlab-markup` gem project](https://gitlab.com/gitlab-org/gitlab-markup) +for more information. + +### Transition from Redcarpet to CommonMark -### Transitioning to CommonMark +Since 11.1, GitLab uses the [CommonMark Ruby Library](https://github.com/gjtorikian/commonmarker) +for Markdown processing of all new issues, merge requests, comments, and other Markdown +content in the GitLab system. Since 11.3, wiki pages and Markdown files (`*.md`) in +repositories are also processed with CommonMark. As of 11.8, the [Redcarpet Ruby library](https://github.com/vmg/redcarpet) +has been removed and all issues and comments, including those from pre-11.1, are now processed +using the [CommonMark Ruby Library](https://github.com/gjtorikian/commonmarker). -You may have older issues/merge requests or Markdown documents in your -repository that were written using some of the nuances of RedCarpet's version +The documentation website had its [markdown engine migrated from Redcarpet to Kramdown](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/108) +in October 2018. + +You may have older issues, merge requests, or Markdown documents in your +repository that were written using some of the nuances of GitLab's RedCarpet version of Markdown. Since CommonMark uses a slightly stricter syntax, these documents -may now display a little strangely since we've transitioned to CommonMark. -Numbered lists with nested lists in particular can be displayed incorrectly. +may now display a little differently since we've transitioned to CommonMark. -It is usually quite easy to fix. In the case of a nested list such as this: +It is usually quite easy to fix. For example, numbered lists with nested lists may +render incorrectly: ```markdown 1. Chocolate @@ -58,7 +57,14 @@ It is usually quite easy to fix. In the case of a nested list such as this: - milk ``` -simply add a space to each nested item: +1. Chocolate + - dark + - milk + +--- + +Simply add a space to each nested item to align the `-` with the first character of +the top list item (`C` in this case): ```markdown 1. Chocolate @@ -66,515 +72,674 @@ simply add a space to each nested item: - milk ``` -In the documentation below, we try to highlight some of the differences. +1. Chocolate + - dark + - milk + +NOTE: **Note:** We will flag any significant differences between Redcarpet and CommonMark + markdown in this document. If you have a large volume of Markdown files, it can be tedious to determine -if they will be displayed correctly or not. You can use the +if they will display correctly or not. You can use the [diff_redcarpet_cmark](https://gitlab.com/digitalmoksha/diff_redcarpet_cmark) -tool (not an officially supported product) to generate a list of files and +tool (not an officially supported product) to generate a list of files, and the differences between how RedCarpet and CommonMark render the files. It can give -you a great idea if anything needs to be changed - many times nothing will need -to changed. +an indication if anything needs to be changed - often nothing will need +to change. + +### GFM extends standard markdown + +GitLab makes full use of the standard (CommonMark) formatting, but also includes additional +functionality useful for GitLab users. + +It makes use of [new markdown features](#new-GFM-markdown-extensions), +not found in standard markdown: + +- [Color "chips" written in HEX, RGB or HSL](#colors) +- [Diagrams and flowcharts using Mermaid](#diagrams-and-flowcharts-using-mermaid) +- [Emoji](#emoji) +- [Front matter](#front-matter) +- [Inline diffs](#inline-diff) +- [Math equations and symbols written in LaTeX](#math) +- [Special GitLab references](#special-gitlab-references) +- [Task Lists](#task-lists) +- [Wiki specific markdown](#wiki-specific-markdown) + +It also has [extended markdown features](#standard-markdown-and-extensions-in-gitlab), without +changing how standard markdown is used: + +| Standard markdown | Extended markdown in GitLab | +| ------------------------------------- | ------------------------- | +| [blockquotes](#blockquotes) | [multiline blockquotes](#multiline-blockquote) | +| [code blocks](#code-spans-and-blocks) | [colored code and syntax highlighting](#colored-code-and-syntax-highlighting) | +| [emphasis](#emphasis) | [multiple underscores in words](#multiple-underscores-in-words-and-mid-word-emphasis) +| [headers](#headers) | [linkable Header IDs](#header-ids-and-links) | +| [images](#images) | [embedded videos](#videos) | +| [linebreaks](#line-breaks) | [more linebreak control](#newlines) | +| [links](#links) | [automatically linking URLs](#url-auto-linking) | + +## New GFM markdown extensions + +### Colors -### Newlines +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#colors). -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#newlines +It is possible to have color written in HEX, RGB or HSL format rendered with a color +indicator. -GFM honors the markdown specification in how [paragraphs and line breaks are handled][commonmark-spec]. +Supported formats (named colors are not supported): -A paragraph is simply one or more consecutive lines of text, separated by one or -more blank lines. -Line-breaks, or soft returns, are rendered if you end a line with two or more spaces: +- HEX: `` `#RGB[A]` `` or `` `#RRGGBB[AA]` `` +- RGB: `` `RGB[A](R, G, B[, A])` `` +- HSL: `` `HSL[A](H, S, L[, A])` `` -<!-- (Do *NOT* remove the two ending whitespaces in the following line.) --> -<!-- (They are needed for the Markdown text to render correctly.) --> - Roses are red [followed by two or more spaces] - Violets are blue +Color written inside backticks will be followed by a color "chip": - Sugar is sweet +```markdown +`#F00` +`#F00A` +`#FF0000` +`#FF0000AA` +`RGB(0,255,0)` +`RGB(0%,100%,0%)` +`RGBA(0,255,0,0.3)` +`HSL(540,70%,50%)` +`HSLA(540,70%,50%,0.3)` +``` -<!-- (Do *NOT* remove the two ending whitespaces in the following line.) --> -<!-- (They are needed for the Markdown text to render correctly.) --> -Roses are red -Violets are blue +`#F00` +`#F00A` +`#FF0000` +`#FF0000AA` +`RGB(0,255,0)` +`RGB(0%,100%,0%)` +`RGBA(0,255,0,0.3)` +`HSL(540,70%,50%)` +`HSLA(540,70%,50%,0.3)` -Sugar is sweet +### Diagrams and flowcharts using Mermaid -### Multiple underscores in words +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107) in +GitLab 10.3. -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiple-underscores-in-words +It is possible to generate diagrams and flowcharts from text using [Mermaid](https://mermaidjs.github.io/). +Visit the official page for more details. -It is not reasonable to italicize just _part_ of a word, especially when you're -dealing with code and names that often appear with multiple underscores. -Therefore, GFM ignores multiple underscores in words: +In order to generate a diagram or flowchart, you should write your text inside the `mermaid` block: - perform_complicated_task +~~~ +```mermaid +graph TD; + A-->B; + A-->C; + B-->D; + C-->D; +``` +~~~ - do_this_and_do_that_and_another_thing +```mermaid +graph TD; + A-->B; + A-->C; + B-->D; + C-->D; +``` -perform_complicated_task +### Emoji -do_this_and_do_that_and_another_thing +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji). -### URL auto-linking +```md +Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#url-auto-linking +:zap: You can use emoji anywhere GFM is supported. :v: -GFM will autolink almost any URL you copy and paste into your text: +You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - * https://www.google.com - * https://google.com/ - * ftp://ftp.us.debian.org/debian/ - * smb://foo/bar/baz - * irc://irc.freenode.net/gitlab - * http://localhost:3000 +If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up one of the supported codes. -* https://www.google.com -* https://google.com/ -* ftp://ftp.us.debian.org/debian/ -* <a href="smb://foo/bar/baz">smb://foo/bar/baz</a> -* <a href="irc://irc.freenode.net/gitlab">irc://irc.freenode.net/gitlab</a> -* http://localhost:3000 +Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: +``` -### Multiline blockquote +Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px" style="display:inline;margin:0"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px" style="display:inline;margin:0"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0">. Well we have a gift for you: -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiline-blockquote +<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/zap.png" width="20px" height="20px" style="display:inline;margin:0">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/v.png" width="20px" height="20px" style="display:inline;margin:0"> -On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, -GFM supports multiline blockquotes fenced by <code>>>></code>: +You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/bug.png" width="20px" height="20px" style="display:inline;margin:0"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/snail.png" width="20px" height="20px" style="display:inline;margin:0"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/birthday.png" width="20px" height="20px" style="display:inline;margin:0">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/heart.png" width="20px" height="20px" style="display:inline;margin:0"> you for that. -``` ->>> -If you paste a message from somewhere else +If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes. -that +Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0"> -spans +> **Note:** The emoji example above uses hard-coded images for this documentation. The emoji, +when rendered within GitLab, may appear different depending on the OS and browser used. -multiple lines, +Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. -you can quote that without having to manually prepend `>` to every line! ->>> -``` +NOTE: **Note:** On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) +to get full native emoji support. Ubuntu 18.04 (like many modern Linux distros) has +this font installed by default. -<blockquote dir="auto"> -<p>If you paste a message from somewhere else</p> -<p>that</p> -<p>spans</p> -<p>multiple lines,</p> -<p>you can quote that without having to manually prepend <code>></code> to every line!</p> -</blockquote> +### Front matter -### Code and syntax highlighting +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23331) + in GitLab 11.6. -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting +Front matter is metadata included at the beginning of a markdown document, preceding +its content. This data can be used by static site generators such as [Jekyll](https://jekyllrb.com/docs/front-matter/), +[Hugo](https://gohugo.io/content-management/front-matter/), and many other applications. -_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a -list of supported languages visit the Rouge website._ +When you view a Markdown file rendered by GitLab, any front matter is displayed as-is, +in a box at the top of the document, before the rendered HTML content. To view an example, +you can toggle between the source and rendered version of a [GitLab documentation file](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/README.md). -Blocks of code are either fenced by lines with three back-ticks <code>```</code>, -or are indented with four spaces. Only the fenced code blocks support syntax -highlighting: +In GitLab, front matter is only used in Markdown files and wiki pages, not the other +places where Markdown formatting is supported. It must be at the very top of the document, +and must be between delimiters, as explained below. -``` -Inline `code` has `back-ticks around` it. -``` +The following delimeters are supported: -Inline `code` has `back-ticks around` it. +- YAML (`---`): -Example: + ~~~yaml + --- + title: About Front Matter + example: + language: yaml + --- + ~~~ - ```javascript - var s = "JavaScript syntax highlighting"; - alert(s); - ``` +- TOML (`+++`): - ```python - def function(): - #indenting works just fine in the fenced code block - s = "Python syntax highlighting" - print s - ``` + ~~~toml + +++ + title = "About Front Matter" + [example] + language = "toml" + +++ + ~~~ - ```ruby - require 'redcarpet' - markdown = Redcarpet.new("Hello World!") - puts markdown.to_html - ``` +- JSON (`;;;`): - ``` - No language indicated, so no syntax highlighting. - s = "There is no highlighting for this." - But let's throw in a <b>tag</b>. - ``` + ~~~json + ;;; + { + "title": "About Front Matter" + "example": { + "language": "json" + } + } + ;;; + ~~~ -becomes: +Other languages are supported by adding a specifier to any of the existing +delimiters. For example: -```javascript -var s = "JavaScript syntax highlighting"; -alert(s); +```php +---php +$title = "About Front Matter"; +$example = array( + 'language' => "php", +); +--- ``` -```python -def function(): - #indenting works just fine in the fenced code block - s = "Python syntax highlighting" - print s -``` +### Inline diff -```ruby -require 'redcarpet' -markdown = Redcarpet.new("Hello World!") -puts markdown.to_html -``` +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-diff). -``` -No language indicated, so no syntax highlighting. -s = "There is no highlighting for this." -But let's throw in a <b>tag</b>. -``` +With inline diff tags you can display {+ additions +} or [- deletions -]. -### Inline diff +The wrapping tags can be either curly braces or square brackets: -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-diff +```markdown +- {+ addition 1 +} +- [+ addition 2 +] +- {- deletion 3 -} +- [- deletion 4 -] +``` -With inline diffs tags you can display {+ additions +} or [- deletions -]. +- {+ addition 1 +} +- [+ addition 2 +] +- {- deletion 3 -} +- [- deletion 4 -] -The wrapping tags can be either curly braces or square brackets. +--- -Examples: +However the wrapping tags cannot be mixed: +```markdown +- {+ addition +] +- [+ addition +} +- {- deletion -] +- [- deletion -} ``` -- {+ additions +} -- [+ additions +] -- {- deletions -} -- [- deletions -] -``` -becomes: +### Math + +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#math). + +It is possible to have math written with LaTeX syntax rendered using [KaTeX](https://github.com/Khan/KaTeX). -![inline diffs tags rendered](img/markdown_inline_diffs_tags_rendered.png) +Math written between dollar signs `$` will be rendered inline with the text. Math written +inside a [code block](#code-spans-and-blocks) with the language declared as `math`, will be rendered +on a separate line: -However the wrapping tags cannot be mixed as such: +~~~ +This math is inline $`a^2+b^2=c^2`$. +This is on a separate line + +```math +a^2+b^2=c^2 ``` -- {+ additions +] -- [+ additions +} -- {- deletions -] -- [- deletions -} +~~~ + +This math is inline $`a^2+b^2=c^2`$. + +This is on a separate line + +```math +a^2+b^2=c^2 ``` -### Emoji +_Be advised that KaTeX only supports a [subset](https://katex.org/docs/supported.html) of LaTeX._ -```md -Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: +NOTE: **Note:** This also works for the asciidoctor `:stem: latexmath`. For details see +the [asciidoctor user manual](http://asciidoctor.org/docs/user-manual/#activating-stem-support). -:zap: You can use emoji anywhere GFM is supported. :v: +### Special GitLab references -You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. +GFM recognizes special GitLab related references. For example, you can easily reference +an issue, a commit, a team member or even the whole team within a project. GFM will turn +that reference into a link so you can navigate between them easily. -If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up one of the supported codes. +Additionally, GFM recognizes certain cross-project references, and also has a shorthand +version to reference other projects from the same namespace. -Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: +GFM will recognize the following: -Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. +| references | input | cross-project reference | shortcut within same namespace | +| :------------------------------ | :------------------------- | :-------------------------------------- | :----------------------------- | +| specific user | `@user_name` | | | +| specific group | `@group_name` | | | +| entire team | `@all` | | | +| project | `namespace/project>` | | | +| issue | ``#123`` | `namespace/project#123` | `project#123` | +| merge request | `!123` | `namespace/project!123` | `project!123` | +| snippet | `$123` | `namespace/project$123` | `project$123` | +| epic **[ULTIMATE]** | `&123` | `group1/subgroup&123` | | +| label by ID | `~123` | `namespace/project~123` | `project~123` | +| one-word label by name | `~bug` | `namespace/project~bug` | `project~bug` | +| multi-word label by name | `~"feature request"` | `namespace/project~"feature request"` | `project~"feature request"` | +| project milestone by ID | `%123` | `namespace/project%123` | `project%123` | +| one-word milestone by name | `%v1.23` | `namespace/project%v1.23` | `project%v1.23` | +| multi-word milestone by name | `%"release candidate"` | `namespace/project%"release candidate"` | `project%"release candidate"` | +| specific commit | `9ba12248` | `namespace/project@9ba12248` | `project@9ba12248` | +| commit range comparison | `9ba12248...b19a04f5` | `namespace/project@9ba12248...b19a04f5` | `project@9ba12248...b19a04f5` | +| repository file references | `[README](doc/README)` | | | +| repository file line references | `[README](doc/README#L13)` | | | -On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. +### Task lists + +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-lists). + +You can add task lists anywhere markdown is supported, but you can only "click" to +toggle the boxes if they are in issues, merge requests, or comments. In other places +you must edit the markdown manually to change the status by adding or removing the `x`. -Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. +To create a task list, add a specially-formatted Markdown list. You can use either +unordered or ordered lists: + +```markdown +- [x] Completed task +- [ ] Incomplete task + - [ ] Sub-task 1 + - [x] Sub-task 2 + - [ ] Sub-task 3 +1. [x] Completed task +1. [ ] Incomplete task + 1. [ ] Sub-task 1 + 1. [x] Sub-task 2 ``` -Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px" style="display:inline;margin:0"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px" style="display:inline;margin:0"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px" style="display:inline;margin:0">. Well we have a gift for you: +- [x] Completed task +- [ ] Incomplete task + - [ ] Sub-task 1 + - [x] Sub-task 2 + - [ ] Sub-task 3 +1. [x] Completed task +1. [ ] Incomplete task + 1. [ ] Sub-task 1 + 1. [x] Sub-task 2 -<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/zap.png" width="20px" height="20px" style="display:inline;margin:0">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/v.png" width="20px" height="20px" style="display:inline;margin:0"> +### Wiki-specific Markdown -You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/bug.png" width="20px" height="20px" style="display:inline;margin:0"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speak_no_evil.png" width="20px" height="20px" style="display:inline;margin:0"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/snail.png" width="20px" height="20px" style="display:inline;margin:0"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/birthday.png" width="20px" height="20px" style="display:inline;margin:0">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/heart.png" width="20px" height="20px" style="display:inline;margin:0"> you for that. +The following examples show how links inside wikis behave. -If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px" style="display:inline;margin:0">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px" style="display:inline;margin:0">. All you need to do is to look up one of the supported codes. +#### Wiki - Direct page link -Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px" style="display:inline;margin:0"> +A link which just includes the slug for a page will point to that page, +_at the base level of the wiki_. -Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. +This snippet would link to a `documentation` page at the root of your wiki: -On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. +```markdown +[Link to Documentation](documentation) +``` -Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. +#### Wiki - Direct file link -### Special GitLab references +Links with a file extension point to that file, _relative to the current page_. -GFM recognizes special references. +If the snippet below was placed on a page at `<your_wiki>/documentation/related`, +it would link to `<your_wiki>/documentation/file.md`: -You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project. +```markdown +[Link to File](file.md) +``` -GFM will turn that reference into a link so you can navigate between them easily. +#### Wiki - Hierarchical link -GFM will recognize the following: +A link can be constructed relative to the current wiki page using `./<page>`, +`../<page>`, etc. -| input | references | -|:---------------------------|:--------------------------------| -| `@user_name` | specific user | -| `@group_name` | specific group | -| `@all` | entire team | -| `namespace/project>` | project | -| `#12345` | issue | -| `!123` | merge request | -| `$123` | snippet | -| `&123` | epic **[ULTIMATE]** | -| `~123` | label by ID | -| `~bug` | one-word label by name | -| `~"feature request"` | multi-word label by name | -| `%123` | project milestone by ID | -| `%v1.23` | one-word milestone by name | -| `%"release candidate"` | multi-word milestone by name | -| `9ba12248` | specific commit | -| `9ba12248...b19a04f5` | commit range comparison | -| `[README](doc/README)` | repository file references | -| `[README](doc/README#L13)` | repository file line references | - -GFM also recognizes certain cross-project references: - -| input | references | -|:----------------------------------------|:------------------------| -| `namespace/project#123` | issue | -| `namespace/project!123` | merge request | -| `namespace/project%123` | project milestone | -| `namespace/project$123` | snippet | -| `namespace/project@9ba12248` | specific commit | -| `group1/subgroup&123` | epic **[ULTIMATE]** | -| `namespace/project@9ba12248...b19a04f5` | commit range comparison | -| `namespace/project~"Some label"` | issues with given label | - -It also has a shorthand version to reference other projects from the same namespace: - -| input | references | -|:------------------------------|:------------------------| -| `project#123` | issue | -| `project!123` | merge request | -| `project%123` | project milestone | -| `project$123` | snippet | -| `project@9ba12248` | specific commit | -| `project@9ba12248...b19a04f5` | commit range comparison | -| `project~"Some label"` | issues with given label | +If this snippet was placed on a page at `<your_wiki>/documentation/main`, +it would link to `<your_wiki>/documentation/related`: -### Task lists +```markdown +[Link to Related Page](./related) +``` + +If this snippet was placed on a page at `<your_wiki>/documentation/related/content`, +it would link to `<your_wiki>/documentation/main`: -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-lists +```markdown +[Link to Related Page](../main) +``` -You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: +If this snippet was placed on a page at `<your_wiki>/documentation/main`, +it would link to `<your_wiki>/documentation/related.md`: +```markdown +[Link to Related Page](./related.md) ``` -- [x] Completed task -- [ ] Incomplete task - - [ ] Sub-task 1 - - [x] Sub-task 2 - - [ ] Sub-task 3 + +If this snippet was placed on a page at `<your_wiki>/documentation/related/content`, +it would link to `<your_wiki>/documentation/main.md`: + +```markdown +[Link to Related Page](../main.md) ``` -![alt unordered-check-list-render-gfm](img/unordered_check_list_render_gfm.png) +#### Wiki - Root link -Tasks formatted as ordered lists are supported as well: +A link starting with a `/` is relative to the wiki root. + +This snippet links to `<wiki_root>/documentation`: +```markdown +[Link to Related Page](/documentation) ``` -1. [x] Completed task -1. [ ] Incomplete task - 1. [ ] Sub-task 1 - 1. [x] Sub-task 2 + +This snippet links to `<wiki_root>/miscellaneous.md`: + +```markdown +[Link to Related Page](/miscellaneous.md) ``` -![alt task-list-ordered-render-gfm](img/task_list_ordered_render_gfm.png) +## Standard markdown and extensions in GitLab -Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. +All standard markdown formatting should work as expected within GitLab. Some standard +functionality is extended with additional features, without affecting the standard usage. +If a functionality is extended, the new option will be listed as a sub-section. -### Videos +### Blockquotes -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#videos +Blockquotes are an easy way to highlight information, such as a side-note. It is generated +by starting the lines of the blockquote with `>`: -Image tags with a video extension are automatically converted to a video player. +```markdown +> Blockquotes are very handy to emulate reply text. +> This line is part of the same quote. -The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`. +Quote break. - Here's a sample video: +> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. +``` - ![Sample Video](img/markdown_video.mp4) +> Blockquotes are very handy to emulate reply text. +> This line is part of the same quote. -Here's a sample video: +Quote break. -<div class="video-container"> - <video src="img/markdown_video.mp4" width="400" controls="true" data-setup="{}" data-title="Sample Video"></video> - <p><a href="img/markdown_video.mp4" target="_blank" rel="noopener noreferrer" title="Download 'Sample Video'">Sample Video</a></p> -</div> +> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. -### Math +#### Multiline blockquote -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#math +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiline-blockquote). -It is possible to have math written with the LaTeX syntax rendered using [KaTeX][katex]. +GFM extends the standard markdown standard by also supporting multiline blockquotes +fenced by `>>>`: -Math written inside ```$``$``` will be rendered inline with the text. +``` +>>> +If you paste a message from somewhere else -Math written inside triple back quotes, with the language declared as `math`, will be rendered on a separate line. +that spans multiple lines, -Example: +you can quote that without having to manually prepend `>` to every line! +>>> +``` - This math is inline $`a^2+b^2=c^2`$. +>>> +If you paste a message from somewhere else - This is on a separate line - ```math - a^2+b^2=c^2 - ``` +that spans multiple lines, -Becomes: +you can quote that without having to manually prepend `>` to every line! +>>> -This math is inline ![alt text](img/math_inline_sup_render_gfm.png). +### Code spans and blocks -This is on a separate line +You can easily highlight anything that should be viewed as code and not simple text. -<img src="./img/math_inline_sup_render_gfm.png" > +Simple inline code is easily highlighted with single backticks `` ` ``: -_Be advised that KaTeX only supports a [subset][katex-subset] of LaTeX._ +```markdown +Inline `code` has `back-ticks around` it. +``` ->**Note:** -This also works for the asciidoctor `:stem: latexmath`. For details see the [asciidoctor user manual][asciidoctor-manual]. +Inline `code` has `back-ticks around` it. -### Colors +--- -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#colors +Similarly, a whole block of code can be fenced with triple backticks ```` ``` ````, +triple tildes (`~~~`), or indended 4 or more spaces to achieve a similar effect for +a larger body of code. test. -It is possible to have color written in HEX, RGB or HSL format rendered with a color indicator. +~~~ +``` +def function(): + #indenting works just fine in the fenced code block + s = "Python code" + print s +``` -Color written inside backticks will be followed by a color "chip". + Using 4 spaces + is like using + 3-backtick fences. +~~~ -Examples: +``` +~~~ +Tildes are OK too. +~~~ +``` - `#F00` - `#F00A` - `#FF0000` - `#FF0000AA` - `RGB(0,255,0)` - `RGB(0%,100%,0%)` - `RGBA(0,255,0,0.7)` - `HSL(540,70%,50%)` - `HSLA(540,70%,50%,0.7)` +The three examples above render as: -Becomes: +``` +def function(): + #indenting works just fine in the fenced code block + s = "Python code" + print s +``` -![alt color-inline-colorchip-render-gfm](img/color_inline_colorchip_render_gfm.png) + Using 4 spaces + is like using + 3-backtick fences. -#### Supported formats: +~~~ +Tildes are OK too. +~~~ -* HEX: `` `#RGB[A]` `` or `` `#RRGGBB[AA]` `` -* RGB: `` `RGB[A](R, G, B[, A])` `` -* HSL: `` `HSL[A](H, S, L[, A])` `` +#### Colored code and syntax highlighting -### Mermaid +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#colored-code-and-syntax-highlighting). -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15107) in -GitLab 10.3. -> -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#mermaid +GitLab uses the [Rouge Ruby library](http://rouge.jneen.net/) for more colorful syntax +highlighting in code blocks. For a list of supported languages visit the +[Rouge project wiki](https://github.com/jneen/rouge/wiki/List-of-supported-languages-and-lexers). +Syntax highlighting is only supported in code blocks, it is not possible to highlight +code when it is inline. -It is possible to generate diagrams and flowcharts from text using [Mermaid](https://mermaidjs.github.io/). +Blocks of code are fenced by lines with three back-ticks ```` ``` ```` or three tildes `~~~`, and have +the language identified at the end of the first fence: -In order to generate a diagram or flowchart, you should write your text inside the `mermaid` block. +~~~ +```javascript +var s = "JavaScript syntax highlighting"; +alert(s); +``` -Example: +```python +def function(): + #indenting works just fine in the fenced code block + s = "Python syntax highlighting" + print s +``` - ```mermaid - graph TD; - A-->B; - A-->C; - B-->D; - C-->D; - ``` +```ruby +require 'redcarpet' +markdown = Redcarpet.new("Hello World!") +puts markdown.to_html +``` -Becomes: +``` +No language indicated, so no syntax highlighting. +s = "There is no highlighting for this." +But let's throw in a <b>tag</b>. +``` +~~~ -```mermaid -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; +The four examples above render as: + +```javascript +var s = "JavaScript syntax highlighting"; +alert(s); ``` -For details see the [Mermaid official page](https://mermaidjs.github.io/). +```python +def function(): + #indenting works just fine in the fenced code block + s = "Python syntax highlighting" + print s +``` -### Front matter +```ruby +require 'redcarpet' +markdown = Redcarpet.new("Hello World!") +puts markdown.to_html +``` -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23331) - in GitLab 11.6. +``` +No language indicated, so no syntax highlighting. +s = "There is no highlighting for this." +But let's throw in a <b>tag</b>. +``` -Front matter is metadata included at the beginning of a markdown document, preceding -its content. This data can be used by static site generators such as [Jekyll](https://jekyllrb.com/docs/front-matter/) and [Hugo](https://gohugo.io/content-management/front-matter/), -and many other applications. +### Emphasis -In GitLab, front matter is only used in Markdown files and wiki pages, not the other places where Markdown formatting is supported. -When you view a Markdown file rendered by GitLab, any front matter is displayed as-is, in a box at the top of the document, before the rendered HTML content. -To view an example, you can toggle between the source and rendered version of a [GitLab documentation file](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/README.md). +There are multiple ways to emphasize text in markdown. You can italicize, bold, strikethrough, +as well as combine these emphasis styles together. -The following delimeters are supported: +Examples: -- YAML (`---`): +```markdown +Emphasis, aka italics, with *asterisks* or _underscores_. - ``` - --- - title: About Front Matter - example: - language: yaml - --- - ``` +Strong emphasis, aka bold, with double **asterisks** or __underscores__. -- TOML (`+++`): +Combined emphasis with **asterisks and _underscores_**. - ``` - +++ - title = "About Front Matter" - [example] - language = "toml" - +++ - ``` +Strikethrough uses two tildes. ~~Scratch this.~~ +``` -- JSON (`;;;`): +Emphasis, aka italics, with *asterisks* or _underscores_. - ``` - ;;; - { - "title": "About Front Matter" - "example": { - "language": "json" - } - } - ;;; - ``` +Strong emphasis, aka bold, with double **asterisks** or __underscores__. -Other languages are supported by adding a specifier to any of the existing -delimiters. For example: +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ +NOTE: **Note:** Strikethrough is not part of the core Markdown standard, but is part of GFM. + +#### Multiple underscores in words and mid-word emphasis + +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiple-underscores-in-words). + +It is not usually useful to italicize just _part_ of a word, especially when you're +dealing with code and names that often appear with multiple underscores. As a result, +GFM extends the standard markdown standard by ignoring multiple underlines in words, +to allow better rendering of markdown documents discussing code: + +```md +perform_complicated_task + +do_this_and_do_that_and_another_thing + +but_emphasis is_desired _here_ ``` ----php -$title = "About Front Matter"; -$example = array( - 'language' => "php", -); + +perform_complicated_task + +do_this_and_do_that_and_another_thing + +but_emphasis is_desired _here_ + --- + +If you wish to emphasize only a part of a word, it can still be done with asterisks: + +```md +perform*complicated*task + +do*this*and*do*that*and*another thing ``` -## Standard Markdown +perform*complicated*task + +do*this*and*do*that*and*another thing -### Headers +### Footnotes + +Footnotes add a link to a note rendered at the end of a markdown file: + +```markdown +You can add footnotes to your text as follows.[^1] +[^1]: This is my awesome footnote (later in file). ``` + +You can add footnotes to your text as follows.[^1] + +[^1]: This is my awesome footnote (later in file). + +### Headers + +```markdown # H1 ## H2 ### H3 @@ -593,9 +758,11 @@ Alt-H2 #### Header IDs and links -All Markdown-rendered headers automatically get IDs, which can be linked to, except in comments. +GFM extends the standard markdown standard so that all Markdown-rendered headers automatically +get IDs, which can be linked to, except in comments. -On hover, a link to those IDs becomes visible to make it easier to copy the link to the header to use it somewhere else. +On hover, a link to those IDs becomes visible to make it easier to copy the link to +the header to use it somewhere else. The IDs are generated from the content of the header according to the following rules: @@ -606,7 +773,7 @@ The IDs are generated from the content of the header according to the following 1. If a header with the same ID has already been generated, a unique incrementing number is appended, starting at 1. -For example: +Example: ``` # This header has spaces in it @@ -626,215 +793,164 @@ Would generate the following link IDs: 1. `this-header-has-spaces-in-it-2` 1. `this-header-has-3-5-in-it-and-parentheses` -Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID. - -### Emphasis - -Examples: - -``` -Emphasis, aka italics, with *asterisks* or _underscores_. - -Strong emphasis, aka bold, with **asterisks** or __underscores__. - -Combined emphasis with **asterisks and _underscores_**. - -Strikethrough uses two tildes. ~~Scratch this.~~ -``` - -Becomes: - -Emphasis, aka italics, with *asterisks* or _underscores_. - -Strong emphasis, aka bold, with **asterisks** or __underscores__. - -Combined emphasis with **asterisks and _underscores_**. - -Strikethrough uses two tildes. ~~Scratch this.~~ - -### Lists - -Examples: - -``` -1. First ordered list item -2. Another item - * Unordered sub-list. -1. Actual numbers don't matter, just that it's a number - 1. Ordered sub-list -4. And another item. - -* Unordered list can use asterisks -- Or minuses -+ Or pluses -``` - -Becomes: - -1. First ordered list item -2. Another item - * Unordered sub-list. -1. Actual numbers don't matter, just that it's a number - 1. Ordered sub-list -4. And another item. - -* Unordered list can use asterisks -- Or minuses -+ Or pluses - -If a list item contains multiple paragraphs, -each subsequent paragraph should be indented to the same level as the start of the list item text +Note that the Emoji processing happens before the header IDs are generated, so the +Emoji is converted to an image which is then removed from the ID. -Example: - -``` -1. First ordered list item - - Second paragraph of first item. - -2. Another item -``` +### Horizontal Rule -Becomes: +It's very simple to create a horizontal rule, by using three or more hyphens, asterisks, +or underscores: -1. First ordered list item +```markdown +Three or more hyphens, - Paragraph of first item. +--- -2. Another item +asterisks, -If the paragraph of the first item is not indented with the proper number of spaces, -the paragraph will appear outside the list, instead of properly indented under the list item. +*** -Example: +or underscores +___ ``` -1. First ordered list item - Paragraph of first item. +Three or more hyphens, -2. Another item -``` +--- -Becomes: +asterisks, -1. First ordered list item +*** - Paragraph of first item. +or underscores -2. Another item +___ -### Links +### Images -There are two ways to create links, inline-style and reference-style. +Examples: ```markdown -[I'm an inline-style link](https://www.google.com) -[I'm a link to a repository file in the same directory](index.md) -[I am an absolute reference within the repository](/doc/user/index.md) -[I'm a relative link to the Milestones page](../README.md) +Inline-style (hover to see title text): -[I link to a section on a different markdown page, using a header ID](index.md#overview) -[I link to a different section on the same page, using the header ID](#header-ids-and-links) +![alt text](img/markdown_logo.png "Title Text") -[I'm a reference-style link][Arbitrary case-insensitive reference text] -[You can use numbers for reference-style link definitions][1] -Or leave it empty and use the [link text itself][] +Reference-style (hover to see title text): -Some text to show that the reference links can follow later. +![alt text1][logo] -[arbitrary case-insensitive reference text]: https://www.mozilla.org -[1]: http://slashdot.org -[link text itself]: https://www.reddit.com +[logo]: img/markdown_logo.png "Title Text" ``` ->**Note:** -Relative links do not allow referencing project files in a wiki page or wiki -page in a project file. The reason for this is that, in GitLab, wiki is always -a separate Git repository. For example, `[I'm a reference-style link](style)` -will point the link to `wikis/style` when the link is inside of a wiki markdown file. - -### Images +Inline-style (hover to see title text): -Examples: +![alt text](img/markdown_logo.png "Title Text") - Here's our logo (hover to see the title text): +Reference-style (hover to see title text): - Inline-style: - ![alt text](img/markdown_logo.png) +![alt text][logo] - Reference-style: - ![alt text1][logo] +[logo]: img/markdown_logo.png "Title Text" - [logo]: img/markdown_logo.png +#### Videos -Becomes: +> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#videos). -Here's our logo: +Image tags that link to files with a video extension are automatically converted to +a video player. The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`: -Inline-style: +```md +Here's a sample video: -![alt text](img/markdown_logo.png) +![Sample Video](img/markdown_video.mp4) +``` -Reference-style: +Here's a sample video: -![alt text][logo] +![Sample Video](img/markdown_video.mp4) -[logo]: img/markdown_logo.png +### Inline HTML -### Blockquotes +> To see the markdown rendered within HTML in the second example, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-html). -Examples: +You can also use raw HTML in your Markdown, and it'll usually work pretty well. -``` -> Blockquotes are very handy in email to emulate reply text. -> This line is part of the same quote. +See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/1.11.0/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) +class for the list of allowed HTML tags and attributes. In addition to the default +`SanitizationFilter` whitelist, GitLab allows `span`, `abbr`, `details` and `summary` elements. -Quote break. +```html +<dl> + <dt>Definition list</dt> + <dd>Is something people use sometimes.</dd> -> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. + <dt>Markdown in HTML</dt> + <dd>Does *not* work **very** well. HTML <em>tags</em> will <b>always</b> work.</dd> +</dl> ``` -Becomes: - -> Blockquotes are very handy in email to emulate reply text. -> This line is part of the same quote. - -Quote break. - -> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. - -### Inline HTML +<dl> + <dt>Definition list</dt> + <dd>Is something people use sometimes.</dd> -You can also use raw HTML in your Markdown, and it'll mostly work pretty well. + <dt>Markdown in HTML</dt> + <dd>Does *not* work **very** well. HTML <em>tags</em> will <b>always</b> work.</dd> +</dl> -See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/1.11.0/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span`, `abbr`, `details` and `summary` elements. +--- -Examples: +It is still possible to use markdown inside HTML tags, but only if the lines containing markdown +are separated into their own lines: -``` +```html <dl> - <dt>Definition list</dt> - <dd>Is something people use sometimes.</dd> + <dt>Markdown in HTML</dt> + <dd>Does *not* work **very** well. HTML tags will always work.</dd> <dt>Markdown in HTML</dt> - <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd> + <dd> + + Does *not* work **very** well. HTML tags will always work. + + </dd> </dl> ``` -Becomes: +<!-- Note: The example below uses HTML to force correct rendering on docs.gitlab.com, markdown will be fine in GitLab --> <dl> - <dt>Definition list</dt> - <dd>Is something people use sometimes.</dd> + <dt>Markdown in HTML</dt> + <dd>Does *not* work **very** well. HTML tags will always work.</dd> <dt>Markdown in HTML</dt> - <dd>Does *not* work **very** well. Use HTML <em>tags</em>.</dd> + <dd> + + Does <em>not</em> work <b>very</b> well. HTML tags will always work. + + </dd> </dl> #### Details and Summary -Content can be collapsed using HTML's [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) and [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary) tags. This is especially useful for collapsing long logs so they take up less screen space. +> To see the markdown rendered within HTML in the second example, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#details-and-summary). + +Content can be collapsed using HTML's [`<details>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details) +and [`<summary>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary) +tags. This is especially useful for collapsing long logs so they take up less screen space. + +```html +<p> +<details> +<summary>Click me to collapse/fold.</summary> + +These details <em>will</em> remain <strong>hidden</strong> until expanded. + +<pre><code>PASTE LOGS HERE</code></pre> + +</details> +</p> +``` <p> <details> @@ -847,7 +963,10 @@ These details <em>will</em> remain <strong>hidden</strong> until expanded. </details> </p> -**Note:** Markdown inside these tags is supported, as long as you have a blank line after the `</summary>` tag and before the `</details>` tag, as shown in the example. +--- + +Markdown inside these tags is supported as well, as long as you have a blank line +after the `</summary>` tag and before the `</details>` tag, as shown in the example: ```html <details> @@ -860,232 +979,302 @@ These details _will_ remain **hidden** until expanded. </details> ``` -### Horizontal Rule +<!-- Note: The example below uses HTML to force correct rendering on docs.gitlab.com, markdown will be fine in GitLab --> -Examples: +<details> +<summary>Click me to collapse/fold.</summary> -``` -Three or more... +These details <em>will</em> remain <b>hidden</b> until expanded. ---- + PASTE LOGS HERE + +</details> -Hyphens +### Line Breaks -*** +A line break will be inserted (a new paragraph will start) if the previous text is +ended with two newlines, i.e. you hit <kbd>Enter</kbd> twice in a row. If you only +use one newline (hit <kbd>Enter</kbd> once), the next sentence will be part of the +same paragraph. This is useful if you want to keep long lines from wrapping, and keep +them easily editable: -Asterisks +```markdown +Here's a line for us to start with. -___ +This longer line is separated from the one above by two newlines, so it will be a *separate paragraph*. -Underscores +This line is also a separate paragraph, but... +These lines are only separated by single newlines, +so they *do not break* and just follow the previous lines +in the *same paragraph*. ``` -Becomes: +Here's a line for us to start with. -Three or more... +This longer line is separated from the one above by two newlines, so it will be a *separate paragraph*. ---- +This line is also a separate paragraph, but... +These lines are only separated by single newlines, +so they *do not break* and just follow the previous lines +in the *same paragraph*. -Hyphens +#### Newlines -*** +GFM adheres to the markdown specification in how [paragraphs and line breaks are handled](https://spec.commonmark.org/current/). -Asterisks +A paragraph is simply one or more consecutive lines of text, separated by one or +more blank lines (i.e. two newlines at the end of the first paragraph), as [explained above](#line-breaks). -___ +If you need more control over line-breaks or soft returns, you can add a single line-break +by ending a line with a backslash, or two or more spaces. Two newlines in a row will create a new +paragraph, with a blank line in between: -Underscores +```markdown +First paragraph. +Another line in the same paragraph. +A third line in the same paragraph, but this time ending with two spaces.{space}{space} +A new line directly under the first paragraph. + +Second paragraph. +Another line, this time ending with a backslash.\ +A new line due to the previous backslash. +``` -### Line Breaks +<!-- (Do *NOT* remove the two ending whitespaces in the third line) --> +<!-- (They are needed for the Markdown text to render correctly) --> -A good way to learn how line breaks work is to experiment and discover -- hit <kbd>Enter</kbd> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. The "Preview" tab is your friend. +First paragraph. +Another line in the same paragraph. +A third line in the same paragraph, but this time ending with two spaces. +A new line directly under the first paragraph. -Here are some things to try out: +<!-- (Do *NOT* remove the two ending whitespaces in the second line) --> +<!-- (They are needed for the Markdown text to render correctly on docs.gitlab.com, the backslash works fine inside GitLab itself) --> -Examples: +Second paragraph. +Another line, this time ending with a backslash. +A new line due to the previous backslash. -``` -Here's a line for us to start with. +### Links -This line is separated from the one above by two newlines, so it will be a *separate paragraph*. +There are two ways to create links, inline-style and reference-style: -This line is also a separate paragraph, but... -This line is only separated by a single newline, so it *does not break* and just follows the previous line in the *same paragraph*. +```md +- This is an [inline-style link](https://www.google.com) +- This is a [link to a repository file in the same directory](index.md) +- This is a [relative link to a readme one directory higher](../README.md) +- This is a [link that also has title text](https://www.google.com "This link takes you to Google!") -This line is also a separate paragraph, and... -This line is *on its own line*, because the previous line ends with two spaces. (but still in the *same paragraph*) +Using header ID anchors: -spaces. -``` +- This links to [a section on a different markdown page, using a "#" and the header ID](index.md#overview) +- This links to [a different section on the same page, using a "#" and the header ID](#header-ids-and-links) -Becomes: +Using references: -Here's a line for us to start with. +- This is a [reference-style link, see below][Arbitrary case-insensitive reference text] +- You can [use numbers for reference-style link definitions, see below][1] +- Or leave it empty and use the [link text itself][], see below. -This line is separated from the one above by two newlines, so it will be a *separate paragraph*. +Some text to show that the reference links can follow later. -This line is also a separate paragraph, but... -This line is only separated by a single newline, so it *does not break* and just follows the previous line in the *same paragraph*. +[arbitrary case-insensitive reference text]: https://www.mozilla.org +[1]: http://slashdot.org +[link text itself]: https://www.reddit.com +``` -This line is also a separate paragraph, and... -This line is *on its own line*, because the previous line ends with two spaces. (but still in the *same paragraph*) +- This is an [inline-style link](https://www.google.com) +- This is a [link to a repository file in the same directory](index.md) +- This is a [relative link to a readme one directory higher](../README.md) +- This is a [link that also has title text](https://www.google.com "This link takes you to Google!") -spaces. +Using header ID anchors: -### Tables +- This links to [a section on a different markdown page, using a "#" and the header ID](index.md#overview) +- This links to [a different section on the same page, using a "#" and the header ID](#header-ids-and-links) -Tables aren't part of the core Markdown spec, but they are part of GFM. +Using references: -Example: +- This is a [reference-style link, see below][Arbitrary case-insensitive reference text] +- You can [use numbers for reference-style link definitions, see below][1] +- Or leave it empty and use the [link text itself][], see below. -``` -| header 1 | header 2 | -| -------- | -------- | -| cell 1 | cell 2 | -| cell 3 | cell 4 | -``` - -Becomes: +Some text to show that the reference links can follow later. -| header 1 | header 2 | -| -------- | -------- | -| cell 1 | cell 2 | -| cell 3 | cell 4 | +[arbitrary case-insensitive reference text]: https://www.mozilla.org +[1]: http://slashdot.org +[link text itself]: https://www.reddit.com -**Note:** The row of dashes between the table header and body must have at least three dashes in each column. +NOTE: **Note:** Relative links do not allow the referencing of project files in a wiki +page, or a wiki page in a project file. The reason for this is that a wiki is always +in a separate Git repository in GitLab. For example, `[I'm a reference-style link](style)` +will point the link to `wikis/style` only when the link is inside of a wiki markdown file. -By including colons in the header row, you can align the text within that column. +#### URL auto-linking -Example: +GFM will autolink almost any URL you put into your text: -``` -| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | -| :----------- | :------: | ------------: | :----------- | :------: | ------------: | -| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | -| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | +```markdown +* https://www.google.com +* https://google.com/ +* ftp://ftp.us.debian.org/debian/ +* smb://foo/bar/baz +* irc://irc.freenode.net/gitlab +* http://localhost:3000 ``` -Becomes: +* https://www.google.com +* https://google.com/ +* ftp://ftp.us.debian.org/debian/ +* smb://foo/bar/baz +* irc://irc.freenode.net/gitlab +* http://localhost:3000 -| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | -| :----------- | :------: | ------------: | :----------- | :------: | ------------: | -| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | -| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | +### Lists -### Footnotes +Ordered and unordered lists can be easily created. Add the number you want the list +to start with, like `1. ` (with a space) at the start of each line for ordered lists. +After the first number, it does not matter what number you use, ordered lists will be +numbered automatically by vertical order, so repeating `1. ` for all items in the +same list is common. If you start with a number other than `1. `, it will use that as the first +number, and count up from there. -Example: +Add a `* `, `- ` or `+ ` (with a space) at the start of each line for unordered lists, but +you should not use a mix of them. -``` -You can add footnotes to your text as follows.[^2] -[^2]: This is my awesome footnote. +Examples: + +```md +1. First ordered list item +2. Another item + * Unordered sub-list. +1. Actual numbers don't matter, just that it's a number + 1. Ordered sub-list + 1. Next ordered sub-list item +4. And another item. + +* Unordered lists can use asterisks +- Or minuses ++ Or pluses ``` -Becomes: +1. First ordered list item +2. Another item + * Unordered sub-list. +1. Actual numbers don't matter, just that it's a number + 1. Ordered sub-list + 1. Next ordered sub-list item +4. And another item. -You can add footnotes to your text as follows.[^2] +* Unordered lists can use asterisks +- Or minuses ++ Or pluses -### Superscripts / Subscripts +--- -CommonMark and GFM currently do not support the superscript syntax ( `x^2` ) that Redcarpet does. You can use the standard HTML syntax for superscripts and subscripts. +If a list item contains multiple paragraphs, each subsequent paragraph should be indented +to the same level as the start of the list item text. -``` -The formula for water is H<sub>2</sub>O -while the equation for the theory of relativity is E = mc<sup>2</sup>. -``` +Example: -The formula for water is H<sub>2</sub>O while the equation for the theory of relativity is E = mc<sup>2</sup>. +```markdown +1. First ordered list item -## Wiki-specific Markdown + Second paragraph of first item. -The following examples show how links inside wikis behave. +2. Another item +``` -### Wiki - Direct page link +1. First ordered list item -A link which just includes the slug for a page will point to that page, -_at the base level of the wiki_. + Second paragraph of first item. -This snippet would link to a `documentation` page at the root of your wiki: +2. Another item -```markdown -[Link to Documentation](documentation) -``` +--- -### Wiki - Direct file link +If the paragraph of the first item is not indented with the proper number of spaces, +the paragraph will appear outside the list, instead of properly indented under the list item. -Links with a file extension point to that file, _relative to the current page_. +Example: -If this snippet was placed on a page at `<your_wiki>/documentation/related`, -it would link to `<your_wiki>/documentation/file.md`: +``` +1. First ordered list item -```markdown -[Link to File](file.md) + Paragraph of first item. + +2. Another item ``` -### Wiki - Hierarchical link +1. First ordered list item -A link can be constructed relative to the current wiki page using `./<page>`, -`../<page>`, etc. + Paragraph of first item. -- If this snippet was placed on a page at `<your_wiki>/documentation/main`, - it would link to `<your_wiki>/documentation/related`: +2. Another item - ```markdown - [Link to Related Page](related) - ``` +### Superscripts / Subscripts -- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`, - it would link to `<your_wiki>/documentation/main`: +CommonMark and GFM currently do not support the superscript syntax ( `x^2` ) that +Redcarpet does. You can use the standard HTML syntax for superscripts and subscripts: - ```markdown - [Link to Related Page](../main) - ``` +```html +The formula for water is H<sub>2</sub>O +while the equation for the theory of relativity is E = mc<sup>2</sup>. +``` -- If this snippet was placed on a page at `<your_wiki>/documentation/main`, - it would link to `<your_wiki>/documentation/related.md`: +The formula for water is H<sub>2</sub>O +while the equation for the theory of relativity is E = mc<sup>2</sup>. - ```markdown - [Link to Related Page](related.md) - ``` +### Tables -- If this snippet was placed on a page at `<your_wiki>/documentation/related/content`, - it would link to `<your_wiki>/documentation/main.md`: +Tables aren't part of the core Markdown spec, but they are part of GFM. - ```markdown - [Link to Related Page](../main.md) - ``` +1. The first line contains the headers, separated by "pipes" (`|`). +1. The second line separates the headers from the cells, and must contain three or more dashes. +1. The third, and any following lines, contain the cell values. + - You **can't** have cells separated over many lines in the markdown, they must be kept to single lines, + but they can be very long. You can also include HTML `<br>` tags to force newlines if needed. + - The cell sizes **don't** have to match each other. They are flexible, but must be separated + by pipes (`|`). + - You **can** have blank cells. -### Wiki - Root link +Example: -A link starting with a `/` is relative to the wiki root. +```markdown +| header 1 | header 2 | header 3 | +| --- | ------ |----------| +| cell 1 | cell 2 | cell 3 | +| cell 4 | cell 5 is longer | cell 6 is much longer than the others, but that's ok. It will eventually wrap the text when the cell is too large for the display size. | +| cell 7 | | cell <br> 9 | +``` -- This snippet links to `<wiki_root>/documentation`: +| header 1 | header 2 | header 3 | +| --- | ------ |---------:| +| cell 1 | cell 2 | cell 3 | +| cell 4 | cell 5 is longer | cell 6 is much longer than the others, but that's ok. It will eventually wrap the text when the cell is too large for the display size. | +| cell 7 | | cell <br> 9 | - ```markdown - [Link to Related Page](/documentation) - ``` +Additionally, you can choose the alignment of text within columns by adding colons (`:`) +to the sides of the "dash" lines in the second row. This will affect every cell in the column. -- This snippet links to `<wiki_root>/miscellaneous.md`: +> Note that the headers are always right aligned [within GitLab itself itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#tables). - ```markdown - [Link to Related Page](/miscellaneous.md) - ``` +```markdown +| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | +| :--- | :---: | ---: | :----------- | :------: | ------------: | +| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | +| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | +``` + +| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | +| :--- | :---: | ---: | :----------- | :------: | ------------: | +| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | +| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | ## References - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). -- The original [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. -- The detailed specification for CommonMark can be found in the [CommonMark Spec][commonmark-spec] +- The original [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) + at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. +- The detailed specification for CommonMark can be found in the [CommonMark Spec](https://spec.commonmark.org/current/) - The [CommonMark Dingus](http://try.commonmark.org) is a handy tool for testing CommonMark syntax. - -[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com -[^2]: This is my awesome footnote. - -[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md -[rouge]: http://rouge.jneen.net/ "Rouge website" -[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website" -[katex]: https://github.com/Khan/KaTeX "KaTeX website" -[katex-subset]: https://katex.org/docs/supported.html "Macros supported by KaTeX" -[asciidoctor-manual]: http://asciidoctor.org/docs/user-manual/#activating-stem-support "Asciidoctor user manual" -[commonmarker]: https://github.com/gjtorikian/commonmarker -[commonmark-spec]: https://spec.commonmark.org/current/ diff --git a/doc/user/operations_dashboard/index.md b/doc/user/operations_dashboard/index.md index 66362f27299..54bf3ff8a40 100644 --- a/doc/user/operations_dashboard/index.md +++ b/doc/user/operations_dashboard/index.md @@ -1,9 +1,6 @@ # Operations Dashboard **[PREMIUM]** -> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5781) -in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.5. -[Moved](https://gitlab.com/gitlab-org/gitlab-ee/issues/9218) to -[GitLab Premium](https://about.gitlab.com/pricing/) in 11.10. +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5781) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.5. [Moved](https://gitlab.com/gitlab-org/gitlab-ee/issues/9218) to [GitLab Premium](https://about.gitlab.com/pricing/) in 11.10. The Operations Dashboard provides a summary of each project's operational health, including pipeline and alert status. @@ -16,9 +13,9 @@ dashboard icon: ## Adding a project to the dashboard NOTE: **Note:** -For GitLab.com, the Operations Dashboard is available for free for public projects. -If your project is private, the group it belongs to must have a -[Gold](https://about.gitlab.com/pricing/) plan. +For GitLab.com, you can add your project to the Operations Dashboard for free if +your project is public. If your project is private, the group it belongs to must +have a [Silver](https://about.gitlab.com/pricing/) plan. To add a project to the dashboard: diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index df413a11af0..26cacbe5545 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -84,9 +84,8 @@ Click on **Register U2F Device** to complete the process. > **Note:** Recovery codes are not generated for U2F devices. -Should you ever lose access to your one time password authenticator, you can use one of the ten provided -backup codes to login to your account. We suggest copying them, printing them, or downloading them using -the **Download codes** button for storage in a safe place. +Immediately after successfully enabling two-factor authentication, you'll be prompted to download a set of set recovery codes. Should you ever lose access to your one time password authenticator, you can use one of them to log in to your account. We suggest copying them, printing them, or downloading them using +the **Download codes** button for storage in a safe place. If you choose to download them, the file will be called **gitlab-recovery-codes.txt**. CAUTION: **Caution:** Each code can be used only once to log in to your account. diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 547a0c36108..97d2dfc0f7e 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -519,10 +519,11 @@ service account of the cluster integration. ### Troubleshooting failed deployment jobs -GitLab will create a namespace and service account specifically for your -deployment jobs. On project level clusters, this happens when the cluster -is created. On group level clusters, resources are created immediately -before the deployment job starts. +Before the deployment jobs starts, GitLab creates the following specifically for +the deployment job: + +- A namespace. +- A service account. However, sometimes GitLab can not create them. In such instances, your job will fail with the message: diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md index 368031070c1..25d8abebf07 100644 --- a/doc/user/project/clusters/kubernetes_pod_logs.md +++ b/doc/user/project/clusters/kubernetes_pod_logs.md @@ -12,9 +12,9 @@ By displaying the logs directly in GitLab, developers can avoid having to manage 1. Go to **Operations > Environments** and find the environment which contains the desired pod, like `production`. 1. On the **Environments** page, you should see the status of the environment's pods with [Deploy Boards](../deploy_boards.md). 1. When mousing over the list of pods, a tooltip will appear with the exact pod name and status. -![Deploy Boards pod list](img/pod_logs_deploy_board.png) + ![Deploy Boards pod list](img/pod_logs_deploy_board.png) 1. Click on the desired pod to bring up the logs view, which will contain the last 500 lines for that pod. Support for pods with multiple containers is coming [in a future release](https://gitlab.com/gitlab-org/gitlab-ee/issues/6502). -![Deploy Boards pod list](img/kubernetes_pod_logs.png) + ![Deploy Boards pod list](img/kubernetes_pod_logs.png) ## Requirements diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md index 58b7fe33906..fdf9ce3e225 100644 --- a/doc/user/project/container_registry.md +++ b/doc/user/project/container_registry.md @@ -113,6 +113,7 @@ This feature requires GitLab 8.8 and GitLab Runner 1.2. Make sure that your GitLab Runner is configured to allow building Docker images by following the [Using Docker Build](../../ci/docker/using_docker_build.md) and [Using the GitLab Container Registry documentation](../../ci/docker/using_docker_build.md#using-the-gitlab-container-registry). +Alternatively, you can [build images with Kaniko](../../ci/docker/using_kaniko.md) if the Docker builds are not an option for you. ## Using with private projects diff --git a/doc/user/project/deploy_boards.md b/doc/user/project/deploy_boards.md index 2aef369c087..175384bc985 100644 --- a/doc/user/project/deploy_boards.md +++ b/doc/user/project/deploy_boards.md @@ -88,7 +88,10 @@ To display the Deploy Boards for a specific [environment] you should: Kubernetes. NOTE: **Note:** - Matching based on the Kubernetes `app` label was removed in [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14020) + Matching based on the Kubernetes `app` label was removed in [GitLab + 12.1](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14020). + To migrate, please apply the required annotations (see above) and + re-deploy your application. ![Deploy Boards Kubernetes Label](img/deploy_boards_kubernetes_label.png) diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md index ca59fe3cc7d..7520237251a 100644 --- a/doc/user/project/description_templates.md +++ b/doc/user/project/description_templates.md @@ -25,14 +25,14 @@ templates of the default branch will be taken into account. ## Use-cases - Add a template to be used in every issue for a specific project, -giving instructions and guidelines, requiring for information specific to that subject. -For example, if you have a project for tracking new blog posts, you can require the -title, outlines, author name, author social media information, etc. + giving instructions and guidelines, requiring for information specific to that subject. + For example, if you have a project for tracking new blog posts, you can require the + title, outlines, author name, author social media information, etc. - Following the previous example, you can make a template for every MR submitted -with a new blog post, requiring information about the post date, frontmatter data, -images guidelines, link to the related issue, reviewer name, etc. + with a new blog post, requiring information about the post date, frontmatter data, + images guidelines, link to the related issue, reviewer name, etc. - You can also create issues and merge request templates for different -stages of your workflow, e.g., feature proposal, feature improvement, bug report, etc. + stages of your workflow, e.g., feature proposal, feature improvement, bug report, etc. ## Creating issue templates diff --git a/doc/user/project/integrations/hipchat.md b/doc/user/project/integrations/hipchat.md index 0fd847d415f..7a0540aa9e3 100644 --- a/doc/user/project/integrations/hipchat.md +++ b/doc/user/project/integrations/hipchat.md @@ -23,7 +23,7 @@ allow GitLab to send messages only to *one* room. 1. Find "Build Your Own!" and click "Create". 1. Select the desired room, name the integration "GitLab", and click "Create". 1. In the "Send messages to this room by posting this URL" column, you should -see a URL in the format: + see a URL in the format: ``` https://api.hipchat.com/v2/room/<room>/notification?auth_token=<token> diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index 751e8e44e60..aab7131e353 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -160,7 +160,7 @@ receivers: > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4925) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.11. -Alerts can be used to trigger actions, like open an issue automatically. To configure the actions: +Alerts can be used to trigger actions, like open an issue automatically (enabled by default since `12.1`). To configure the actions: 1. Navigate to your project's **Settings > Operations > Incidents**. 1. Enable the option to create issues. diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md index 76dc6e49bce..4acbb4cc3f6 100644 --- a/doc/user/project/issues/index.md +++ b/doc/user/project/issues/index.md @@ -134,4 +134,4 @@ For more information, see [Crosslinking issues](crosslinking_issues.md). - [Export issues](csv_export.md) **[STARTER]** - [Issues API](../../../api/issues.md) - Configure an [external issue tracker](../../../integration/external-issue-tracker.md) such as Jira, Redmine, -or Bugzilla. + or Bugzilla. diff --git a/doc/user/project/merge_requests/merge_request_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md index fd151a6df45..8e8ec26daf2 100644 --- a/doc/user/project/merge_requests/merge_request_approvals.md +++ b/doc/user/project/merge_requests/merge_request_approvals.md @@ -12,9 +12,9 @@ to approve a merge request before it can be unblocked for merging. ## Use cases 1. Enforcing review of all code that gets merged into a repository. -2. Specifying code maintainers for an entire repository. -3. Specifying reviewers for a given proposed code change. -4. Specifying categories of reviewers, such as BE, FE, QA, DB, etc., for all proposed code changes. +1. Specifying code maintainers for an entire repository. +1. Specifying reviewers for a given proposed code change. +1. Specifying categories of reviewers, such as BE, FE, QA, DB, etc., for all proposed code changes. ## Enabling the new approvals interface @@ -246,7 +246,7 @@ restrictions (compared to [GitLab Starter](#overriding-the-merge-request-approva - Approval rules can be added to an MR with no restriction. - For project sourced approval rules, editing and removing approvers is not allowed. - The approvals required of all approval rules is configurable, but if a rule is backed by a project rule, then it is restricted -to the minimum approvals required set in the project's corresponding rule. + to the minimum approvals required set in the project's corresponding rule. ## Resetting approvals on push diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md index c93c7a5fe08..0dd60d84c42 100644 --- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md @@ -42,6 +42,8 @@ Navigate to your project's settings page and expand the **Merge requests** secti In the **Merge checks** subsection, select the **Pipelines must succeed** check box and hit **Save** for the changes to take effect. +NOTE: **Note:** This setting also prevents merge requests from being merged if there is no pipeline. + ![Pipelines must succeed settings](img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png) From now on, every time the pipeline fails you will not be able to merge the @@ -49,6 +51,21 @@ merge request from the UI, until you make all relevant jobs pass. ![Only allow merge if pipeline succeeds message](img/merge_when_pipeline_succeeds_only_if_succeeds_msg.png) +### Limitations + +When this setting is enabled, a merge request is prevented from being merged if there is no pipeline. This may conflict with some use cases where [`only/except`](../../../ci/yaml/README.md#onlyexcept-advanced) rules are used and they don't generate any pipelines. + +Users that expect to be able to merge a merge request in this scenario should ensure that [there is always a pipeline](https://gitlab.com/gitlab-org/gitlab-ce/issues/54226) and that it's succesful. + +For example, to that on merge requests there is always a passing job even though `only/except` rules may not generate any other jobs: + +```yaml +enable_merge: + only: merge_requests + script: + - echo true +``` + <!-- ## Troubleshooting Include any troubleshooting steps that you can foresee. If you know beforehand what issues diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index d585c19fc5c..bc9a11504cd 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -1,5 +1,5 @@ --- -last_updated: 2019-06-04 +last_updated: 2019-06-25 type: concepts, reference, howto --- @@ -138,9 +138,9 @@ verify your domain's ownership with a TXT record: > - **Do not** add any special chars after the default Pages domain. E.g., **do not** point your `subdomain.domain.com` to `namespace.gitlab.io.` or `namespace.gitlab.io/`. -> - GitLab Pages IP on GitLab.com [was changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) in 2017 +> - GitLab Pages IP on GitLab.com [was changed](https://about.gitlab.com/2017/03/06/we-are-changing-the-ip-of-gitlab-pages-on-gitlab-com/) in 2017. > - GitLab Pages IP on GitLab.com [has been changed](https://about.gitlab.com/2018/07/19/gcp-move-update/#gitlab-pages-and-custom-domains) - from `52.167.214.135` to `35.185.44.232` in 2018 + from `52.167.214.135` to `35.185.44.232` in 2018. ### Add your custom domain to GitLab Pages settings @@ -199,7 +199,7 @@ Certificates are NOT required to add to your custom highly recommendable. Let's start with an introduction to the importance of HTTPS. -Alternatively, jump ahead to [adding certificates to your project](#adding-certificates-to-your-project). +Alternatively, jump ahead to [adding certificates to your project](#adding-certificates-to-pages). ### Why should I care about HTTPS? @@ -255,12 +255,12 @@ which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-f Their certs are valid up to 15 years. See the tutorial on [how to add a CloudFlare Certificate to your GitLab Pages website](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/). -### Adding certificates to your project +### Adding certificates to Pages Regardless the CA you choose, the steps to add your certificate to your Pages project are the same. -### What do you need +#### Requirements 1. A PEM certificate 1. An intermediate certificate @@ -270,7 +270,7 @@ your Pages project are the same. These fields are found under your **Project**'s **Settings** > **Pages** > **New Domain**. -### What's what? +#### Certificate types - A PEM certificate is the certificate generated by the CA, which needs to be added to the field **Certificate (PEM)**. @@ -283,21 +283,32 @@ These fields are found under your **Project**'s **Settings** > **Pages** > **New - A private key is an encrypted key which validates your PEM against your domain. -### Now what? +#### Add the certificate to your project -Now that you hopefully understand why you need all -of this, it's simple: +Once you've met the requirements: -- Your PEM certificate needs to be added to the first field +- Your PEM certificate needs to be added to the first field. - If your certificate is missing its intermediate, copy and paste the root certificate (usually available from your CA website) and paste it in the [same field as your PEM certificate](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/), just jumping a line between them. -- Copy your private key and paste it in the last field +- Copy your private key and paste it in the last field. ->**Note:** +NOTE: **Note:** **Do not** open certificates or encryption keys in regular text editors. Always use code editors (such as Sublime Text, Atom, Dreamweaver, Brackets, etc). -_Read on about [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md)_ +## Force HTTPS for GitLab Pages websites + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28857) in GitLab 10.7. + +To make your website's visitors even more secure, you can choose to +force HTTPS for GitLab Pages. By doing so, all attempts to visit your +website via HTTP will be automatically redirected to HTTPS via 301. + +It works with both GitLab's default domain and with your custom +domain (as long as you've set a valid certificate for it). + +To enable this setting, navigate to your project's **Settings > Pages** +and tick the checkbox **Force HTTPS (requires valid certificates)**. diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md index 3e50cd4887c..fe92d19567d 100644 --- a/doc/user/project/pages/getting_started_part_two.md +++ b/doc/user/project/pages/getting_started_part_two.md @@ -77,10 +77,10 @@ containing the most popular SSGs templates to get you started. 1. [Fork](../../../gitlab-basics/fork-project.md) a sample project from the [GitLab Pages examples](https://gitlab.com/pages) group. 1. From the left sidebar, navigate to your project's **CI/CD > Pipelines** -and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your -site to the server. + and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your + site to the server. 1. Once the pipeline has finished successfully, find the link to visit your -website from your project's **Settings > Pages**. + website from your project's **Settings > Pages**. You can also take some **optional** further steps: @@ -89,14 +89,14 @@ You can also take some **optional** further steps: ![remove fork relationship](img/remove_fork_relationship.png) - _Make it a user or group website._ To turn a **project website** forked -from the Pages group into a **user/group** website, you'll need to: + from the Pages group into a **user/group** website, you'll need to: - Rename it to `namespace.gitlab.io`: go to your project's - **Settings > General** and expand **Advanced**. Scroll down to - **Rename repository** and change the path to `namespace.gitlab.io`. + **Settings > General** and expand **Advanced**. Scroll down to + **Rename repository** and change the path to `namespace.gitlab.io`. - Adjust your SSG's [base URL](#urls-and-baseurls) from `"project-name"` to - `""`. This setting will be at a different place for each SSG, as each of them - have their own structure and file tree. Most likely, it will be in the SSG's - config file. + `""`. This setting will be at a different place for each SSG, as each of them + have their own structure and file tree. Most likely, it will be in the SSG's + config file. ### Create a project from scratch diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index 04bda212128..fa79c393b72 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -12,7 +12,6 @@ type: index, reference > - Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8. > - Bundled project templates were [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/47857) in GitLab 11.8. - **GitLab Pages is a feature that allows you to publish static websites directly from a repository in GitLab.** @@ -105,10 +104,10 @@ To get started with GitLab Pages, you can either: ![Project templates for Pages](img/pages_project_templates_11-8.png) 1. From the left sidebar, navigate to your project's **CI/CD > Pipelines** -and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your -site to the server. + and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your + site to the server. 1. Once the pipeline has finished successfully, find the link to visit your -website from your project's **Settings > Pages**. + website from your project's **Settings > Pages**. Your website is then visible on your domain, and you can modify yourfiles as you wish. For every modification pushed to your repository, GitLab CI/CD diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index 4fab7f79e0c..4ea3bd9be9b 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -13,17 +13,17 @@ To familiarize yourself with GitLab Pages first: - Read an [introduction to GitLab Pages](index.md#overview). - Learn [how to get started with Pages](index.md#getting-started). - Learn how to enable GitLab Pages -across your GitLab instance on the [administrator documentation](../../../administration/pages/index.md). + across your GitLab instance on the [administrator documentation](../../../administration/pages/index.md). ## GitLab Pages requirements In brief, this is what you need to upload your website in GitLab Pages: 1. Domain of the instance: domain name that is used for GitLab Pages -(ask your administrator). + (ask your administrator). 1. GitLab CI/CD: a `.gitlab-ci.yml` file with a specific job named [`pages`][pages] in the root directory of your repository. 1. A directory called `public` in your site's repo containing the content -to be published. + to be published. 1. GitLab Runner enabled for the project. ## GitLab Pages on GitLab.com diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index ba890c5ac01..2bf8d4dfe7b 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -18,7 +18,7 @@ Adjust your project's name, description, avatar, [default branch](../repository/ ![general project settings](img/general_settings.png) -The project description also partially supports [standard markdown](../../markdown.md#standard-markdown). You can use [emphasis](../../markdown.md#emphasis), [links](../../markdown.md#links), and [line-breaks](../../markdown.md#line-breaks) to add more context to the project description. +The project description also partially supports [standard markdown](../../markdown.md#standard-markdown-and-extensions-in-gitlab). You can use [emphasis](../../markdown.md#emphasis), [links](../../markdown.md#links), and [line-breaks](../../markdown.md#line-breaks) to add more context to the project description. ### Sharing and permissions @@ -26,10 +26,10 @@ Set up your project's access, [visibility](../../../public_access/public_access. ![projects sharing permissions](img/sharing_and_permissions_settings.png) -If Issues are disabled, or you can't access Issues because you're not a project member, then Lables and Milestones +If Issues are disabled, or you can't access Issues because you're not a project member, then Lables and Milestones links will be missing from the sidebar UI. -You can still access them with direct links if you can access Merge Requests. This is deliberate, if you can see +You can still access them with direct links if you can access Merge Requests. This is deliberate, if you can see Issues or Merge Requests, both of which use Labels and Milestones, then you shouldn't be denied access to Labels and Milestones pages. ### Issue settings @@ -109,8 +109,8 @@ You can transfer an existing project into a [group](../../group/index.md) if: 1. You have at least **Maintainer** [permissions] to that group. 1. The project is in a subgroup you own. 1. You are at least a **Maintainer** of the project under your personal namespace. -Similarly, if you are an owner of a group, you can transfer any of its projects -under your own user. + Similarly, if you are an owner of a group, you can transfer any of its projects + under your own user. To transfer a project: diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index b2bf85335c5..7d85f4adfed 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -171,13 +171,13 @@ syntax but with some restrictions: - No global blocks can be defined (ie: `before_script` or `after_script`) - Only one job named `terminal` can be added to this file. - Only the keywords `image`, `services`, `tags`, `before_script`, `script`, and -`variables` are allowed to be used to configure the job. + `variables` are allowed to be used to configure the job. - To connect to the interactive terminal, the `terminal` job must be still alive -and running, otherwise the terminal won't be able to connect to the job's session. -By default the `script` keyword has the value `sleep 60` to prevent -the job from ending and giving the Web IDE enough time to connect. This means -that, if you override the default `script` value, you'll have to add a command -which would keep the job running, like `sleep`. + and running, otherwise the terminal won't be able to connect to the job's session. + By default the `script` keyword has the value `sleep 60` to prevent + the job from ending and giving the Web IDE enough time to connect. This means + that, if you override the default `script` value, you'll have to add a command + which would keep the job running, like `sleep`. In the code below there is an example of this configuration file: diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md index c03dffa967d..4286a3625a2 100644 --- a/doc/workflow/time_tracking.md +++ b/doc/workflow/time_tracking.md @@ -73,7 +73,15 @@ The following time units are available: Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h. -Other interesting links: +### Limit displayed units to hours + +> Introduced in GitLab 12.0. + +The display of time units can be limited to hours through the option in **Admin Area > Settings > Preferences** under 'Localization'. + +With this option enabled, `75h` is displayed instead of `1w 4d 3h`. + +## Other interesting links - [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/solutions/time-tracking/) diff --git a/lib/after_commit_queue.rb b/lib/after_commit_queue.rb index 6fb7985f955..6a180fdf338 100644 --- a/lib/after_commit_queue.rb +++ b/lib/after_commit_queue.rb @@ -15,7 +15,7 @@ module AfterCommitQueue end def run_after_commit_or_now(&block) - if AfterCommitQueue.inside_transaction? + if Gitlab::Database.inside_transaction? if ActiveRecord::Base.connection.current_transaction.records.include?(self) run_after_commit(&block) else @@ -32,18 +32,6 @@ module AfterCommitQueue true end - def self.open_transactions_baseline - if ::Rails.env.test? - return DatabaseCleaner.connections.count { |conn| conn.strategy.is_a?(DatabaseCleaner::ActiveRecord::Transaction) } - end - - 0 - end - - def self.inside_transaction? - ActiveRecord::Base.connection.open_transactions > open_transactions_baseline - end - protected def _run_after_commit_queue diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 65d7f68bbf9..c3821630b6b 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -8,7 +8,10 @@ module API BRANCH_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(branch: API::NO_SLASH_URL_PART_REGEX) - before { authorize! :download_code, user_project } + before do + require_repository_enabled! + authorize! :download_code, user_project + end helpers do params :filter_params do diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 80913f4ca07..eebded87ebc 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -6,7 +6,10 @@ module API class Commits < Grape::API include PaginationParams - before { authorize! :download_code, user_project } + before do + require_repository_enabled! + authorize! :download_code, user_project + end helpers do def user_access diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 25e9fdd5fce..ead01dc53f7 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -703,7 +703,7 @@ module API # See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more # information. expose :merge_status do |merge_request| - merge_request.check_if_can_be_merged + merge_request.check_mergeability merge_request.merge_status end expose :diff_head_sha, as: :sha diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 6cd43923559..ec58b3b7bb9 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -18,11 +18,16 @@ module API end params do use :pagination + optional :name, type: String, desc: 'Returns the environment with this name' + optional :search, type: String, desc: 'Returns list of environments matching the search criteria' + mutually_exclusive :name, :search, message: 'cannot be used together' end get ':id/environments' do authorize! :read_environment, user_project - present paginate(user_project.environments), with: Entities::Environment, current_user: current_user + environments = ::EnvironmentsFinder.new(user_project, current_user, params).find + + present paginate(environments), with: Entities::Environment, current_user: current_user end desc 'Creates a new environment' do diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6382d295f79..8ae42c6dadd 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -250,6 +250,10 @@ module API authorize! :update_build, user_project end + def require_repository_enabled!(subject = :global) + not_found!("Repository") unless user_project.feature_available?(:repository, current_user) + end + def require_gitlab_workhorse! unless env['HTTP_GITLAB_WORKHORSE'].present? forbidden!('Request should be executed via GitLab Workhorse') diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 955624404f1..bf87e9ec2ff 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -397,28 +397,16 @@ module API present merge_request, with: Entities::MergeRequest, current_user: current_user, project: user_project end - desc 'Merge a merge request to its default temporary merge ref path' - params do - optional :merge_commit_message, type: String, desc: 'Custom merge commit message' - end - put ':id/merge_requests/:merge_request_iid/merge_to_ref' do + desc 'Returns the up to date merge-ref HEAD commit' + get ':id/merge_requests/:merge_request_iid/merge_ref' do merge_request = find_project_merge_request(params[:merge_request_iid]) - authorize! :admin_merge_request, user_project - - merge_params = { - commit_message: params[:merge_commit_message] - } - - result = ::MergeRequests::MergeToRefService - .new(merge_request.target_project, current_user, merge_params) - .execute(merge_request) + result = ::MergeRequests::MergeabilityCheckService.new(merge_request).execute(recheck: true) - if result[:status] == :success - present result.slice(:commit_id), 200 + if result.success? + present :commit_id, result.payload.dig(:merge_ref_head, :commit_id) else - http_status = result[:http_status] || 400 - render_api_error!(result[:message], http_status) + render_api_error!(result.message, 400) end end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 6767ef882cb..3c5c1a9fd5f 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -36,10 +36,6 @@ module API given akismet_enabled: ->(val) { val } do requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com' end - optional :clientside_sentry_enabled, type: Boolean, desc: 'Sentry can also be used for reporting and logging clientside exceptions. https://sentry.io/for/javascript/' - given clientside_sentry_enabled: ->(val) { val } do - requires :clientside_sentry_dsn, type: String, desc: 'Clientside Sentry Data Source Name' - end optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts" optional :default_project_creation, type: Integer, values: ::Gitlab::Access.project_creation_values, desc: 'Determine if developers can create projects in the group' @@ -114,10 +110,6 @@ module API end optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.' optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up' - optional :sentry_enabled, type: Boolean, desc: 'Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: https://getsentry.com' - given sentry_enabled: ->(val) { val } do - requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name' - end optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.' optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects' given shared_runners_enabled: ->(val) { val } do diff --git a/lib/api/todos.rb b/lib/api/todos.rb index d2196f05173..871eaabc887 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -77,7 +77,7 @@ module API use :pagination end get do - todos = paginate(find_todos.with_api_entity_associations) + todos = paginate(find_todos.with_entity_associations) options = { with: Entities::Todo, current_user: current_user } batch_load_issuable_metadata(todos, options) diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb index 55411b5a8c4..fc3223e7442 100644 --- a/lib/gitlab/ci/ansi2html.rb +++ b/lib/gitlab/ci/ansi2html.rb @@ -200,9 +200,7 @@ module Gitlab css_classes = %w[section line] + sections.map { |section| "s_#{section}" } end - ensure_open_new_tag - write_raw %{<br/>} - close_open_tags + write_in_tag %{<br/>} write_raw %{<span class="#{css_classes.join(' ')}"></span>} if css_classes.any? @lineno_in_section += 1 open_new_tag diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb index 531c9ce4256..49c680605ea 100644 --- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb +++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb @@ -8,7 +8,6 @@ module Gitlab def unmet? deployment_cluster.present? && deployment_cluster.managed? && - !deployment_cluster.project_type? && (kubernetes_namespace.new_record? || kubernetes_namespace.service_account_token.blank?) end @@ -21,7 +20,7 @@ module Gitlab private def deployment_cluster - build.deployment&.cluster + build.deployment&.deployment_platform_cluster end def kubernetes_namespace diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml index 46c4c755729..8a84744aa2d 100644 --- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml @@ -17,7 +17,7 @@ code_quality: --env SOURCE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock - "registry.gitlab.com/gitlab-org/security-products/codequality:11-8-stable" /code + "registry.gitlab.com/gitlab-org/security-products/codequality:12-0-stable" /code artifacts: reports: codequality: gl-code-quality-report.json diff --git a/lib/gitlab/data_builder/note.rb b/lib/gitlab/data_builder/note.rb index 16e62622ed4..2c4ef73a688 100644 --- a/lib/gitlab/data_builder/note.rb +++ b/lib/gitlab/data_builder/note.rb @@ -44,7 +44,7 @@ module Gitlab data[:commit] = build_data_for_commit(project, user, note) elsif note.for_issue? data[:issue] = note.noteable.hook_attrs - data[:issue][:labels] = note.noteable.labels(&:hook_attrs) + data[:issue][:labels] = note.noteable.labels_hook_attrs elsif note.for_merge_request? data[:merge_request] = note.noteable.hook_attrs elsif note.for_snippet? diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 8da98cc3909..e4d4779ba9a 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -234,6 +234,7 @@ module Gitlab def self.connection ActiveRecord::Base.connection end + private_class_method :connection def self.cached_column_exists?(table_name, column_name) connection.schema_cache.columns_hash(table_name).has_key?(column_name.to_s) @@ -243,8 +244,6 @@ module Gitlab connection.schema_cache.data_source_exists?(table_name) end - private_class_method :connection - def self.database_version row = connection.execute("SELECT VERSION()").first @@ -272,5 +271,20 @@ module Gitlab end end end + + # inside_transaction? will return true if the caller is running within a transaction. Handles special cases + # when running inside a test environment, in which the entire test is running with a DatabaseCleaner transaction + def self.inside_transaction? + ActiveRecord::Base.connection.open_transactions > open_transactions_baseline + end + + def self.open_transactions_baseline + if ::Rails.env.test? + return DatabaseCleaner.connections.count { |conn| conn.strategy.is_a?(DatabaseCleaner::ActiveRecord::Transaction) } + end + + 0 + end + private_class_method :open_transactions_baseline end end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index a6739f12280..19b6aab1c4f 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -15,11 +15,6 @@ module Gitlab SEARCH_CONTEXT_LINES = 3 REV_LIST_COMMIT_LIMIT = 2_000 - # In https://gitlab.com/gitlab-org/gitaly/merge_requests/698 - # We copied these two prefixes into gitaly-go, so don't change these - # or things will break! (REBASE_WORKTREE_PREFIX and SQUASH_WORKTREE_PREFIX) - REBASE_WORKTREE_PREFIX = 'rebase'.freeze - SQUASH_WORKTREE_PREFIX = 'squash'.freeze GITALY_INTERNAL_URL = 'ssh://gitaly/internal.git'.freeze GITLAB_PROJECTS_TIMEOUT = Gitlab.config.gitlab_shell.git_timeout EMPTY_REPOSITORY_CHECKSUM = '0000000000000000000000000000000000000000'.freeze diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index d21b98d36ea..a80ce462ab0 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -271,26 +271,30 @@ module Gitlab end def find_commit(revision) - if Gitlab::SafeRequestStore.active? - # We don't use Gitlab::SafeRequestStore.fetch(key) { ... } directly - # because `revision` can be a branch name, so we can't use it as a key - # as it could point to another commit later on (happens a lot in - # tests). - key = { - storage: @gitaly_repo.storage_name, - relative_path: @gitaly_repo.relative_path, - commit_id: revision - } - return Gitlab::SafeRequestStore[key] if Gitlab::SafeRequestStore.exist?(key) - - commit = call_find_commit(revision) - return unless commit - - key[:commit_id] = commit.id unless GitalyClient.ref_name_caching_allowed? + return call_find_commit(revision) unless Gitlab::SafeRequestStore.active? + + # We don't use Gitlab::SafeRequestStore.fetch(key) { ... } directly + # because `revision` can be a branch name, so we can't use it as a key + # as it could point to another commit later on (happens a lot in + # tests). + key = { + storage: @gitaly_repo.storage_name, + relative_path: @gitaly_repo.relative_path, + commit_id: revision + } + return Gitlab::SafeRequestStore[key] if Gitlab::SafeRequestStore.exist?(key) + + commit = call_find_commit(revision) + + if GitalyClient.ref_name_caching_allowed? Gitlab::SafeRequestStore[key] = commit - else - call_find_commit(revision) + return commit end + + return unless commit + + key[:commit_id] = commit.id + Gitlab::SafeRequestStore[key] = commit end # rubocop: disable CodeReuse/ActiveRecord diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 582c3065189..92917028851 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -16,8 +16,8 @@ module Gitlab gon.shortcuts_path = Gitlab::Routing.url_helpers.help_page_path('shortcuts') gon.user_color_scheme = Gitlab::ColorSchemes.for_user(current_user).css_class - if Gitlab::CurrentSettings.clientside_sentry_enabled - gon.sentry_dsn = Gitlab::CurrentSettings.clientside_sentry_dsn + if Gitlab.config.sentry.enabled + gon.sentry_dsn = Gitlab.config.sentry.clientside_dsn gon.sentry_environment = Gitlab.config.sentry.environment end diff --git a/lib/gitlab/graphql/authorize/authorize_resource.rb b/lib/gitlab/graphql/authorize/authorize_resource.rb index b367a97105c..ef5caaf5b0e 100644 --- a/lib/gitlab/graphql/authorize/authorize_resource.rb +++ b/lib/gitlab/graphql/authorize/authorize_resource.rb @@ -27,12 +27,6 @@ module Gitlab raise NotImplementedError, "Implement #find_object in #{self.class.name}" end - def authorized_find(*args) - object = find_object(*args) - - object if authorized?(object) - end - def authorized_find!(*args) object = find_object(*args) authorize!(object) @@ -48,6 +42,12 @@ module Gitlab end def authorized?(object) + # Sanity check. We don't want to accidentally allow a developer to authorize + # without first adding permissions to authorize against + if self.class.required_permissions.empty? + raise Gitlab::Graphql::Errors::ArgumentError, "#{self.class.name} has no authorizations" + end + self.class.required_permissions.all? do |ability| # The actions could be performed across multiple objects. In which # case the current user is common, and we could benefit from the diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb index cfc9ebe4f92..e5f86ca02b5 100644 --- a/lib/gitlab/hook_data/issue_builder.rb +++ b/lib/gitlab/hook_data/issue_builder.rb @@ -45,7 +45,7 @@ module Gitlab human_time_estimate: issue.human_time_estimate, assignee_ids: issue.assignee_ids, assignee_id: issue.assignee_ids.first, # This key is deprecated - labels: issue.labels + labels: issue.labels_hook_attrs } issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes) diff --git a/lib/gitlab/json_cache.rb b/lib/gitlab/json_cache.rb index 9a0b2b35112..84c6817f3c7 100644 --- a/lib/gitlab/json_cache.rb +++ b/lib/gitlab/json_cache.rb @@ -22,10 +22,10 @@ module Gitlab expanded_cache_key = [namespace, key].compact if cache_key_with_version - expanded_cache_key << Rails.version + expanded_cache_key << [Gitlab::VERSION, Rails.version] end - expanded_cache_key.join(':') + expanded_cache_key.flatten.join(':').freeze end def expire(key) @@ -34,7 +34,7 @@ module Gitlab def read(key, klass = nil) value = backend.read(cache_key(key)) - value = parse_value(value, klass) if value + value = parse_value(value, klass) unless value.nil? value end diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb index 1103a4eed1d..22bd00751bc 100644 --- a/lib/gitlab/kubernetes.rb +++ b/lib/gitlab/kubernetes.rb @@ -43,6 +43,14 @@ module Gitlab }) end + def filter_by_legacy_label(items, app, env) + legacy_items = filter_by_label(items, { app: env }) + + non_legacy_items = filter_by_project_environment(legacy_items, app, env) + + legacy_items - non_legacy_items + end + # Converts a pod (as returned by the kubernetes API) into a terminal def terminals_for_pod(api_url, namespace, pod) metadata = pod.fetch("metadata", {}) diff --git a/lib/gitlab/lets_encrypt.rb b/lib/gitlab/lets_encrypt.rb new file mode 100644 index 00000000000..cdf24f24647 --- /dev/null +++ b/lib/gitlab/lets_encrypt.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Gitlab + module LetsEncrypt + def self.enabled?(pages_domain = nil) + return false unless Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted + + return false unless Feature.enabled?(:pages_auto_ssl) + + # If no domain is passed, just check whether we're enabled globally + return true unless pages_domain + + !!pages_domain.project && Feature.enabled?(:pages_auto_ssl_for_project, pages_domain.project) + end + end +end diff --git a/lib/gitlab/lets_encrypt/client.rb b/lib/gitlab/lets_encrypt/client.rb index 66aea137012..ad2921ed555 100644 --- a/lib/gitlab/lets_encrypt/client.rb +++ b/lib/gitlab/lets_encrypt/client.rb @@ -34,14 +34,6 @@ module Gitlab acme_client.terms_of_service end - def enabled? - return false unless Feature.enabled?(:pages_auto_ssl) - - return false unless private_key - - Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted - end - private def acme_client @@ -65,7 +57,7 @@ module Gitlab end def ensure_account - raise 'Acme integration is disabled' unless enabled? + raise 'Acme integration is disabled' unless ::Gitlab::LetsEncrypt.enabled? @acme_account ||= acme_client.new_account(contact: contact, terms_of_service_agreed: true) end diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb index a07b1246bee..a13b3f9e069 100644 --- a/lib/gitlab/path_regex.rb +++ b/lib/gitlab/path_regex.rb @@ -53,7 +53,6 @@ module Gitlab sent_notifications slash-command-logo.png snippets - u unsubscribes uploads users diff --git a/lib/gitlab/search/found_blob.rb b/lib/gitlab/search/found_blob.rb index a62ab1521a7..01ce90c85f7 100644 --- a/lib/gitlab/search/found_blob.rb +++ b/lib/gitlab/search/found_blob.rb @@ -93,7 +93,7 @@ module Gitlab data = { id: blob.id, binary_filename: blob.path, - binary_basename: File.basename(blob.path, File.extname(blob.path)), + binary_basename: path_without_extension(blob.path), ref: ref, startline: 1, binary_data: blob.data, @@ -111,6 +111,10 @@ module Gitlab content_match.match(FILENAME_REGEXP) { |matches| matches[:filename] } end + def path_without_extension(path) + Pathname.new(path).sub_ext('').to_s + end + def parsed_content strong_memoize(:parsed_content) do if content_match @@ -137,8 +141,7 @@ module Gitlab filename = matches[:filename] startline = matches[:startline] startline = startline.to_i - index - extname = Regexp.escape(File.extname(filename)) - basename = filename.sub(/#{extname}$/, '') + basename = path_without_extension(filename) end data << line.sub(prefix.to_s, '') diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb index 72c44114001..764db14d720 100644 --- a/lib/gitlab/sentry.rb +++ b/lib/gitlab/sentry.rb @@ -4,7 +4,7 @@ module Gitlab module Sentry def self.enabled? (Rails.env.production? || Rails.env.development?) && - Gitlab::CurrentSettings.sentry_enabled? + Gitlab.config.sentry.enabled end def self.context(current_user = nil) diff --git a/lib/gitlab/time_tracking_formatter.rb b/lib/gitlab/time_tracking_formatter.rb index cc206010e74..302da91328a 100644 --- a/lib/gitlab/time_tracking_formatter.rb +++ b/lib/gitlab/time_tracking_formatter.rb @@ -6,7 +6,7 @@ module Gitlab def parse(string) with_custom_config do - string.sub!(/\A-/, '') + string = string.sub(/\A-/, '') seconds = ChronicDuration.parse(string, default_unit: 'hours') rescue nil seconds *= -1 if seconds && Regexp.last_match @@ -16,10 +16,12 @@ module Gitlab def output(seconds) with_custom_config do - ChronicDuration.output(seconds, format: :short, limit_to_hours: false, weeks: true) rescue nil + ChronicDuration.output(seconds, format: :short, limit_to_hours: limit_to_hours_setting, weeks: true) rescue nil end end + private + def with_custom_config # We may want to configure it through project settings in a future version. ChronicDuration.hours_per_day = 8 @@ -32,5 +34,9 @@ module Gitlab result end + + def limit_to_hours_setting + Gitlab::CurrentSettings.time_tracking_limit_to_hours + end end end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index c531eb1d216..2bf71701b57 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -21,10 +21,10 @@ namespace :gitlab do backup.cleanup backup.remove_old - puts "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \ + progress.puts "Warning: Your gitlab.rb and gitlab-secrets.json files contain sensitive data \n" \ "and are not included in this backup. You will need these files to restore a backup.\n" \ "Please back them up manually.".color(:red) - puts "Backup task is done." + progress.puts "Backup task is done." end # Restore backup of GitLab system diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ed06389cf05..2da0633f4cc 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16,6 +16,9 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" +msgid " (from %{timeoutSource})" +msgstr "" + msgid " Please sign in." msgstr "" @@ -38,6 +41,11 @@ msgid_plural "%d commits behind" msgstr[0] "" msgstr[1] "" +msgid "%d commit," +msgid_plural "%d commits," +msgstr[0] "" +msgstr[1] "" + msgid "%d commits" msgstr "" @@ -388,6 +396,9 @@ msgstr "" msgid "<no name set>" msgstr "" +msgid "<no scopes selected>" +msgstr "" + msgid "<strong>%{changedFilesLength} unstaged</strong> and <strong>%{stagedFilesLength} staged</strong> changes" msgstr "" @@ -499,6 +510,54 @@ msgstr "" msgid "Access to '%{classification_label}' not allowed" msgstr "" +msgid "AccessTokens|Access Tokens" +msgstr "" + +msgid "AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working." +msgstr "" + +msgid "AccessTokens|Are you sure? Any issue email addresses currently in use will stop working." +msgstr "" + +msgid "AccessTokens|Created" +msgstr "" + +msgid "AccessTokens|Feed token" +msgstr "" + +msgid "AccessTokens|Incoming email token" +msgstr "" + +msgid "AccessTokens|It cannot be used to access any other data." +msgstr "" + +msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens." +msgstr "" + +msgid "AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens." +msgstr "" + +msgid "AccessTokens|Personal Access Tokens" +msgstr "" + +msgid "AccessTokens|They are the only accepted password when you have Two-Factor Authentication (2FA) enabled." +msgstr "" + +msgid "AccessTokens|You can also use personal access tokens to authenticate against Git over HTTP." +msgstr "" + +msgid "AccessTokens|You can generate a personal access token for each application you use that needs access to the GitLab API." +msgstr "" + +msgid "AccessTokens|Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar, and is included in those feed URLs." +msgstr "" + +msgid "AccessTokens|Your incoming email token is used to authenticate you when you create a new issue by email, and is included in your personal project-specific email addresses." +msgstr "" + +msgid "AccessTokens|reset it" +msgstr "" + msgid "Account" msgstr "" @@ -511,6 +570,9 @@ msgstr "" msgid "Active" msgstr "" +msgid "Active %{type} Tokens (%{token_length})" +msgstr "" + msgid "Active Sessions" msgstr "" @@ -529,6 +591,9 @@ msgstr "" msgid "Add README" msgstr "" +msgid "Add a %{type} token" +msgstr "" + msgid "Add a GPG key" msgstr "" @@ -1156,6 +1221,9 @@ msgstr "" msgid "Are you sure you want to reset the health check token?" msgstr "" +msgid "Are you sure you want to revoke this %{type} Token? This action cannot be undone." +msgstr "" + msgid "Are you sure you want to revoke this nickname?" msgstr "" @@ -2061,6 +2129,9 @@ msgstr "" msgid "Clear search input" msgstr "" +msgid "Clear templates search input" +msgstr "" + msgid "Click any <strong>project name</strong> in the project list below to navigate to the project milestone." msgstr "" @@ -2786,9 +2857,15 @@ msgstr "" msgid "Configure Let's Encrypt" msgstr "" +msgid "Configure Prometheus" +msgstr "" + msgid "Configure automatic git checks and housekeeping on repositories." msgstr "" +msgid "Configure existing installation" +msgstr "" + msgid "Configure limits for web and API requests." msgstr "" @@ -2975,6 +3052,9 @@ msgstr "" msgid "Copy link" msgstr "" +msgid "Copy personal access token to clipboard" +msgstr "" + msgid "Copy reference to clipboard" msgstr "" @@ -3023,6 +3103,9 @@ msgstr "" msgid "Create" msgstr "" +msgid "Create %{type} token" +msgstr "" + msgid "Create New Directory" msgstr "" @@ -3056,6 +3139,12 @@ msgstr "" msgid "Create commit" msgstr "" +msgid "Create confidential merge request" +msgstr "" + +msgid "Create confidential merge request and branch" +msgstr "" + msgid "Create directory" msgstr "" @@ -3137,6 +3226,9 @@ msgstr "" msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue" msgstr "" +msgid "Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available." +msgstr "" + msgid "Cron Timezone" msgstr "" @@ -3508,6 +3600,9 @@ msgstr "" msgid "Diff limits" msgstr "" +msgid "DiffsCompareBaseBranch|(base)" +msgstr "" + msgid "Diffs|No file name available" msgstr "" @@ -3775,9 +3870,6 @@ msgstr "" msgid "Enable HTML emails" msgstr "" -msgid "Enable Sentry for error reporting and logging." -msgstr "" - msgid "Enable access to the Performance Bar for a given group." msgstr "" @@ -3997,9 +4089,6 @@ msgstr "" msgid "Error" msgstr "" -msgid "Error Reporting and Logging" -msgstr "" - msgid "Error Tracking" msgstr "" @@ -4222,6 +4311,12 @@ msgstr "" msgid "Expired %{expiredOn}" msgstr "" +msgid "Expires" +msgstr "" + +msgid "Expires at" +msgstr "" + msgid "Expires in %{expires_at}" msgstr "" @@ -4647,6 +4742,9 @@ msgstr "" msgid "Get started with error tracking" msgstr "" +msgid "Get started with performance monitoring" +msgstr "" + msgid "Getting started with releases" msgstr "" @@ -4992,6 +5090,9 @@ msgstr "" msgid "Help page text and support page url." msgstr "" +msgid "Helps prevent bots from creating accounts. We currently only support %{recaptcha_v2_link_start}reCAPTCHA v2%{recaptcha_v2_link_end}" +msgstr "" + msgid "Hide archived projects" msgstr "" @@ -5324,6 +5425,9 @@ msgstr "" msgid "Install a soft token authenticator like %{free_otp_link} or Google Authenticator from your application repository and scan this QR code. More information is available in the %{help_link_start}documentation%{help_link_end}." msgstr "" +msgid "Install on clusters" +msgstr "" + msgid "Installed" msgstr "" @@ -5830,6 +5934,9 @@ msgstr "" msgid "Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}." msgstr "" +msgid "Limit display of time tracking units to hours." +msgstr "" + msgid "Limited to showing %d event at most" msgid_plural "Limited to showing %d events at most" msgstr[0] "" @@ -5922,6 +6029,9 @@ msgstr "" msgid "Make issue confidential." msgstr "" +msgid "Make sure you save it - you won't be able to access it again." +msgstr "" + msgid "Make sure you're logged into the account that owns the projects you'd like to import." msgstr "" @@ -5970,6 +6080,9 @@ msgstr "" msgid "Manual job" msgstr "" +msgid "ManualOrdering|Couldn't save the order of the issues" +msgstr "" + msgid "Map a FogBugz account ID to a GitLab user" msgstr "" @@ -6575,6 +6688,9 @@ msgstr "" msgid "No contributions were found" msgstr "" +msgid "No data found" +msgstr "" + msgid "No details available" msgstr "" @@ -6638,6 +6754,9 @@ msgstr "" msgid "No schedules" msgstr "" +msgid "No template" +msgstr "" + msgid "No, directly import the existing email addresses and usernames." msgstr "" @@ -6943,13 +7062,13 @@ msgstr "" msgid "Pagination|Last »" msgstr "" -msgid "Pagination|Next" +msgid "Pagination|Next ›" msgstr "" -msgid "Pagination|Prev" +msgid "Pagination|« First" msgstr "" -msgid "Pagination|« First" +msgid "Pagination|‹ Prev" msgstr "" msgid "Parameter" @@ -7042,6 +7161,9 @@ msgstr "" msgid "Pick a name" msgstr "" +msgid "Pick a name for the application, and we'll give you a unique %{type} token." +msgstr "" + msgid "Pin code" msgstr "" @@ -7567,6 +7689,9 @@ msgstr "" msgid "Profiles|Full name" msgstr "" +msgid "Profiles|Impersonation" +msgstr "" + msgid "Profiles|Include private contributions on my profile" msgstr "" @@ -7609,6 +7734,9 @@ msgstr "" msgid "Profiles|Path" msgstr "" +msgid "Profiles|Personal Access" +msgstr "" + msgid "Profiles|Position and size your new avatar" msgstr "" @@ -7744,6 +7872,12 @@ msgstr "" msgid "Profiles|e.g. My MacBook key" msgstr "" +msgid "Profiles|impersonation" +msgstr "" + +msgid "Profiles|personal access" +msgstr "" + msgid "Profiles|username" msgstr "" @@ -8062,6 +8196,9 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "Prometheus server" +msgstr "" + msgid "PrometheusService|%{exporters} with %{metrics} were found" msgstr "" @@ -8532,6 +8669,9 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reset template" +msgstr "" + msgid "Resolve all discussions in new issue" msgstr "" @@ -8771,6 +8911,9 @@ msgstr "" msgid "Scoped label" msgstr "" +msgid "Scopes" +msgstr "" + msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right." msgstr "" @@ -9146,6 +9289,9 @@ msgstr "" msgid "Show command" msgstr "" +msgid "Show comments" +msgstr "" + msgid "Show comments only" msgstr "" @@ -9175,6 +9321,9 @@ msgid_plural "Showing %d events" msgstr[0] "" msgstr[1] "" +msgid "Showing last %{size} of log -" +msgstr "" + msgid "Side-by-side" msgstr "" @@ -9580,6 +9729,9 @@ msgstr "" msgid "Status:" msgstr "" +msgid "Stay updated about the performance and health of your environment by configuring Prometheus to monitor your deployments." +msgstr "" + msgid "Stop environment" msgstr "" @@ -10479,6 +10631,9 @@ msgstr "" msgid "This user cannot be unlocked manually from GitLab" msgstr "" +msgid "This user has no active %{type} Tokens." +msgstr "" + msgid "This user has no identities" msgstr "" @@ -10491,6 +10646,9 @@ msgstr "" msgid "Thursday" msgstr "" +msgid "Time" +msgstr "" + msgid "Time based: Yes" msgstr "" @@ -10741,6 +10899,9 @@ msgstr "" msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed." msgstr "" +msgid "To see all the user's personal access tokens you must impersonate them first." +msgstr "" + msgid "To specify the notification level per project of a group you belong to, you need to visit project page and change notification level there." msgstr "" @@ -10936,6 +11097,9 @@ msgstr "" msgid "U2F only works with HTTPS-enabled websites. Contact your administrator for more details." msgstr "" +msgid "Unable to connect to Prometheus server" +msgstr "" + msgid "Unable to connect to server: %{error}" msgstr "" @@ -11341,6 +11505,9 @@ msgstr "" msgid "View details: %{details_url}" msgstr "" +msgid "View documentation" +msgstr "" + msgid "View file @ " msgstr "" @@ -11416,6 +11583,9 @@ msgstr "" msgid "Wait for the source to load to copy it to the clipboard" msgstr "" +msgid "Waiting for performance data" +msgstr "" + msgid "Want to see the data? Please ask an administrator for access." msgstr "" @@ -11673,6 +11843,9 @@ msgstr "" msgid "You are attempting to update a file that has changed since you started editing it." msgstr "" +msgid "You are connected to the Prometheus server, but there is currently no data to display." +msgstr "" + msgid "You are going to remove %{group_name}, this will also remove all of its subgroups and projects. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "" @@ -11952,6 +12125,9 @@ msgstr "" msgid "Your Groups" msgstr "" +msgid "Your New Personal Access Token" +msgstr "" + msgid "Your Primary Email will be used for avatar detection." msgstr "" @@ -12618,6 +12794,9 @@ msgstr "" msgid "verify ownership" msgstr "" +msgid "version %{versionIndex}" +msgstr "" + msgid "via %{closed_via}" msgstr "" diff --git a/package.json b/package.json index 54998cc81dd..ce3e5bd4490 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "apollo-upload-client": "^10.0.0", "at.js": "^1.5.4", "autosize": "^4.0.0", - "axios": "^0.17.1", + "axios": "^0.19.0", "babel-loader": "^8.0.5", "bootstrap": "4.3.1", "brace-expansion": "^1.1.8", @@ -87,7 +87,7 @@ "imports-loader": "^0.8.0", "jed": "^1.1.1", "jest-transform-graphql": "^2.1.0", - "jquery": "^3.2.1", + "jquery": "^3.4.1", "jquery-ujs": "1.2.2", "jquery.caret": "^0.3.1", "jquery.waitforimages": "^2.2.0", @@ -96,7 +96,7 @@ "jszip-utils": "^0.0.2", "katex": "^0.10.0", "marked": "^0.3.12", - "mermaid": "^8.0.0", + "mermaid": "^8.1.0", "monaco-editor": "^0.15.6", "monaco-editor-webpack-plugin": "^1.7.0", "mousetrap": "^1.4.6", diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb index 68850d989b1..fae7818f871 100644 --- a/qa/qa/page/project/pipeline/index.rb +++ b/qa/qa/page/project/pipeline/index.rb @@ -4,7 +4,7 @@ module QA::Page module Project::Pipeline class Index < QA::Page::Base view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do - element :pipeline_link, 'class="js-pipeline-url-link"' # rubocop:disable QA/ElementWithPattern + element :pipeline_link, 'class="js-pipeline-url-link' # rubocop:disable QA/ElementWithPattern end def click_on_latest_pipeline diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb index f6f0468e76e..796de44a012 100644 --- a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true module QA - # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/49 - context 'Create', :smoke, :quarantine do + context 'Create', :smoke do describe 'Snippet creation' do it 'User creates a snippet' do Runtime::Browser.visit(:gitlab, Page::Main::Login) @@ -13,7 +12,7 @@ module QA Resource::Snippet.fabricate_via_browser_ui! do |snippet| snippet.title = 'Snippet title' snippet.description = 'Snippet description' - snippet.visibility = 'Public' + snippet.visibility = 'Private' snippet.file_name = 'New snippet file name' snippet.file_content = 'Snippet file text' end @@ -21,8 +20,7 @@ module QA Page::Dashboard::Snippet::Show.perform do |snippet| expect(snippet).to have_snippet_title('Snippet title') expect(snippet).to have_snippet_description('Snippet description') - expect(snippet).to have_embed_type('Embed') - expect(snippet).to have_visibility_type('Public') + expect(snippet).to have_visibility_type('Private') expect(snippet).to have_file_name('New snippet file name') expect(snippet).to have_file_content('Snippet file text') end diff --git a/scripts/generate-gems-memory-metrics-static b/scripts/generate-gems-memory-metrics-static new file mode 100755 index 00000000000..aa7ce3615bf --- /dev/null +++ b/scripts/generate-gems-memory-metrics-static @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +abort "usage: #{__FILE__} <memory_bundle_objects_file_name>" unless ARGV.length == 1 +memory_bundle_objects_file_name = ARGV.first + +full_report = File.readlines(memory_bundle_objects_file_name) + +allocated_str = full_report[1] +retained_str = full_report[2] +allocated_stats = /Total allocated: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(allocated_str) +retained_stats = /Total retained: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(retained_str) + +abort 'failed to process the benchmark output' unless allocated_stats && retained_stats + +puts "memory_static_objects_allocated_mb #{(allocated_stats[:bytes].to_f / (1024 * 1024)).round(1)}" +puts "memory_static_objects_retained_mb #{(retained_stats[:bytes].to_f / (1024 * 1024)).round(1)}" +puts "memory_static_objects_allocated_items #{allocated_stats[:objects]}" +puts "memory_static_objects_retained_items #{retained_stats[:objects]}" diff --git a/scripts/generate-gems-size-metrics-static b/scripts/generate-gems-size-metrics-static new file mode 100755 index 00000000000..ceec8aaccf1 --- /dev/null +++ b/scripts/generate-gems-size-metrics-static @@ -0,0 +1,30 @@ +#!/usr/bin/env ruby + +abort "usage: #{__FILE__} <memory_bundle_mem_file_name>" unless ARGV.length == 1 +memory_bundle_mem_file_name = ARGV.first + +full_report = File.readlines(memory_bundle_mem_file_name) + +def total_size(memory_bundle_mem_report) + stats = /TOP: (?<total_mibs_str>.*) MiB/.match(memory_bundle_mem_report.first) + abort 'failed to process the benchmark output' unless stats + "gem_total_size_mb #{stats[:total_mibs_str].to_f.round(1)}" +end + +TOP_LEVEL_GEM_LOG_FORMAT = /^ (?<gem_name>\S.*):\s*(?<gem_size>\d[.\d]*)\s*MiB/.freeze +def all_gems(memory_bundle_mem_report) + memory_bundle_mem_report.map do |line| + TOP_LEVEL_GEM_LOG_FORMAT.match(line) + end.compact +end + +def gems_as_metrics(gems_match_data) + gems_match_data.map do |gem| + gem_name = gem[:gem_name] + gem_size_mb = gem[:gem_size].to_f.round(1) + "gem_size_mb{name=\"#{gem_name}\"} #{gem_size_mb}" + end +end + +puts total_size(full_report) +puts gems_as_metrics(all_gems(full_report)).sort(&:casecmp) diff --git a/scripts/memory-static b/scripts/memory-static deleted file mode 100755 index 54f147a7a91..00000000000 --- a/scripts/memory-static +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env ruby - -require_relative '../lib/gitlab/popen' - -full_report_filename, metrics_filename = ARGV -abort 'usage: memory-static <full_report_filename> <metrics_filename>' unless full_report_filename && metrics_filename - -full_report, status = Gitlab::Popen.popen(%w(bundle exec derailed bundle:mem)) -abort 'failed to execute the benchmark' unless status.zero? - -File.open(full_report_filename, 'w') do |f| - f.write(full_report) -end - -stats = /TOP: (?<total_mibs_str>.*) MiB/.match(full_report.lines.first) -abort 'failed to process the benchmark output' unless stats - -File.open(metrics_filename, 'a') do |f| - f.puts "memory_static_total_mb #{stats[:total_mibs_str].to_f.round(1)}" -end diff --git a/scripts/memory-static-objects b/scripts/memory-static-objects deleted file mode 100755 index 2ad38d9717c..00000000000 --- a/scripts/memory-static-objects +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env ruby - -require_relative '../lib/gitlab/popen' - -full_report_filename, metrics_filename = ARGV -abort 'usage: memory-static-objects <full_report_filename> <metrics_filename>' unless full_report_filename && metrics_filename - -full_report, status = Gitlab::Popen.popen(%w(bundle exec derailed bundle:objects)) -abort 'failed to execute the benchmark' unless status.zero? - -File.open(full_report_filename, 'w') do |f| - f.write(full_report) -end - -allocated_str = full_report.lines[1] -retained_str = full_report.lines[2] -allocated_stats = /Total allocated: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(allocated_str) -retained_stats = /Total retained: (?<bytes>.*) bytes \((?<objects>.*) objects\)/.match(retained_str) - -abort 'failed to process the benchmark output' unless allocated_stats && retained_stats - -File.open(metrics_filename, 'a') do |f| - f.puts "memory_static_objects_allocated_mb #{(allocated_stats[:bytes].to_f / (1024 * 1024)).round(1)}" - f.puts "memory_static_objects_retained_mb #{(retained_stats[:bytes].to_f / (104 * 1024)).round(1)}" - f.puts "memory_static_objects_allocated_items #{allocated_stats[:objects]}" - f.puts "memory_static_objects_retained_items #{retained_stats[:objects]}" -end diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index 3bae2e08a6f..633ea28e96c 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -1,7 +1,7 @@ [[ "$TRACE" ]] && set -x export TILLER_NAMESPACE="$KUBE_NAMESPACE" -function deployExists() { +function deploy_exists() { local namespace="${1}" local deploy="${2}" echoinfo "Checking if ${deploy} exists in the ${namespace} namespace..." true @@ -13,8 +13,7 @@ function deployExists() { return $deploy_exists } -function previousDeployFailed() { - set +e +function previous_deploy_failed() { local deploy="${1}" echoinfo "Checking for previous deployment of ${deploy}" true @@ -34,7 +33,6 @@ function previousDeployFailed() { else echoerr "Previous deployment NOT found." fi - set -e return $status } @@ -51,49 +49,35 @@ function delete() { helm delete --purge "$name" } -function cleanup() { - if [ -z "$CI_ENVIRONMENT_SLUG" ]; then - echoerr "No release given, aborting the delete!" - return - fi - - echoinfo "Cleaning up '$CI_ENVIRONMENT_SLUG'..." true - - kubectl -n "$KUBE_NAMESPACE" delete \ - ingress,svc,pdb,hpa,deploy,statefulset,job,pod,secret,configmap,pvc,secret,clusterrole,clusterrolebinding,role,rolebinding,sa \ - --now --ignore-not-found --include-uninitialized \ - -l release="$CI_ENVIRONMENT_SLUG" -} - function get_pod() { local app_name="${1}" local status="${2-Running}" get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name" - echoinfo "Running '${get_pod_cmd}'" true + echoinfo "Waiting till '${app_name}' pod is ready" true + echoinfo "Running '${get_pod_cmd}'" + local interval=5 + local elapsed_seconds=0 + local max_seconds=$((2 * 60)) while true; do local pod_name pod_name="$(eval "${get_pod_cmd}")" [[ "${pod_name}" == "" ]] || break - echoinfo "Waiting till '${app_name}' pod is ready"; - sleep 5; + if [[ "${elapsed_seconds}" -gt "${max_seconds}" ]]; then + echoerr "The pod name couldn't be found after ${elapsed_seconds} seconds, aborting." + break + fi + + printf "." + let "elapsed_seconds+=interval" + sleep ${interval} done echoinfo "The pod name is '${pod_name}'." echo "${pod_name}" } -function perform_review_app_deployment() { - check_kube_domain - ensure_namespace - install_tiller - install_external_dns - time deploy - wait_for_review_app_to_be_accessible - add_license -} - function check_kube_domain() { echoinfo "Checking that Kube domain exists..." true @@ -119,9 +103,16 @@ function install_tiller() { echoinfo "Initiating the Helm client..." helm init --client-only + # Set toleration for Tiller to be installed on a specific node pool helm init \ + --wait \ --upgrade \ - --replicas 2 + --node-selectors "app=helm" \ + --replicas 3 \ + --override "spec.template.spec.tolerations[0].key"="dedicated" \ + --override "spec.template.spec.tolerations[0].operator"="Equal" \ + --override "spec.template.spec.tolerations[0].value"="helm" \ + --override "spec.template.spec.tolerations[0].effect"="NoSchedule" kubectl rollout status -n "$TILLER_NAMESPACE" -w "deployment/tiller-deploy" @@ -137,7 +128,7 @@ function install_external_dns() { domain=$(echo "${REVIEW_APPS_DOMAIN}" | awk -F. '{printf "%s.%s", $(NF-1), $NF}') echoinfo "Installing external DNS for domain ${domain}..." true - if ! deployExists "${KUBE_NAMESPACE}" "${release_name}" || previousDeployFailed "${release_name}" ; then + if ! deploy_exists "${KUBE_NAMESPACE}" "${release_name}" || previous_deploy_failed "${release_name}" ; then echoinfo "Installing external-dns Helm chart" helm repo update helm install stable/external-dns \ @@ -156,7 +147,7 @@ function install_external_dns() { fi } -function create_secret() { +function create_application_secret() { echoinfo "Creating the ${CI_ENVIRONMENT_SLUG}-gitlab-initial-root-password secret in the ${KUBE_NAMESPACE} namespace..." true kubectl create secret generic -n "$KUBE_NAMESPACE" \ @@ -165,7 +156,7 @@ function create_secret() { --dry-run -o json | kubectl apply -f - } -function download_gitlab_chart() { +function download_chart() { echoinfo "Downloading the GitLab chart..." true curl -o gitlab.tar.bz2 "https://gitlab.com/charts/gitlab/-/archive/${GITLAB_HELM_CHART_REF}/gitlab-${GITLAB_HELM_CHART_REF}.tar.bz2" @@ -194,14 +185,12 @@ function deploy() { gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${IMAGE_VERSION}" # Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade` - if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previousDeployFailed "$CI_ENVIRONMENT_SLUG" ; then + if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previous_deploy_failed "$CI_ENVIRONMENT_SLUG" ; then echo "Deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG" delete - cleanup fi - create_secret - download_gitlab_chart + create_application_secret HELM_CMD=$(cat << EOF helm upgrade --install \ @@ -216,7 +205,7 @@ HELM_CMD=$(cat << EOF --set prometheus.install=false \ --set global.ingress.configureCertmanager=false \ --set global.ingress.tls.secretName=tls-cert \ - --set global.ingress.annotations."external-dns\.alpha\.kubernetes\.io/ttl"="10" + --set global.ingress.annotations."external-dns\.alpha\.kubernetes\.io/ttl"="10" \ --set nginx-ingress.controller.service.enableHttp=false \ --set nginx-ingress.defaultBackend.resources.requests.memory=7Mi \ --set nginx-ingress.controller.resources.requests.memory=440M \ @@ -252,14 +241,35 @@ EOF echoinfo "Deploying with:" echoinfo "${HELM_CMD}" - eval $HELM_CMD || true + eval "${HELM_CMD}" +} + +function display_deployment_debug() { + migrations_pod=$(get_pod "migrations"); + if [ -z "${migrations_pod}" ]; then + echoerr "Migrations pod not found." + else + echoinfo "Logs tail of the ${migrations_pod} pod..." + + kubectl logs -n "$KUBE_NAMESPACE" "${migrations_pod}" | sed "s/${REVIEW_APPS_ROOT_PASSWORD}/[REDACTED]/g" + fi + + unicorn_pod=$(get_pod "unicorn"); + if [ -z "${unicorn_pod}" ]; then + echoerr "Unicorn pod not found." + else + echoinfo "Logs tail of the ${unicorn_pod} pod..." + + kubectl logs -n "$KUBE_NAMESPACE" -c unicorn "${unicorn_pod}" | sed "s/${REVIEW_APPS_ROOT_PASSWORD}/[REDACTED]/g" + fi } function wait_for_review_app_to_be_accessible() { - # In case the Review App isn't completely available yet. Keep trying for 5 minutes. + echoinfo "Waiting for the Review App at ${CI_ENVIRONMENT_URL} to be accessible..." true + local interval=5 local elapsed_seconds=0 - local max_seconds=$((5 * 60)) + local max_seconds=$((2 * 60)) while true; do local review_app_http_code review_app_http_code=$(curl --silent --output /dev/null --max-time 5 --write-out "%{http_code}" "${CI_ENVIRONMENT_URL}/users/sign_in") @@ -272,10 +282,10 @@ function wait_for_review_app_to_be_accessible() { sleep ${interval} done - if [[ "${review_app_http_code}" == "200" ]]; then - echoinfo "The Review App at ${CI_ENVIRONMENT_URL} is ready!" + if [[ "${review_app_http_code}" -eq "200" ]]; then + echoinfo "The Review App at ${CI_ENVIRONMENT_URL} is ready after ${elapsed_seconds} seconds!" else - echoerr "The Review App at ${CI_ENVIRONMENT_URL} isn't ready after 5 minutes of polling..." + echoerr "The Review App at ${CI_ENVIRONMENT_URL} isn't ready after ${max_seconds} seconds of polling..." exit 1 fi } diff --git a/scripts/trigger-build b/scripts/trigger-build index 52bc61cac56..4d8110fce10 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -122,7 +122,14 @@ module Trigger end def ref - ENV['CNG_BRANCH'] || 'master' + default_ref = + if ENV['CI_COMMIT_REF_NAME'] =~ /^[\d-]+-stable(-ee)?$/ + ENV['CI_COMMIT_REF_NAME'] + else + 'master' + end + + ENV['CNG_BRANCH'] || default_ref end def trigger_token diff --git a/spec/controllers/concerns/continue_params_spec.rb b/spec/controllers/concerns/continue_params_spec.rb index 5e47f5e9f28..b4b62cbe1e3 100644 --- a/spec/controllers/concerns/continue_params_spec.rb +++ b/spec/controllers/concerns/continue_params_spec.rb @@ -18,6 +18,14 @@ describe ContinueParams do ActionController::Parameters.new(continue: params) end + it 'returns an empty hash if params are not present' do + allow(controller).to receive(:params) do + ActionController::Parameters.new + end + + expect(controller.continue_params).to eq({}) + end + it 'cleans up any params that are not allowed' do allow(controller).to receive(:params) do strong_continue_params(to: '/hello', diff --git a/spec/controllers/concerns/internal_redirect_spec.rb b/spec/controllers/concerns/internal_redirect_spec.rb index 97119438ca1..da68c8c8697 100644 --- a/spec/controllers/concerns/internal_redirect_spec.rb +++ b/spec/controllers/concerns/internal_redirect_spec.rb @@ -15,44 +15,71 @@ describe InternalRedirect do subject(:controller) { controller_class.new } describe '#safe_redirect_path' do - it 'is `nil` for invalid uris' do - expect(controller.safe_redirect_path('Hello world')).to be_nil + where(:input) do + [ + 'Hello world', + '//example.com/hello/world', + 'https://example.com/hello/world' + ] end - it 'is `nil` for paths trying to include a host' do - expect(controller.safe_redirect_path('//example.com/hello/world')).to be_nil + with_them 'being invalid' do + it 'returns nil' do + expect(controller.safe_redirect_path(input)).to be_nil + end end - it 'returns the path if it is valid' do - expect(controller.safe_redirect_path('/hello/world')).to eq('/hello/world') + where(:input) do + [ + '/hello/world', + '/-/ide/project/path' + ] end - it 'returns the path with querystring if it is valid' do - expect(controller.safe_redirect_path('/hello/world?hello=world#L123')) - .to eq('/hello/world?hello=world#L123') + with_them 'being valid' do + it 'returns the path' do + expect(controller.safe_redirect_path(input)).to eq(input) + end + + it 'returns the path with querystring and fragment' do + expect(controller.safe_redirect_path("#{input}?hello=world#L123")) + .to eq("#{input}?hello=world#L123") + end end end describe '#safe_redirect_path_for_url' do - it 'is `nil` for invalid urls' do - expect(controller.safe_redirect_path_for_url('Hello world')).to be_nil + where(:input) do + [ + 'Hello world', + 'http://example.com/hello/world', + 'http://test.host:3000/hello/world' + ] end - it 'is `nil` for urls from a with a different host' do - expect(controller.safe_redirect_path_for_url('http://example.com/hello/world')).to be_nil + with_them 'being invalid' do + it 'returns nil' do + expect(controller.safe_redirect_path_for_url(input)).to be_nil + end end - it 'is `nil` for urls from a with a different port' do - expect(controller.safe_redirect_path_for_url('http://test.host:3000/hello/world')).to be_nil + where(:input) do + [ + 'http://test.host/hello/world' + ] end - it 'returns the path if the url is on the same host' do - expect(controller.safe_redirect_path_for_url('http://test.host/hello/world')).to eq('/hello/world') - end + with_them 'being on the same host' do + let(:path) { URI(input).path } - it 'returns the path including querystring if the url is on the same host' do - expect(controller.safe_redirect_path_for_url('http://test.host/hello/world?hello=world#L123')) - .to eq('/hello/world?hello=world#L123') + it 'returns the path' do + expect(controller.safe_redirect_path_for_url(input)).to eq(path) + end + + it 'returns the path with querystring and fragment' do + expect(controller.safe_redirect_path_for_url("#{input}?hello=world#L123")) + .to eq("#{path}?hello=world#L123") + end end end @@ -82,12 +109,16 @@ describe InternalRedirect do end describe '#host_allowed?' do - it 'allows uris with the same host and port' do + it 'allows URI with the same host and port' do expect(controller.host_allowed?(URI('http://test.host/test'))).to be(true) end - it 'rejects uris with other host and port' do + it 'rejects URI with other host' do expect(controller.host_allowed?(URI('http://example.com/test'))).to be(false) end + + it 'rejects URI with other port' do + expect(controller.host_allowed?(URI('http://test.host:3000/test'))).to be(false) + end end end diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb index 6243ddc03c0..9a3fbfaac51 100644 --- a/spec/controllers/dashboard/todos_controller_spec.rb +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -44,6 +44,34 @@ describe Dashboard::TodosController do end end + context "with render_views" do + render_views + + it 'avoids N+1 queries', :request_store do + merge_request = create(:merge_request, source_project: project) + create(:todo, project: project, author: author, user: user, target: merge_request) + create(:issue, project: project, assignees: [user]) + + group = create(:group) + group.add_owner(user) + + get :index + + control = ActiveRecord::QueryRecorder.new { get :index } + + create(:issue, project: project, assignees: [user]) + group_2 = create(:group) + group_2.add_owner(user) + project_2 = create(:project) + project_2.add_developer(user) + merge_request_2 = create(:merge_request, source_project: project_2) + create(:todo, project: project, author: author, user: user, target: merge_request_2) + + expect { get :index }.not_to exceed_query_limit(control) + expect(response.status).to eq(200) + end + end + context 'group authorization' do it 'renders 404 when user does not have read access on given group' do unauthorized_group = create(:group, :private) diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index fa49438287f..35cbab57037 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -340,7 +340,6 @@ describe Projects::ClustersController do describe 'security' do before do - allow(ClusterConfigureWorker).to receive(:perform_async) stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') end @@ -438,7 +437,6 @@ describe Projects::ClustersController do end before do - allow(ClusterConfigureWorker).to receive(:perform_async) stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') end diff --git a/spec/controllers/projects/forks_controller_spec.rb b/spec/controllers/projects/forks_controller_spec.rb index 3423fdf4c41..5ac5279e997 100644 --- a/spec/controllers/projects/forks_controller_spec.rb +++ b/spec/controllers/projects/forks_controller_spec.rb @@ -115,24 +115,34 @@ describe Projects::ForksController do end describe 'POST create' do - def post_create + def post_create(params = {}) post :create, params: { namespace_id: project.namespace, project_id: project, namespace_key: user.namespace.id - } + }.merge(params) end context 'when user is signed in' do - it 'responds with status 302' do + before do sign_in(user) + end + it 'responds with status 302' do post_create expect(response).to have_gitlab_http_status(302) expect(response).to redirect_to(namespace_project_import_path(user.namespace, project)) end + + it 'passes continue params to the redirect' do + continue_params = { to: '/-/ide/project/path', notice: 'message' } + post_create continue: continue_params + + expect(response).to have_gitlab_http_status(302) + expect(response).to redirect_to(namespace_project_import_path(user.namespace, project, continue: continue_params)) + end end context 'when user is not signed in' do diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 34cbf0c8723..0eca663a683 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -1001,6 +1001,8 @@ describe Projects::MergeRequestsController do before do project.add_developer(user) sign_in(user) + + expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original end it 'returns 200' do diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 9a598790ff2..faf3c990cb2 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -6,7 +6,8 @@ describe RegistrationsController do include TermsHelper describe '#create' do - let(:user_params) { { user: { name: 'new_user', username: 'new_username', email: 'new@user.com', password: 'Any_password' } } } + let(:base_user_params) { { name: 'new_user', username: 'new_username', email: 'new@user.com', password: 'Any_password' } } + let(:user_params) { { user: base_user_params } } context 'email confirmation' do around do |example| @@ -105,6 +106,20 @@ describe RegistrationsController do expect(subject.current_user.terms_accepted?).to be(true) end end + + it "logs a 'User Created' message" do + stub_feature_flags(registrations_recaptcha: false) + + expect(Gitlab::AppLogger).to receive(:info).with(/\AUser Created: username=new_username email=new@user.com.+\z/).and_call_original + + post(:create, params: user_params) + end + + it 'handles when params are new_user' do + post(:create, params: { new_user: base_user_params }) + + expect(subject.current_user).not_to be_nil + end end describe '#destroy' do diff --git a/spec/factories/namespace/aggregation_schedules.rb b/spec/factories/namespace/aggregation_schedules.rb new file mode 100644 index 00000000000..c172c3360e2 --- /dev/null +++ b/spec/factories/namespace/aggregation_schedules.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :namespace_aggregation_schedules, class: Namespace::AggregationSchedule do + namespace + end +end diff --git a/spec/factories/namespace/root_storage_statistics.rb b/spec/factories/namespace/root_storage_statistics.rb new file mode 100644 index 00000000000..54c5921eb44 --- /dev/null +++ b/spec/factories/namespace/root_storage_statistics.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :namespace_root_storage_statistics, class: Namespace::RootStorageStatistics do + namespace + end +end diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb index 6feafa5ece9..0cfc6e3aa46 100644 --- a/spec/factories/namespaces.rb +++ b/spec/factories/namespaces.rb @@ -19,5 +19,13 @@ FactoryBot.define do owner.namespace = namespace end end + + trait :with_aggregation_schedule do + association :aggregation_schedule, factory: :namespace_aggregation_schedules + end + + trait :with_root_storage_statistics do + association :root_storage_statistics, factory: :namespace_root_storage_statistics + end end end diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index 8da19a37a6a..3e0baab04ce 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -182,6 +182,7 @@ ZDXgrA== end trait :letsencrypt do + auto_ssl_enabled { true } certificate_source { :gitlab_provided } end end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 29545779a34..dafec29dfcc 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -419,6 +419,32 @@ describe "Admin::Users" do end end end + + describe 'Email verification status' do + let!(:secondary_email) do + create :email, email: 'secondary@example.com', user: user + end + + it 'displays the correct status for an unverified email address' do + user.update(confirmed_at: nil, unconfirmed_email: user.email) + visit admin_user_path(user) + + expect(page).to have_content("#{user.email} Unverified") + + expect(page).to have_content("#{secondary_email.email} Unverified") + end + + it 'displays the correct status for a verified email address' do + visit admin_user_path(user) + expect(page).to have_content("#{user.email} Verified") + + secondary_email.confirm + expect(secondary_email.confirmed?).to be_truthy + + visit admin_user_path(user) + expect(page).to have_content("#{secondary_email.email} Verified") + end + end end describe "GET /admin/users/:id/edit" do diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index b1798c11361..6c9ae343e01 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -16,7 +16,9 @@ describe 'Issue Boards', :js do let!(:issue2) { create(:labeled_issue, project: project, labels: [development, stretch], relative_position: 1) } let(:board) { create(:board, project: project) } let!(:list) { create(:list, board: board, label: development, position: 0) } - let(:card) { find('.board:nth-child(2)').first('.board-card') } + let(:card) { find('.board:nth-child(2)').first('.board-card') } + + let(:application_settings) { {} } around do |example| Timecop.freeze { example.run } @@ -27,6 +29,8 @@ describe 'Issue Boards', :js do sign_in(user) + stub_application_setting(application_settings) + visit project_board_path(project, board) wait_for_requests end @@ -223,16 +227,24 @@ describe 'Issue Boards', :js do end context 'time tracking' do + let(:compare_meter_tooltip) { find('.time-tracking .time-tracking-content .compare-meter')['data-original-title'] } + before do issue2.timelogs.create(time_spent: 14400, user: user) - issue2.update!(time_estimate: 28800) + issue2.update!(time_estimate: 128800) + + click_card(card) end it 'shows time tracking progress bar' do - click_card(card) + expect(compare_meter_tooltip).to eq('Time remaining: 3d 7h 46m') + end + + context 'when time_tracking_limit_to_hours is true' do + let(:application_settings) { { time_tracking_limit_to_hours: true } } - page.within('.time-tracking') do - expect(find('.time-tracking-content .compare-meter')['data-original-title']).to eq('Time remaining: 4h') + it 'shows time tracking progress bar' do + expect(compare_meter_tooltip).to eq('Time remaining: 31h 46m') end end end diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index 176f4a668ff..c000165ccd9 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe 'Group issues page' do include FilteredSearchHelpers + include DragTo let(:group) { create(:group) } let(:project) { create(:project, :public, group: group)} @@ -99,4 +100,62 @@ describe 'Group issues page' do end end end + + context 'manual ordering' do + let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user } + + let!(:issue1) { create(:issue, project: project, title: 'Issue #1', relative_position: 1) } + let!(:issue2) { create(:issue, project: project, title: 'Issue #2', relative_position: 2) } + let!(:issue3) { create(:issue, project: project, title: 'Issue #3', relative_position: 3) } + + before do + sign_in(user_in_group) + end + + it 'displays all issues' do + visit issues_group_path(group, sort: 'relative_position') + + page.within('.issues-list') do + expect(page).to have_selector('li.issue', count: 3) + end + end + + it 'has manual-ordering css applied' do + visit issues_group_path(group, sort: 'relative_position') + + expect(page).to have_selector('.manual-ordering') + end + + it 'each issue item has a user-can-drag css applied' do + visit issues_group_path(group, sort: 'relative_position') + + page.within('.manual-ordering') do + expect(page).to have_selector('.issue.user-can-drag', count: 3) + end + end + + it 'issues should be draggable and persist order', :js do + visit issues_group_path(group, sort: 'relative_position') + + drag_to(selector: '.manual-ordering', + from_index: 0, + to_index: 2) + + wait_for_requests + + check_issue_order + + visit issues_group_path(group, sort: 'relative_position') + + check_issue_order + end + + def check_issue_order + page.within('.manual-ordering') do + expect(find('.issue:nth-child(1) .title')).to have_content('Issue #2') + expect(find('.issue:nth-child(2) .title')).to have_content('Issue #3') + expect(find('.issue:nth-child(3) .title')).to have_content('Issue #1') + end + end + end end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 8eb413bdd8d..40845ec48f9 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -3,14 +3,14 @@ require 'rails_helper' describe 'GFM autocomplete', :js do let(:issue_xss_title) { 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' } let(:user_xss_title) { 'eve <img src=x onerror=alert(2)<img src=x onerror=alert(1)>' } - let(:label_xss_title) { 'alert label <img src=x onerror="alert(\'Hello xss\');" a'} + let(:label_xss_title) { 'alert label <img src=x onerror="alert(\'Hello xss\');" a' } let(:milestone_xss_title) { 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' } let(:user_xss) { create(:user, name: user_xss_title, username: 'xss.user') } - let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } + let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } let(:project) { create(:project) } let(:label) { create(:label, project: project, title: 'special+') } - let(:issue) { create(:issue, project: project) } + let(:issue) { create(:issue, project: project) } before do project.add_maintainer(user) @@ -293,6 +293,70 @@ describe 'GFM autocomplete', :js do expect(find('.atwho-view-ul').text).to have_content('alert label') end end + + it 'allows colons when autocompleting scoped labels' do + create(:label, project: project, title: 'scoped:label') + + note = find('#note-body') + type(note, '~scoped:') + + wait_for_requests + + page.within '.atwho-container #at-view-labels' do + expect(find('.atwho-view-ul').text).to have_content('scoped:label') + end + end + + it 'allows colons when autocompleting scoped labels with double colons' do + create(:label, project: project, title: 'scoped::label') + + note = find('#note-body') + type(note, '~scoped::') + + wait_for_requests + + page.within '.atwho-container #at-view-labels' do + expect(find('.atwho-view-ul').text).to have_content('scoped::label') + end + end + + it 'allows spaces when autocompleting multi-word labels' do + create(:label, project: project, title: 'Accepting merge requests') + + note = find('#note-body') + type(note, '~Accepting merge') + + wait_for_requests + + page.within '.atwho-container #at-view-labels' do + expect(find('.atwho-view-ul').text).to have_content('Accepting merge requests') + end + end + + it 'only autocompletes the latest label' do + create(:label, project: project, title: 'Accepting merge requests') + create(:label, project: project, title: 'Accepting job applicants') + + note = find('#note-body') + type(note, '~Accepting merge requests foo bar ~Accepting job') + + wait_for_requests + + page.within '.atwho-container #at-view-labels' do + expect(find('.atwho-view-ul').text).to have_content('Accepting job applicants') + end + end + + it 'does not autocomplete labels if no tilde is typed' do + create(:label, project: project, title: 'Accepting merge requests') + + note = find('#note-body') + type(note, 'Accepting merge') + + wait_for_requests + + expect(page).not_to have_css('.atwho-container #at-view-labels') + end end shared_examples 'autocomplete suggestions' do diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb index b69fba0db00..f9103d83ba0 100644 --- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb +++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'User creates branch and merge request on issue page', :js do let(:membership_level) { :developer } let(:user) { create(:user) } - let!(:project) { create(:project, :repository) } + let!(:project) { create(:project, :repository, :public) } let(:issue) { create(:issue, project: project, title: 'Cherry-Coloured Funk') } context 'when signed out' do @@ -163,10 +163,21 @@ describe 'User creates branch and merge request on issue page', :js do let(:issue) { create(:issue, :confidential, project: project) } it 'disables the create branch button' do + stub_feature_flags(create_confidential_merge_request: false) + visit project_issue_path(project, issue) expect(page).not_to have_css('.create-mr-dropdown-wrap') end + + it 'enables the create branch button when feature flag is enabled' do + stub_feature_flags(create_confidential_merge_request: true) + + visit project_issue_path(project, issue) + + expect(page).to have_css('.create-mr-dropdown-wrap') + expect(page).to have_button('Create confidential merge request') + end end context 'when related branch exists' do diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index e4a3a1a8c92..974e0f84681 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -118,7 +118,6 @@ describe 'Gcp Cluster', :js do context 'when user changes cluster parameters' do before do - allow(ClusterConfigureWorker).to receive(:perform_async) fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' page.within('#js-cluster-details') { click_button 'Save changes' } end diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb index edbab14f7c1..b08ccdc2a7c 100644 --- a/spec/features/projects/environments/environment_metrics_spec.rb +++ b/spec/features/projects/environments/environment_metrics_spec.rb @@ -9,11 +9,11 @@ describe 'Environment > Metrics' do let(:build) { create(:ci_build, pipeline: pipeline) } let(:environment) { create(:environment, project: project) } let(:current_time) { Time.now.utc } + let!(:staging) { create(:environment, name: 'staging', project: project) } before do project.add_developer(user) - create(:deployment, environment: environment, deployable: build) - stub_all_prometheus_requests(environment.slug) + stub_any_prometheus_request sign_in(user) visit_environment(environment) @@ -23,15 +23,50 @@ describe 'Environment > Metrics' do Timecop.freeze(current_time) { example.run } end + shared_examples 'has environment selector' do + it 'has a working environment selector', :js do + click_link('See metrics') + + expect(page).to have_metrics_path(environment) + expect(page).to have_css('div.js-environments-dropdown') + + within('div.js-environments-dropdown') do + # Click on the dropdown + click_on(environment.name) + + # Select the staging environment + click_on(staging.name) + end + + expect(page).to have_metrics_path(staging) + + wait_for_requests + end + end + + context 'without deployments' do + it_behaves_like 'has environment selector' + end + context 'with deployments and related deployable present' do + before do + create(:deployment, environment: environment, deployable: build) + end + it 'shows metrics' do click_link('See metrics') expect(page).to have_css('div#prometheus-graphs') end + + it_behaves_like 'has environment selector' end def visit_environment(environment) visit project_environment_path(environment.project, environment) end + + def have_metrics_path(environment) + have_current_path(metrics_project_environment_path(project, id: environment.id)) + end end diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index 683268d064a..e0fa9dbb5fa 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -118,19 +118,31 @@ describe 'Projects > Files > User edits files', :js do wait_for_requests end - it 'inserts a content of a file in a forked project' do - click_link('.gitignore') - find('.js-edit-blob').click - + def expect_fork_prompt expect(page).to have_link('Fork') expect(page).to have_button('Cancel') + expect(page).to have_content( + "You're not allowed to edit files in this project directly. "\ + "Please fork this project, make your changes there, and submit a merge request." + ) + end - click_link('Fork') - + def expect_fork_status expect(page).to have_content( "You're not allowed to make changes to this project directly. "\ "A fork of this project has been created that you can make changes in, so you can submit a merge request." ) + end + + it 'inserts a content of a file in a forked project' do + click_link('.gitignore') + click_button('Edit') + + expect_fork_prompt + + click_link('Fork') + + expect_fork_status find('.file-editor', match: :first) @@ -140,12 +152,24 @@ describe 'Projects > Files > User edits files', :js do expect(evaluate_script('ace.edit("editor").getValue()')).to eq('*.rbca') end + it 'opens the Web IDE in a forked project' do + click_link('.gitignore') + click_button('Web IDE') + + expect_fork_prompt + + click_link('Fork') + + expect_fork_status + + expect(page).to have_css('.ide .multi-file-tab', text: '.gitignore') + end + it 'commits an edited file in a forked project' do click_link('.gitignore') find('.js-edit-blob').click - expect(page).to have_link('Fork') - expect(page).to have_button('Cancel') + expect_fork_prompt click_link('Fork') diff --git a/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb b/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb index 28d83a8b961..c50fd93e4cb 100644 --- a/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb +++ b/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb @@ -15,6 +15,8 @@ describe 'User activates JetBrains TeamCity CI' do it 'activates service' do check('Active') + check('Push') + check('Merge request') fill_in('Teamcity url', with: 'http://teamcity.example.com') fill_in('Build type', with: 'GitlabTest_Build') fill_in('Username', with: 'user') diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb index 9a049764dec..a4dd79b3179 100644 --- a/spec/features/raven_js_spec.rb +++ b/spec/features/raven_js_spec.rb @@ -10,7 +10,7 @@ describe 'RavenJS' do end it 'loads raven if sentry is enabled' do - stub_application_setting(clientside_sentry_dsn: 'https://key@domain.com/id', clientside_sentry_enabled: true) + stub_sentry_settings visit new_user_session_path diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb index 6d4facd0649..ee43755262e 100644 --- a/spec/features/search/user_searches_for_wiki_pages_spec.rb +++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'User searches for wiki pages', :js do let(:user) { create(:user) } let(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) } - let!(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'test_wiki', content: 'Some Wiki content' }) } + let!(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'directory/title', content: 'Some Wiki content' }) } before do project.add_maintainer(user) @@ -22,7 +22,7 @@ describe 'User searches for wiki pages', :js do click_link(project.full_name) end - fill_in('dashboard_search', with: 'content') + fill_in('dashboard_search', with: search_term) find('.btn-search').click page.within('.search-filter') do @@ -43,7 +43,7 @@ describe 'User searches for wiki pages', :js do context 'when searching by title' do it_behaves_like 'search wiki blobs' do - let(:search_term) { 'test_wiki' } + let(:search_term) { 'title' } end end end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 8a6901ea4e9..50befa7028d 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -90,7 +90,7 @@ describe 'Signup' do expect(page).to have_content("Invalid input, please avoid emojis") end - it 'shows a pending message if the username availability is being fetched' do + it 'shows a pending message if the username availability is being fetched', :quarantine do fill_in 'new_user_username', with: 'new-user' expect(find('.username > .validation-pending')).not_to have_css '.hide' diff --git a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb index 79d2f9cdb45..c4e6c9cc9f5 100644 --- a/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb +++ b/spec/finders/autocomplete/acts_as_taggable_on/tags_finder_spec.rb @@ -17,13 +17,13 @@ describe Autocomplete::ActsAsTaggableOn::TagsFinder do context 'filter by search' do context 'with an empty search term' do - it 'returns an empty collection' do - ActsAsTaggableOn::Tag.create!(name: 'tag1') - ActsAsTaggableOn::Tag.create!(name: 'tag2') + it 'returns all tags' do + tag1 = ActsAsTaggableOn::Tag.create!(name: 'tag1') + tag2 = ActsAsTaggableOn::Tag.create!(name: 'tag2') tags = described_class.new(params: { search: '' }).execute - expect(tags).to be_empty + expect(tags).to match_array [tag1, tag2] end end diff --git a/spec/frontend/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js index 4dd27e94d97..5b5ae4b6556 100644 --- a/spec/frontend/boards/modal_store_spec.js +++ b/spec/frontend/boards/modal_store_spec.js @@ -25,7 +25,7 @@ describe('Modal store', () => { }); issue2 = new ListIssue({ title: 'Testing', - id: 1, + id: 2, iid: 2, confidential: false, labels: [], diff --git a/spec/frontend/boards/services/board_service_spec.js b/spec/frontend/boards/services/board_service_spec.js new file mode 100644 index 00000000000..de9fc998360 --- /dev/null +++ b/spec/frontend/boards/services/board_service_spec.js @@ -0,0 +1,390 @@ +import BoardService from '~/boards/services/board_service'; +import { TEST_HOST } from 'helpers/test_constants'; +import AxiosMockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; + +describe('BoardService', () => { + const dummyResponse = "without type checking this doesn't matter"; + const boardId = 'dummy-board-id'; + const endpoints = { + boardsEndpoint: `${TEST_HOST}/boards`, + listsEndpoint: `${TEST_HOST}/lists`, + bulkUpdatePath: `${TEST_HOST}/bulk/update`, + recentBoardsEndpoint: `${TEST_HOST}/recent/boards`, + }; + + let service; + let axiosMock; + + beforeEach(() => { + axiosMock = new AxiosMockAdapter(axios); + service = new BoardService({ + ...endpoints, + boardId, + }); + }); + + describe('all', () => { + it('makes a request to fetch lists', () => { + axiosMock.onGet(endpoints.listsEndpoint).replyOnce(200, dummyResponse); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.all()).resolves.toEqual(expectedResponse); + }); + + it('fails for error response', () => { + axiosMock.onGet(endpoints.listsEndpoint).replyOnce(500); + + return expect(service.all()).rejects.toThrow(); + }); + }); + + describe('generateDefaultLists', () => { + const listsEndpointGenerate = `${endpoints.listsEndpoint}/generate.json`; + + it('makes a request to generate default lists', () => { + axiosMock.onPost(listsEndpointGenerate).replyOnce(200, dummyResponse); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.generateDefaultLists()).resolves.toEqual(expectedResponse); + }); + + it('fails for error response', () => { + axiosMock.onPost(listsEndpointGenerate).replyOnce(500); + + return expect(service.generateDefaultLists()).rejects.toThrow(); + }); + }); + + describe('createList', () => { + const entityType = 'moorhen'; + const entityId = 'quack'; + const expectedRequest = expect.objectContaining({ + data: JSON.stringify({ list: { [entityType]: entityId } }), + }); + + let requestSpy; + + beforeEach(() => { + requestSpy = jest.fn(); + axiosMock.onPost(endpoints.listsEndpoint).replyOnce(config => requestSpy(config)); + }); + + it('makes a request to create a list', () => { + requestSpy.mockReturnValue([200, dummyResponse]); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.createList(entityId, entityType)) + .resolves.toEqual(expectedResponse) + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + + it('fails for error response', () => { + requestSpy.mockReturnValue([500]); + + return expect(service.createList(entityId, entityType)) + .rejects.toThrow() + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + }); + + describe('updateList', () => { + const id = 'David Webb'; + const position = 'unknown'; + const expectedRequest = expect.objectContaining({ + data: JSON.stringify({ list: { position } }), + }); + + let requestSpy; + + beforeEach(() => { + requestSpy = jest.fn(); + axiosMock.onPut(`${endpoints.listsEndpoint}/${id}`).replyOnce(config => requestSpy(config)); + }); + + it('makes a request to update a list position', () => { + requestSpy.mockReturnValue([200, dummyResponse]); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.updateList(id, position)) + .resolves.toEqual(expectedResponse) + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + + it('fails for error response', () => { + requestSpy.mockReturnValue([500]); + + return expect(service.updateList(id, position)) + .rejects.toThrow() + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + }); + + describe('destroyList', () => { + const id = '-42'; + + let requestSpy; + + beforeEach(() => { + requestSpy = jest.fn(); + axiosMock + .onDelete(`${endpoints.listsEndpoint}/${id}`) + .replyOnce(config => requestSpy(config)); + }); + + it('makes a request to delete a list', () => { + requestSpy.mockReturnValue([200, dummyResponse]); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.destroyList(id)) + .resolves.toEqual(expectedResponse) + .then(() => { + expect(requestSpy).toHaveBeenCalled(); + }); + }); + + it('fails for error response', () => { + requestSpy.mockReturnValue([500]); + + return expect(service.destroyList(id)) + .rejects.toThrow() + .then(() => { + expect(requestSpy).toHaveBeenCalled(); + }); + }); + }); + + describe('getIssuesForList', () => { + const id = 'TOO-MUCH'; + const url = `${endpoints.listsEndpoint}/${id}/issues?id=${id}`; + + it('makes a request to fetch list issues', () => { + axiosMock.onGet(url).replyOnce(200, dummyResponse); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.getIssuesForList(id)).resolves.toEqual(expectedResponse); + }); + + it('makes a request to fetch list issues with filter', () => { + const filter = { algal: 'scrubber' }; + axiosMock.onGet(`${url}&algal=scrubber`).replyOnce(200, dummyResponse); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.getIssuesForList(id, filter)).resolves.toEqual(expectedResponse); + }); + + it('fails for error response', () => { + axiosMock.onGet(url).replyOnce(500); + + return expect(service.getIssuesForList(id)).rejects.toThrow(); + }); + }); + + describe('moveIssue', () => { + const urlRoot = 'potato'; + const id = 'over 9000'; + const fromListId = 'left'; + const toListId = 'right'; + const moveBeforeId = 'up'; + const moveAfterId = 'down'; + const expectedRequest = expect.objectContaining({ + data: JSON.stringify({ + from_list_id: fromListId, + to_list_id: toListId, + move_before_id: moveBeforeId, + move_after_id: moveAfterId, + }), + }); + + let requestSpy; + + beforeAll(() => { + global.gon.relative_url_root = urlRoot; + }); + + afterAll(() => { + delete global.gon.relative_url_root; + }); + + beforeEach(() => { + requestSpy = jest.fn(); + axiosMock + .onPut(`${urlRoot}/-/boards/${boardId}/issues/${id}`) + .replyOnce(config => requestSpy(config)); + }); + + it('makes a request to move an issue between lists', () => { + requestSpy.mockReturnValue([200, dummyResponse]); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId)) + .resolves.toEqual(expectedResponse) + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + + it('fails for error response', () => { + requestSpy.mockReturnValue([500]); + + return expect(service.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId)) + .rejects.toThrow() + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + }); + + describe('newIssue', () => { + const id = 'not-creative'; + const issue = { some: 'issue data' }; + const url = `${endpoints.listsEndpoint}/${id}/issues`; + const expectedRequest = expect.objectContaining({ + data: JSON.stringify({ + issue, + }), + }); + + let requestSpy; + + beforeEach(() => { + requestSpy = jest.fn(); + axiosMock.onPost(url).replyOnce(config => requestSpy(config)); + }); + + it('makes a request to create a new issue', () => { + requestSpy.mockReturnValue([200, dummyResponse]); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.newIssue(id, issue)) + .resolves.toEqual(expectedResponse) + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + + it('fails for error response', () => { + requestSpy.mockReturnValue([500]); + + return expect(service.newIssue(id, issue)) + .rejects.toThrow() + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + }); + + describe('getBacklog', () => { + const urlRoot = 'deep'; + const url = `${urlRoot}/-/boards/${boardId}/issues.json?not=relevant`; + const requestParams = { + not: 'relevant', + }; + + beforeAll(() => { + global.gon.relative_url_root = urlRoot; + }); + + afterAll(() => { + delete global.gon.relative_url_root; + }); + + it('makes a request to fetch backlog', () => { + axiosMock.onGet(url).replyOnce(200, dummyResponse); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.getBacklog(requestParams)).resolves.toEqual(expectedResponse); + }); + + it('fails for error response', () => { + axiosMock.onGet(url).replyOnce(500); + + return expect(service.getBacklog(requestParams)).rejects.toThrow(); + }); + }); + + describe('bulkUpdate', () => { + const issueIds = [1, 2, 3]; + const extraData = { moar: 'data' }; + const expectedRequest = expect.objectContaining({ + data: JSON.stringify({ + update: { + ...extraData, + issuable_ids: '1,2,3', + }, + }), + }); + + let requestSpy; + + beforeEach(() => { + requestSpy = jest.fn(); + axiosMock.onPost(endpoints.bulkUpdatePath).replyOnce(config => requestSpy(config)); + }); + + it('makes a request to create a list', () => { + requestSpy.mockReturnValue([200, dummyResponse]); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(service.bulkUpdate(issueIds, extraData)) + .resolves.toEqual(expectedResponse) + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + + it('fails for error response', () => { + requestSpy.mockReturnValue([500]); + + return expect(service.bulkUpdate(issueIds, extraData)) + .rejects.toThrow() + .then(() => { + expect(requestSpy).toHaveBeenCalledWith(expectedRequest); + }); + }); + }); + + describe('getIssueInfo', () => { + const dummyEndpoint = `${TEST_HOST}/some/where`; + + it('makes a request to the given endpoint', () => { + axiosMock.onGet(dummyEndpoint).replyOnce(200, dummyResponse); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(BoardService.getIssueInfo(dummyEndpoint)).resolves.toEqual(expectedResponse); + }); + + it('fails for error response', () => { + axiosMock.onGet(dummyEndpoint).replyOnce(500); + + return expect(BoardService.getIssueInfo(dummyEndpoint)).rejects.toThrow(); + }); + }); + + describe('toggleIssueSubscription', () => { + const dummyEndpoint = `${TEST_HOST}/some/where`; + + it('makes a request to the given endpoint', () => { + axiosMock.onPost(dummyEndpoint).replyOnce(200, dummyResponse); + const expectedResponse = expect.objectContaining({ data: dummyResponse }); + + return expect(BoardService.toggleIssueSubscription(dummyEndpoint)).resolves.toEqual( + expectedResponse, + ); + }); + + it('fails for error response', () => { + axiosMock.onPost(dummyEndpoint).replyOnce(500); + + return expect(BoardService.toggleIssueSubscription(dummyEndpoint)).rejects.toThrow(); + }); + }); +}); diff --git a/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap b/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap new file mode 100644 index 00000000000..511c027dbc2 --- /dev/null +++ b/spec/frontend/branches/components/__snapshots__/divergence_graph_spec.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Branch divergence graph component renders ahead and behind count 1`] = ` +<div + class="divergence-graph px-2 d-none d-md-block" + title="10 commits behind master, 10 commits ahead" +> + <graphbar-stub + count="10" + maxcommits="100" + position="left" + /> + + <div + class="graph-separator pull-left mt-1" + /> + + <graphbar-stub + count="10" + maxcommits="100" + position="right" + /> +</div> +`; + +exports[`Branch divergence graph component renders distance count 1`] = ` +<div + class="divergence-graph px-2 d-none d-md-block" + title="More than 900 commits different with master" +> + <graphbar-stub + count="900" + maxcommits="100" + position="full" + /> +</div> +`; diff --git a/spec/frontend/branches/components/divergence_graph_spec.js b/spec/frontend/branches/components/divergence_graph_spec.js new file mode 100644 index 00000000000..b54b2ceb233 --- /dev/null +++ b/spec/frontend/branches/components/divergence_graph_spec.js @@ -0,0 +1,67 @@ +import { shallowMount } from '@vue/test-utils'; +import DivergenceGraph from '~/branches/components/divergence_graph.vue'; +import GraphBar from '~/branches/components/graph_bar.vue'; + +let vm; + +function factory(propsData = {}) { + vm = shallowMount(DivergenceGraph, { propsData }); +} + +describe('Branch divergence graph component', () => { + afterEach(() => { + vm.destroy(); + }); + + it('renders ahead and behind count', () => { + factory({ + defaultBranch: 'master', + aheadCount: 10, + behindCount: 10, + maxCommits: 100, + }); + + expect(vm.findAll(GraphBar).length).toBe(2); + expect(vm.element).toMatchSnapshot(); + }); + + it('sets title for ahead and behind count', () => { + factory({ + defaultBranch: 'master', + aheadCount: 10, + behindCount: 10, + maxCommits: 100, + }); + + expect(vm.attributes('title')).toBe('10 commits behind master, 10 commits ahead'); + }); + + it('renders distance count', () => { + factory({ + defaultBranch: 'master', + aheadCount: 0, + behindCount: 0, + distance: 900, + maxCommits: 100, + }); + + expect(vm.findAll(GraphBar).length).toBe(1); + expect(vm.element).toMatchSnapshot(); + }); + + it.each` + distance | titleText + ${900} | ${'900'} + ${1100} | ${'999+'} + `('sets title for $distance as $titleText', ({ distance, titleText }) => { + factory({ + defaultBranch: 'master', + aheadCount: 0, + behindCount: 0, + distance, + maxCommits: 100, + }); + + expect(vm.attributes('title')).toBe(`More than ${titleText} commits different with master`); + }); +}); diff --git a/spec/frontend/branches/components/graph_bar_spec.js b/spec/frontend/branches/components/graph_bar_spec.js new file mode 100644 index 00000000000..61c051b49c6 --- /dev/null +++ b/spec/frontend/branches/components/graph_bar_spec.js @@ -0,0 +1,89 @@ +import { shallowMount } from '@vue/test-utils'; +import GraphBar from '~/branches/components/graph_bar.vue'; + +let vm; + +function factory(propsData = {}) { + vm = shallowMount(GraphBar, { propsData }); +} + +describe('Branch divergence graph bar component', () => { + afterEach(() => { + vm.destroy(); + }); + + it.each` + position | positionClass + ${'left'} | ${'position-right-0'} + ${'right'} | ${'position-left-0'} + ${'full'} | ${'position-left-0'} + `( + 'sets position class as $positionClass for position $position', + ({ position, positionClass }) => { + factory({ + position, + count: 10, + maxCommits: 100, + }); + + expect(vm.find('.js-graph-bar').classes()).toContain(positionClass); + }, + ); + + it.each` + position | textAlignmentClass + ${'left'} | ${'text-right'} + ${'right'} | ${'text-left'} + ${'full'} | ${'text-center'} + `( + 'sets text alignment class as $textAlignmentClass for position $position', + ({ position, textAlignmentClass }) => { + factory({ + position, + count: 10, + maxCommits: 100, + }); + + expect(vm.find('.js-graph-count').classes()).toContain(textAlignmentClass); + }, + ); + + it.each` + position | roundedClass + ${'left'} | ${'rounded-left'} + ${'right'} | ${'rounded-right'} + ${'full'} | ${'rounded'} + `('sets rounded class as $roundedClass for position $position', ({ position, roundedClass }) => { + factory({ + position, + count: 10, + maxCommits: 100, + }); + + expect(vm.find('.js-graph-bar').classes()).toContain(roundedClass); + }); + + it.each` + count | label + ${100} | ${'100'} + ${1000} | ${'999+'} + `('renders label as $roundedClass for $count', ({ count, label }) => { + factory({ + position: 'left', + count, + maxCommits: 1000, + }); + + expect(vm.find('.js-graph-count').text()).toContain(label); + }); + + it('sets width of bar', () => { + factory({ + position: 'left', + count: 100, + maxCommits: 1000, + }); + + expect(vm.find('.js-graph-bar').attributes('style')).toEqual('width: 10%;'); + }); +}); diff --git a/spec/frontend/clusters/services/application_state_machine_spec.js b/spec/frontend/clusters/services/application_state_machine_spec.js index c146ef79be7..8632c5c4e26 100644 --- a/spec/frontend/clusters/services/application_state_machine_spec.js +++ b/spec/frontend/clusters/services/application_state_machine_spec.js @@ -72,9 +72,10 @@ describe('applicationStateMachine', () => { describe(`current state is ${INSTALLABLE}`, () => { it.each` - expectedState | event | effects - ${INSTALLING} | ${INSTALL_EVENT} | ${{ installFailed: false }} - ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} + expectedState | event | effects + ${INSTALLING} | ${INSTALL_EVENT} | ${{ installFailed: false }} + ${INSTALLED} | ${INSTALLED} | ${NO_EFFECTS} + ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS} `(`transitions to $expectedState on $event event and applies $effects`, data => { const { expectedState, event, effects } = data; const currentAppState = { @@ -108,9 +109,10 @@ describe('applicationStateMachine', () => { describe(`current state is ${INSTALLED}`, () => { it.each` - expectedState | event | effects - ${UPDATING} | ${UPDATE_EVENT} | ${{ updateFailed: false, updateSuccessful: false }} - ${UNINSTALLING} | ${UNINSTALL_EVENT} | ${{ uninstallFailed: false, uninstallSuccessful: false }} + expectedState | event | effects + ${UPDATING} | ${UPDATE_EVENT} | ${{ updateFailed: false, updateSuccessful: false }} + ${UNINSTALLING} | ${UNINSTALL_EVENT} | ${{ uninstallFailed: false, uninstallSuccessful: false }} + ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS} `(`transitions to $expectedState on $event event and applies $effects`, data => { const { expectedState, event, effects } = data; const currentAppState = { @@ -119,7 +121,7 @@ describe('applicationStateMachine', () => { expect(transitionApplicationState(currentAppState, event)).toEqual({ status: expectedState, - ...effects, + ...noEffectsToEmptyObject(effects), }); }); }); diff --git a/spec/frontend/helpers/vuex_action_helper.js b/spec/frontend/helpers/vuex_action_helper.js index 88652202a8e..6c3569a2247 100644 --- a/spec/frontend/helpers/vuex_action_helper.js +++ b/spec/frontend/helpers/vuex_action_helper.js @@ -20,7 +20,7 @@ const noop = () => {}; * // expected mutations * [ * { type: types.MUTATION} - * { type: types.MUTATION_1, payload: jasmine.any(Number)} + * { type: types.MUTATION_1, payload: expect.any(Number)} * ], * // expected actions * [ @@ -89,10 +89,7 @@ export default ( payload, ); - return new Promise(resolve => { - setImmediate(resolve); - }) - .then(() => result) + return (result || new Promise(resolve => setImmediate(resolve))) .catch(error => { validateResults(); throw error; diff --git a/spec/frontend/helpers/vuex_action_helper_spec.js b/spec/frontend/helpers/vuex_action_helper_spec.js new file mode 100644 index 00000000000..61d05762a04 --- /dev/null +++ b/spec/frontend/helpers/vuex_action_helper_spec.js @@ -0,0 +1,166 @@ +import MockAdapter from 'axios-mock-adapter'; +import { TEST_HOST } from 'helpers/test_constants'; +import axios from '~/lib/utils/axios_utils'; +import testAction from './vuex_action_helper'; + +describe('VueX test helper (testAction)', () => { + let originalExpect; + let assertion; + let mock; + const noop = () => {}; + + beforeEach(() => { + mock = new MockAdapter(axios); + /** + * In order to test the helper properly, we need to overwrite the Jest + * `expect` helper. We test that the testAction helper properly passes the + * dispatched actions/committed mutations to the Jest helper. + */ + originalExpect = expect; + assertion = null; + global.expect = actual => ({ + toEqual: () => { + originalExpect(actual).toEqual(assertion); + }, + }); + }); + + afterEach(() => { + mock.restore(); + global.expect = originalExpect; + }); + + it('properly passes state and payload to action', () => { + const exampleState = { FOO: 12, BAR: 3 }; + const examplePayload = { BAZ: 73, BIZ: 55 }; + + const action = ({ state }, payload) => { + originalExpect(state).toEqual(exampleState); + originalExpect(payload).toEqual(examplePayload); + }; + + assertion = { mutations: [], actions: [] }; + + testAction(action, examplePayload, exampleState); + }); + + describe('given a sync action', () => { + it('mocks committing mutations', () => { + const action = ({ commit }) => { + commit('MUTATION'); + }; + + assertion = { mutations: [{ type: 'MUTATION' }], actions: [] }; + + testAction(action, null, {}, assertion.mutations, assertion.actions, noop); + }); + + it('mocks dispatching actions', () => { + const action = ({ dispatch }) => { + dispatch('ACTION'); + }; + + assertion = { actions: [{ type: 'ACTION' }], mutations: [] }; + + testAction(action, null, {}, assertion.mutations, assertion.actions, noop); + }); + + it('works with done callback once finished', done => { + assertion = { mutations: [], actions: [] }; + + testAction(noop, null, {}, assertion.mutations, assertion.actions, done); + }); + + it('returns a promise', done => { + assertion = { mutations: [], actions: [] }; + + testAction(noop, null, {}, assertion.mutations, assertion.actions) + .then(done) + .catch(done.fail); + }); + }); + + describe('given an async action (returning a promise)', () => { + let lastError; + const data = { FOO: 'BAR' }; + + const asyncAction = ({ commit, dispatch }) => { + dispatch('ACTION'); + + return axios + .get(TEST_HOST) + .catch(error => { + commit('ERROR'); + lastError = error; + throw error; + }) + .then(() => { + commit('SUCCESS'); + return data; + }); + }; + + beforeEach(() => { + lastError = null; + }); + + it('works with done callback once finished', done => { + mock.onGet(TEST_HOST).replyOnce(200, 42); + + assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + + testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done); + }); + + it('returns original data of successful promise while checking actions/mutations', done => { + mock.onGet(TEST_HOST).replyOnce(200, 42); + + assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + + testAction(asyncAction, null, {}, assertion.mutations, assertion.actions) + .then(res => { + originalExpect(res).toEqual(data); + done(); + }) + .catch(done.fail); + }); + + it('returns original error of rejected promise while checking actions/mutations', done => { + mock.onGet(TEST_HOST).replyOnce(500, ''); + + assertion = { mutations: [{ type: 'ERROR' }], actions: [{ type: 'ACTION' }] }; + + testAction(asyncAction, null, {}, assertion.mutations, assertion.actions) + .then(done.fail) + .catch(error => { + originalExpect(error).toBe(lastError); + done(); + }); + }); + }); + + it('works with async actions not returning promises', done => { + const data = { FOO: 'BAR' }; + + const asyncAction = ({ commit, dispatch }) => { + dispatch('ACTION'); + + axios + .get(TEST_HOST) + .then(() => { + commit('SUCCESS'); + return data; + }) + .catch(error => { + commit('ERROR'); + throw error; + }); + }; + + mock.onGet(TEST_HOST).replyOnce(200, 42); + + assertion = { mutations: [{ type: 'SUCCESS' }], actions: [{ type: 'ACTION' }] }; + + testAction(asyncAction, null, {}, assertion.mutations, assertion.actions, done); + }); +}); diff --git a/spec/frontend/ide/utils_spec.js b/spec/frontend/ide/utils_spec.js new file mode 100644 index 00000000000..2b7dffdcd88 --- /dev/null +++ b/spec/frontend/ide/utils_spec.js @@ -0,0 +1,44 @@ +import { commitItemIconMap } from '~/ide/constants'; +import { getCommitIconMap } from '~/ide/utils'; +import { decorateData } from '~/ide/stores/utils'; + +describe('WebIDE utils', () => { + const createFile = (name = 'name', id = name, type = '', parent = null) => + decorateData({ + id, + type, + icon: 'icon', + url: 'url', + name, + path: parent ? `${parent.path}/${name}` : name, + parentPath: parent ? parent.path : '', + lastCommit: {}, + }); + + describe('getCommitIconMap', () => { + let entry; + + beforeEach(() => { + entry = createFile('Entry item'); + }); + + it('renders "deleted" icon for deleted entries', () => { + entry.deleted = true; + expect(getCommitIconMap(entry)).toEqual(commitItemIconMap.deleted); + }); + it('renders "addition" icon for temp entries', () => { + entry.tempFile = true; + expect(getCommitIconMap(entry)).toEqual(commitItemIconMap.addition); + }); + it('renders "modified" icon for newly-renamed entries', () => { + entry.prevPath = 'foo/bar'; + entry.tempFile = false; + expect(getCommitIconMap(entry)).toEqual(commitItemIconMap.modified); + }); + it('renders "modified" icon even for temp entries if they are newly-renamed', () => { + entry.prevPath = 'foo/bar'; + entry.tempFile = true; + expect(getCommitIconMap(entry)).toEqual(commitItemIconMap.modified); + }); + }); +}); diff --git a/spec/frontend/lib/utils/datetime_utility_spec.js b/spec/frontend/lib/utils/datetime_utility_spec.js index 9f49e68cfe8..751fb5e1b94 100644 --- a/spec/frontend/lib/utils/datetime_utility_spec.js +++ b/spec/frontend/lib/utils/datetime_utility_spec.js @@ -334,6 +334,12 @@ describe('prettyTime methods', () => { assertTimeUnits(aboveOneDay, 33, 2, 2, 0); assertTimeUnits(aboveOneWeek, 26, 0, 1, 9); }); + + it('should correctly parse values when limitedToHours is true', () => { + const twoDays = datetimeUtility.parseSeconds(173000, { limitToHours: true }); + + assertTimeUnits(twoDays, 3, 48, 0, 0); + }); }); describe('stringifyTime', () => { diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap index 1f06d693411..d55dc553031 100644 --- a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap +++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap @@ -29,10 +29,20 @@ exports[`Repository table row component renders table row 1`] = ` <td class="d-none d-sm-table-cell tree-commit" - /> + > + <glskeletonloading-stub + class="h-auto" + lines="1" + /> + </td> <td class="tree-time-ago text-right" - /> + > + <glskeletonloading-stub + class="ml-auto h-auto w-50" + lines="1" + /> + </td> </tr> `; diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 5a345ddeacd..c566057ad3f 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -16,6 +16,8 @@ function factory(propsData = {}) { vm = shallowMount(TableRow, { propsData: { ...propsData, + name: propsData.path, + projectPath: 'gitlab-org/gitlab-ce', url: `https://test.com`, }, mocks: { diff --git a/spec/frontend/repository/log_tree_spec.js b/spec/frontend/repository/log_tree_spec.js new file mode 100644 index 00000000000..a9499f7c61b --- /dev/null +++ b/spec/frontend/repository/log_tree_spec.js @@ -0,0 +1,129 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import { normalizeData, resolveCommit, fetchLogsTree } from '~/repository/log_tree'; + +const mockData = [ + { + commit: { + id: '123', + message: 'testing message', + committed_date: '2019-01-01', + }, + commit_path: `https://test.com`, + file_name: 'index.js', + type: 'blob', + }, +]; + +describe('normalizeData', () => { + it('normalizes data into LogTreeCommit object', () => { + expect(normalizeData(mockData)).toEqual([ + { + sha: '123', + message: 'testing message', + committedDate: '2019-01-01', + commitPath: 'https://test.com', + fileName: 'index.js', + type: 'blob', + __typename: 'LogTreeCommit', + }, + ]); + }); +}); + +describe('resolveCommit', () => { + it('calls resolve when commit found', () => { + const resolver = { + entry: { name: 'index.js', type: 'blob' }, + resolve: jest.fn(), + }; + const commits = [{ fileName: 'index.js', type: 'blob' }]; + + resolveCommit(commits, resolver); + + expect(resolver.resolve).toHaveBeenCalledWith({ fileName: 'index.js', type: 'blob' }); + }); +}); + +describe('fetchLogsTree', () => { + let mock; + let client; + let resolver; + + beforeEach(() => { + mock = new MockAdapter(axios); + + mock.onGet(/(.*)/).reply(200, mockData, {}); + + jest.spyOn(axios, 'get'); + + global.gon = { gitlab_url: 'https://test.com' }; + + client = { + readQuery: () => ({ + projectPath: 'gitlab-org/gitlab-ce', + ref: 'master', + commits: [], + }), + writeQuery: jest.fn(), + }; + + resolver = { + entry: { name: 'index.js', type: 'blob' }, + resolve: jest.fn(), + }; + }); + + afterEach(() => { + mock.restore(); + }); + + it('calls axios get', () => + fetchLogsTree(client, '', '0', resolver).then(() => { + expect(axios.get).toHaveBeenCalledWith( + 'https://test.com/gitlab-org/gitlab-ce/refs/master/logs_tree', + { params: { format: 'json', offset: '0' } }, + ); + })); + + it('calls axios get once', () => + Promise.all([ + fetchLogsTree(client, '', '0', resolver), + fetchLogsTree(client, '', '0', resolver), + ]).then(() => { + expect(axios.get.mock.calls.length).toEqual(1); + })); + + it('calls entry resolver', () => + fetchLogsTree(client, '', '0', resolver).then(() => { + expect(resolver.resolve).toHaveBeenCalledWith({ + __typename: 'LogTreeCommit', + commitPath: 'https://test.com', + committedDate: '2019-01-01', + fileName: 'index.js', + message: 'testing message', + sha: '123', + type: 'blob', + }); + })); + + it('writes query to client', () => + fetchLogsTree(client, '', '0', resolver).then(() => { + expect(client.writeQuery).toHaveBeenCalledWith({ + query: expect.anything(), + data: { + commits: [ + { + __typename: 'LogTreeCommit', + commitPath: 'https://test.com', + committedDate: '2019-01-01', + fileName: 'index.js', + message: 'testing message', + sha: '123', + type: 'blob', + }, + ], + }, + }); + })); +}); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 7e7cc1488b8..c17d5253997 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -1,10 +1,16 @@ import Vue from 'vue'; import * as jqueryMatchers from 'custom-jquery-matchers'; +import $ from 'jquery'; import Translate from '~/vue_shared/translate'; import axios from '~/lib/utils/axios_utils'; import { initializeTestTimeout } from './helpers/timeout'; import { loadHTMLFixture, setHTMLFixture } from './helpers/fixtures'; +// Expose jQuery so specs using jQuery plugins can be imported nicely. +// Here is an issue to explore better alternatives: +// https://gitlab.com/gitlab-org/gitlab-ee/issues/12448 +window.jQuery = $; + process.on('unhandledRejection', global.promiseRejectionHandler); afterEach(() => diff --git a/spec/helpers/appearances_helper_spec.rb b/spec/helpers/appearances_helper_spec.rb index a3511e078ce..ed3e31b3c53 100644 --- a/spec/helpers/appearances_helper_spec.rb +++ b/spec/helpers/appearances_helper_spec.rb @@ -8,6 +8,22 @@ describe AppearancesHelper do allow(helper).to receive(:current_user).and_return(user) end + describe '.current_appearance' do + it 'memoizes empty appearance' do + expect(Appearance).to receive(:current).once + + 2.times { helper.current_appearance } + end + + it 'memoizes custom appearance' do + create(:appearance) + + expect(Appearance).to receive(:current).once.and_call_original + + 2.times { helper.current_appearance } + end + end + describe '#header_message' do it 'returns nil when header message field is not set' do create(:appearance) diff --git a/spec/helpers/recaptcha_experiment_helper_spec.rb b/spec/helpers/recaptcha_experiment_helper_spec.rb new file mode 100644 index 00000000000..775c2caa082 --- /dev/null +++ b/spec/helpers/recaptcha_experiment_helper_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe RecaptchaExperimentHelper, type: :helper do + describe '.show_recaptcha_sign_up?' do + context 'when reCAPTCHA is disabled' do + it 'returns false' do + stub_application_setting(recaptcha_enabled: false) + + expect(helper.show_recaptcha_sign_up?).to be(false) + end + end + + context 'when reCAPTCHA is enabled' do + it 'returns true' do + stub_application_setting(recaptcha_enabled: true) + + expect(helper.show_recaptcha_sign_up?).to be(true) + end + end + end +end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index da14f7f16fb..c69493b579f 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -114,7 +114,7 @@ describe SearchHelper do end it 'includes project endpoints' do - expect(search_filter_input_options('')[:data]['base-endpoint']).to eq(project_path(@project)) + expect(search_filter_input_options('')[:data]['runner-tags-endpoint']).to eq(tag_list_admin_runners_path) expect(search_filter_input_options('')[:data]['labels-endpoint']).to eq(project_labels_path(@project)) expect(search_filter_input_options('')[:data]['milestones-endpoint']).to eq(project_milestones_path(@project)) end @@ -134,7 +134,7 @@ describe SearchHelper do end it 'includes group endpoints' do - expect(search_filter_input_options('')[:data]['base-endpoint']).to eq("/groups#{group_path(@group)}") + expect(search_filter_input_options('')[:data]['runner-tags-endpoint']).to eq(tag_list_admin_runners_path) expect(search_filter_input_options('')[:data]['labels-endpoint']).to eq(group_labels_path(@group)) expect(search_filter_input_options('')[:data]['milestones-endpoint']).to eq(group_milestones_path(@group)) end @@ -147,7 +147,7 @@ describe SearchHelper do end it 'includes dashboard endpoints' do - expect(search_filter_input_options('')[:data]['base-endpoint']).to eq("/dashboard") + expect(search_filter_input_options('')[:data]['runner-tags-endpoint']).to eq(tag_list_admin_runners_path) expect(search_filter_input_options('')[:data]['labels-endpoint']).to eq(dashboard_labels_path) expect(search_filter_input_options('')[:data]['milestones-endpoint']).to eq(dashboard_milestones_path) end diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index e81115e10c9..5266b1bdbfc 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -355,4 +355,14 @@ describe('Store', () => { expect(boardsStore.moving.list).toEqual(dummyList); }); }); + + describe('setTimeTrackingLimitToHours', () => { + it('sets the timeTracking.LimitToHours option', () => { + boardsStore.timeTracking.limitToHours = false; + + boardsStore.setTimeTrackingLimitToHours('true'); + + expect(boardsStore.timeTracking.limitToHours).toEqual(true); + }); + }); }); diff --git a/spec/javascripts/boards/components/issue_time_estimate_spec.js b/spec/javascripts/boards/components/issue_time_estimate_spec.js index ba65d3287da..de48e3f6091 100644 --- a/spec/javascripts/boards/components/issue_time_estimate_spec.js +++ b/spec/javascripts/boards/components/issue_time_estimate_spec.js @@ -1,40 +1,70 @@ import Vue from 'vue'; import IssueTimeEstimate from '~/boards/components/issue_time_estimate.vue'; +import boardsStore from '~/boards/stores/boards_store'; import mountComponent from '../../helpers/vue_mount_component_helper'; -describe('Issue Tine Estimate component', () => { +describe('Issue Time Estimate component', () => { let vm; beforeEach(() => { - const Component = Vue.extend(IssueTimeEstimate); - vm = mountComponent(Component, { - estimate: 374460, - }); + boardsStore.create(); }); afterEach(() => { vm.$destroy(); }); - it('renders the correct time estimate', () => { - expect(vm.$el.querySelector('time').textContent.trim()).toEqual('2w 3d 1m'); - }); + describe('when limitToHours is false', () => { + beforeEach(() => { + boardsStore.timeTracking.limitToHours = false; + + const Component = Vue.extend(IssueTimeEstimate); + vm = mountComponent(Component, { + estimate: 374460, + }); + }); + + it('renders the correct time estimate', () => { + expect(vm.$el.querySelector('time').textContent.trim()).toEqual('2w 3d 1m'); + }); + + it('renders expanded time estimate in tooltip', () => { + expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain( + '2 weeks 3 days 1 minute', + ); + }); + + it('prevents tooltip xss', done => { + const alertSpy = spyOn(window, 'alert'); + vm.estimate = 'Foo <script>alert("XSS")</script>'; - it('renders expanded time estimate in tooltip', () => { - expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain( - '2 weeks 3 days 1 minute', - ); + vm.$nextTick(() => { + expect(alertSpy).not.toHaveBeenCalled(); + expect(vm.$el.querySelector('time').textContent.trim()).toEqual('0m'); + expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain('0m'); + done(); + }); + }); }); - it('prevents tooltip xss', done => { - const alertSpy = spyOn(window, 'alert'); - vm.estimate = 'Foo <script>alert("XSS")</script>'; + describe('when limitToHours is true', () => { + beforeEach(() => { + boardsStore.timeTracking.limitToHours = true; + + const Component = Vue.extend(IssueTimeEstimate); + vm = mountComponent(Component, { + estimate: 374460, + }); + }); + + it('renders the correct time estimate', () => { + expect(vm.$el.querySelector('time').textContent.trim()).toEqual('104h 1m'); + }); - vm.$nextTick(() => { - expect(alertSpy).not.toHaveBeenCalled(); - expect(vm.$el.querySelector('time').textContent.trim()).toEqual('0m'); - expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain('0m'); - done(); + it('renders expanded time estimate in tooltip', () => { + expect(vm.$el.querySelector('.js-issue-time-estimate').textContent).toContain( + '104 hours 1 minute', + ); }); }); }); diff --git a/spec/javascripts/filtered_search/visual_token_value_spec.js b/spec/javascripts/filtered_search/visual_token_value_spec.js index d1d16afc977..10d844fd94b 100644 --- a/spec/javascripts/filtered_search/visual_token_value_spec.js +++ b/spec/javascripts/filtered_search/visual_token_value_spec.js @@ -155,7 +155,7 @@ describe('Filtered Search Visual Tokens', () => { `); const filteredSearchInput = document.querySelector('.filtered-search'); - filteredSearchInput.dataset.baseEndpoint = dummyEndpoint; + filteredSearchInput.dataset.runnerTagsEndpoint = `${dummyEndpoint}/admin/runners/tag_list`; filteredSearchInput.dataset.labelsEndpoint = `${dummyEndpoint}/-/labels`; filteredSearchInput.dataset.milestonesEndpoint = `${dummyEndpoint}/-/milestones`; diff --git a/spec/javascripts/groups/components/group_item_spec.js b/spec/javascripts/groups/components/group_item_spec.js index 4d6d0c895b6..cc88a7ac6c1 100644 --- a/spec/javascripts/groups/components/group_item_spec.js +++ b/spec/javascripts/groups/components/group_item_spec.js @@ -156,6 +156,8 @@ describe('GroupItemComponent', () => { describe('template', () => { it('should render component template correctly', () => { + const visibilityIconEl = vm.$el.querySelector('.item-visibility'); + expect(vm.$el.getAttribute('id')).toBe('group-55'); expect(vm.$el.classList.contains('group-row')).toBeTruthy(); @@ -173,6 +175,11 @@ describe('GroupItemComponent', () => { expect(vm.$el.querySelector('.title')).toBeDefined(); expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined(); + + expect(visibilityIconEl).not.toBe(null); + expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip); + expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0); + expect(vm.$el.querySelector('.access-type')).toBeDefined(); expect(vm.$el.querySelector('.description')).toBeDefined(); diff --git a/spec/javascripts/groups/components/item_stats_spec.js b/spec/javascripts/groups/components/item_stats_spec.js index 00d6a4817d7..b2441babf3f 100644 --- a/spec/javascripts/groups/components/item_stats_spec.js +++ b/spec/javascripts/groups/components/item_stats_spec.js @@ -108,18 +108,6 @@ describe('ItemStatsComponent', () => { vm.$destroy(); }); - it('renders item visibility icon and tooltip correctly', () => { - const vm = createComponent(); - - const visibilityIconEl = vm.$el.querySelector('.item-visibility'); - - expect(visibilityIconEl).not.toBe(null); - expect(visibilityIconEl.dataset.originalTitle).toBe(vm.visibilityTooltip); - expect(visibilityIconEl.querySelectorAll('svg').length).toBeGreaterThan(0); - - vm.$destroy(); - }); - it('renders start count and last updated information for project item correctly', () => { const item = Object.assign({}, mockParentGroupItem, { type: ITEM_TYPE.PROJECT, diff --git a/spec/javascripts/ide/components/ide_tree_list_spec.js b/spec/javascripts/ide/components/ide_tree_list_spec.js index f63007c7dd2..554bd1ae3b5 100644 --- a/spec/javascripts/ide/components/ide_tree_list_spec.js +++ b/spec/javascripts/ide/components/ide_tree_list_spec.js @@ -58,6 +58,20 @@ describe('IDE tree list', () => { it('renders list of files', () => { expect(vm.$el.textContent).toContain('fileName'); }); + + it('does not render moved entries', done => { + const tree = [file('moved entry'), file('normal entry')]; + tree[0].moved = true; + store.state.trees['abcproject/master'].tree = tree; + const container = vm.$el.querySelector('.ide-tree-body'); + + vm.$nextTick(() => { + expect(container.children.length).toBe(1); + expect(vm.$el.textContent).not.toContain('moved entry'); + expect(vm.$el.textContent).toContain('normal entry'); + done(); + }); + }); }); describe('empty-branch state', () => { diff --git a/spec/javascripts/ide/stores/actions/file_spec.js b/spec/javascripts/ide/stores/actions/file_spec.js index dd2313dc800..021c3076094 100644 --- a/spec/javascripts/ide/stores/actions/file_spec.js +++ b/spec/javascripts/ide/stores/actions/file_spec.js @@ -275,6 +275,43 @@ describe('IDE store file actions', () => { }); }); + describe('Re-named success', () => { + beforeEach(() => { + localFile = file(`newCreate-${Math.random()}`); + localFile.url = `project/getFileDataURL`; + localFile.prevPath = 'old-dull-file'; + localFile.path = 'new-shiny-file'; + store.state.entries[localFile.path] = localFile; + + mock.onGet(`${RELATIVE_URL_ROOT}/project/getFileDataURL`).replyOnce( + 200, + { + blame_path: 'blame_path', + commits_path: 'commits_path', + permalink: 'permalink', + raw_path: 'raw_path', + binary: false, + html: '123', + render_error: '', + }, + { + 'page-title': 'testing old-dull-file', + }, + ); + }); + + it('sets document title considering `prevPath` on a file', done => { + store + .dispatch('getFileData', { path: localFile.path }) + .then(() => { + expect(document.title).toBe('testing new-shiny-file'); + + done(); + }) + .catch(done.fail); + }); + }); + describe('error', () => { beforeEach(() => { mock.onGet(`project/getFileDataURL`).networkError(); diff --git a/spec/javascripts/ide/stores/actions_spec.js b/spec/javascripts/ide/stores/actions_spec.js index 37354283cab..2d105103c1c 100644 --- a/spec/javascripts/ide/stores/actions_spec.js +++ b/spec/javascripts/ide/stores/actions_spec.js @@ -492,6 +492,33 @@ describe('Multi-file store actions', () => { done, ); }); + + it('does not delete a folder after it is emptied', done => { + const testFolder = { + type: 'tree', + tree: [], + }; + const testEntry = { + path: 'testFolder/entry-to-delete', + parentPath: 'testFolder', + opened: false, + tree: [], + }; + testFolder.tree.push(testEntry); + store.state.entries = { + testFolder, + 'testFolder/entry-to-delete': testEntry, + }; + + testAction( + deleteEntry, + 'testFolder/entry-to-delete', + store.state, + [{ type: types.DELETE_ENTRY, payload: 'testFolder/entry-to-delete' }], + [{ type: 'burstUnusedSeal' }, { type: 'triggerFilesChange' }], + done, + ); + }); }); describe('renameEntry', () => { @@ -509,8 +536,15 @@ describe('Multi-file store actions', () => { type: types.RENAME_ENTRY, payload: { path: 'test', name: 'new-name', entryPath: null, parentPath: 'parent-path' }, }, + { + type: types.TOGGLE_FILE_CHANGED, + payload: { + file: store.state.entries['parent-path/new-name'], + changed: true, + }, + }, ], - [{ type: 'deleteEntry', payload: 'test' }, { type: 'triggerFilesChange' }], + [{ type: 'triggerFilesChange' }], done, ); }); @@ -557,7 +591,6 @@ describe('Multi-file store actions', () => { parentPath: 'parent-path/new-name', }, }, - { type: 'deleteEntry', payload: 'test' }, { type: 'triggerFilesChange' }, ], done, diff --git a/spec/javascripts/ide/stores/mutations_spec.js b/spec/javascripts/ide/stores/mutations_spec.js index 5ee098bf17f..460c5b01081 100644 --- a/spec/javascripts/ide/stores/mutations_spec.js +++ b/spec/javascripts/ide/stores/mutations_spec.js @@ -309,7 +309,7 @@ describe('Multi-file store mutations', () => { ...localState.entries.oldPath, id: 'newPath', name: 'newPath', - key: 'newPath-blob-name', + key: 'newPath-blob-oldPath', path: 'newPath', tempFile: true, prevPath: 'oldPath', @@ -318,6 +318,7 @@ describe('Multi-file store mutations', () => { url: `${gl.TEST_HOST}/newPath`, moved: jasmine.anything(), movedPath: jasmine.anything(), + opened: false, }); }); @@ -349,13 +350,5 @@ describe('Multi-file store mutations', () => { expect(localState.entries.parentPath.tree.length).toBe(1); }); - - it('adds to openFiles if previously opened', () => { - localState.entries.oldPath.opened = true; - - mutations.RENAME_ENTRY(localState, { path: 'oldPath', name: 'newPath' }); - - expect(localState.openFiles).toEqual([localState.entries.newPath]); - }); }); }); diff --git a/spec/javascripts/monitoring/charts/column_spec.js b/spec/javascripts/monitoring/charts/column_spec.js new file mode 100644 index 00000000000..d8ac68b9484 --- /dev/null +++ b/spec/javascripts/monitoring/charts/column_spec.js @@ -0,0 +1,58 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlColumnChart } from '@gitlab/ui/dist/charts'; +import ColumnChart from '~/monitoring/components/charts/column.vue'; + +describe('Column component', () => { + let columnChart; + + beforeEach(() => { + columnChart = shallowMount(ColumnChart, { + propsData: { + graphData: { + queries: [ + { + x_label: 'Time', + y_label: 'Usage', + result: [ + { + metric: {}, + values: [ + [1495700554.925, '8.0390625'], + [1495700614.925, '8.0390625'], + [1495700674.925, '8.0390625'], + ], + }, + ], + }, + ], + }, + containerWidth: 100, + }, + }); + }); + + afterEach(() => { + columnChart.destroy(); + }); + + describe('wrapped components', () => { + describe('GitLab UI column chart', () => { + let glColumnChart; + + beforeEach(() => { + glColumnChart = columnChart.find(GlColumnChart); + }); + + it('is a Vue instance', () => { + expect(glColumnChart.isVueInstance()).toBe(true); + }); + + it('receives data properties needed for proper chart render', () => { + const props = glColumnChart.props(); + + expect(props.data).toBe(columnChart.vm.chartData); + expect(props.option).toBe(columnChart.vm.chartOptions); + }); + }); + }); +}); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index f4166987aed..ab8360193be 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -10,6 +10,7 @@ import { mockApiEndpoint, environmentData, singleGroupResponse, + dashboardGitResponse, } from './mock_data'; const propsData = { @@ -62,16 +63,34 @@ describe('Dashboard', () => { }); describe('no metrics are available yet', () => { - it('shows a getting started empty state when no metrics are present', () => { + beforeEach(() => { component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), propsData: { ...propsData }, store, }); + }); + it('shows a getting started empty state when no metrics are present', () => { expect(component.$el.querySelector('.prometheus-graphs')).toBe(null); expect(component.emptyState).toEqual('gettingStarted'); }); + + it('shows the environment selector', () => { + expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); + }); + }); + + describe('no data found', () => { + it('shows the environment selector dropdown', () => { + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { ...propsData, showEmptyState: true }, + store, + }); + + expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy(); + }); }); describe('requests information to the server', () => { @@ -150,14 +169,24 @@ describe('Dashboard', () => { singleGroupResponse, ); - setTimeout(() => { - const dropdownMenuEnvironments = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item', - ); + Vue.nextTick() + .then(() => { + const dropdownMenuEnvironments = component.$el.querySelectorAll( + '.js-environments-dropdown .dropdown-item', + ); - expect(dropdownMenuEnvironments.length).toEqual(component.environments.length); - done(); - }); + expect(component.environments.length).toEqual(environmentData.length); + expect(dropdownMenuEnvironments.length).toEqual(component.environments.length); + + Array.from(dropdownMenuEnvironments).forEach((value, index) => { + if (environmentData[index].metrics_path) { + expect(value).toHaveAttr('href', environmentData[index].metrics_path); + } + }); + + done(); + }) + .catch(done.fail); }); it('hides the environments dropdown list when there is no environments', done => { @@ -212,7 +241,7 @@ describe('Dashboard', () => { Vue.nextTick() .then(() => { const dropdownItems = component.$el.querySelectorAll( - '.js-environments-dropdown .dropdown-item[active="true"]', + '.js-environments-dropdown .dropdown-item.is-active', ); expect(dropdownItems.length).toEqual(1); @@ -281,10 +310,6 @@ describe('Dashboard', () => { const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff'); component.$store.commit( - `monitoringDashboard/${types.SET_ENVIRONMENTS_ENDPOINT}`, - '/environments', - ); - component.$store.commit( `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, environmentData, ); @@ -402,4 +427,49 @@ describe('Dashboard', () => { }); }); }); + + describe('Dashboard dropdown', () => { + beforeEach(() => { + mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse); + + component = new DashboardComponent({ + el: document.querySelector('.prometheus-graphs'), + propsData: { + ...propsData, + hasMetrics: true, + showPanels: false, + }, + store, + }); + + component.$store.dispatch('monitoringDashboard/setFeatureFlags', { + prometheusEndpoint: false, + multipleDashboardsEnabled: true, + }); + + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, + environmentData, + ); + + component.$store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + singleGroupResponse, + ); + + component.$store.commit( + `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`, + dashboardGitResponse, + ); + }); + + it('shows the dashboard dropdown', done => { + setTimeout(() => { + const dashboardDropdown = component.$el.querySelector('.js-dashboards-dropdown'); + + expect(dashboardDropdown).not.toEqual(null); + done(); + }); + }); + }); }); diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index 82e42fe9ade..7bbb215475a 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -922,3 +922,16 @@ export const metricsDashboardResponse = { }, status: 'success', }; + +export const dashboardGitResponse = [ + { + path: 'config/prometheus/common_metrics.yml', + display_name: 'Common Metrics', + default: true, + }, + { + path: '.gitlab/dashboards/super.yml', + display_name: 'Custom Dashboard 1', + default: false, + }, +]; diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js index 083a01c4d74..677455275de 100644 --- a/spec/javascripts/monitoring/store/actions_spec.js +++ b/spec/javascripts/monitoring/store/actions_spec.js @@ -22,6 +22,7 @@ import { environmentData, metricsDashboardResponse, metricsGroupsAPIResponse, + dashboardGitResponse, } from '../mock_data'; describe('Monitoring store actions', () => { @@ -212,17 +213,19 @@ describe('Monitoring store actions', () => { describe('receiveMetricsDashboardSuccess', () => { let commit; let dispatch; + let state; beforeEach(() => { commit = jasmine.createSpy(); dispatch = jasmine.createSpy(); + state = storeState(); }); it('stores groups ', () => { const params = {}; const response = metricsDashboardResponse; - receiveMetricsDashboardSuccess({ commit, dispatch }, { response, params }); + receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params }); expect(commit).toHaveBeenCalledWith( types.RECEIVE_METRICS_DATA_SUCCESS, @@ -231,6 +234,18 @@ describe('Monitoring store actions', () => { expect(dispatch).toHaveBeenCalledWith('fetchPrometheusMetrics', params); }); + + it('sets the dashboards loaded from the repository', () => { + const params = {}; + const response = metricsDashboardResponse; + + response.all_dashboards = dashboardGitResponse; + state.multipleDashboardsEnabled = true; + + receiveMetricsDashboardSuccess({ state, commit, dispatch }, { response, params }); + + expect(commit).toHaveBeenCalledWith(types.SET_ALL_DASHBOARDS, dashboardGitResponse); + }); }); describe('receiveMetricsDashboardFailure', () => { diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js index 02ff5847b34..91580366531 100644 --- a/spec/javascripts/monitoring/store/mutations_spec.js +++ b/spec/javascripts/monitoring/store/mutations_spec.js @@ -1,7 +1,12 @@ import mutations from '~/monitoring/stores/mutations'; import * as types from '~/monitoring/stores/mutation_types'; import state from '~/monitoring/stores/state'; -import { metricsGroupsAPIResponse, deploymentData, metricsDashboardResponse } from '../mock_data'; +import { + metricsGroupsAPIResponse, + deploymentData, + metricsDashboardResponse, + dashboardGitResponse, +} from '../mock_data'; describe('Monitoring mutations', () => { let stateCopy; @@ -156,4 +161,12 @@ describe('Monitoring mutations', () => { expect(stateCopy.metricsWithData).toEqual([]); }); }); + + describe('SET_ALL_DASHBOARDS', () => { + it('stores the dashboards loaded from the git repository', () => { + mutations[types.SET_ALL_DASHBOARDS](stateCopy, dashboardGitResponse); + + expect(stateCopy.allDashboards).toEqual(dashboardGitResponse); + }); + }); }); diff --git a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js index 4c3dd713589..2e1863cff86 100644 --- a/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js +++ b/spec/javascripts/sidebar/components/time_tracking/time_tracker_spec.js @@ -13,6 +13,7 @@ describe('Issuable Time Tracker', () => { timeSpent, timeEstimateHumanReadable, timeSpentHumanReadable, + limitToHours, }) => { setFixtures(` <div> @@ -25,6 +26,7 @@ describe('Issuable Time Tracker', () => { timeSpent, humanTimeEstimate: timeEstimateHumanReadable, humanTimeSpent: timeSpentHumanReadable, + limitToHours: Boolean(limitToHours), rootPath: '/', }; @@ -128,6 +130,29 @@ describe('Issuable Time Tracker', () => { }); }); + describe('Comparison pane when limitToHours is true', () => { + beforeEach(() => { + initTimeTrackingComponent({ + timeEstimate: 100000, // 1d 3h + timeSpent: 5000, // 1h 23m + timeEstimateHumanReadable: '', + timeSpentHumanReadable: '', + limitToHours: true, + }); + }); + + it('should show the correct tooltip text', done => { + Vue.nextTick(() => { + expect(vm.showComparisonState).toBe(true); + const $title = vm.$el.querySelector('.time-tracking-content .compare-meter').dataset + .originalTitle; + + expect($title).toBe('Time remaining: 26h 23m'); + done(); + }); + }); + }); + describe('Estimate only pane', () => { beforeEach(() => { initTimeTrackingComponent({ diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js b/spec/javascripts/vue_shared/components/table_pagination_spec.js index 42abb4d83f0..258530f32f7 100644 --- a/spec/javascripts/vue_shared/components/table_pagination_spec.js +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js @@ -217,7 +217,7 @@ describe('Pagination component', () => { change: spy, }); - expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next'); + expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next ›'); component.$el.querySelector('.js-next-button .page-link').click(); @@ -237,7 +237,7 @@ describe('Pagination component', () => { change: spy, }); - expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next'); + expect(component.$el.querySelector('.js-next-button').textContent.trim()).toEqual('Next ›'); component.$el.querySelector('.js-next-button .page-link').click(); diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index aa4e358b148..3d57ce431ab 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -141,11 +141,11 @@ describe Gitlab::Ci::Ansi2html do end it "replaces newlines with line break tags" do - expect(convert_html("\n")).to eq('<span class=""></span><br/><span class=""></span>') + expect(convert_html("\n")).to eq('<span class=""><br/><span class=""></span></span>') end it "groups carriage returns with newlines" do - expect(convert_html("\r\n")).to eq('<span class=""></span><br/><span class=""></span>') + expect(convert_html("\r\n")).to eq('<span class=""><br/><span class=""></span></span>') end describe "incremental update" do @@ -193,7 +193,7 @@ describe Gitlab::Ci::Ansi2html do let(:pre_text) { "Hello\r" } let(:pre_html) { "<span class=\"\">Hello\r</span>" } let(:text) { "\nWorld" } - let(:html) { "<span class=\"\"></span><br/><span class=\"\">World</span>" } + let(:html) { "<span class=\"\"><br/><span class=\"\">World</span></span>" } it_behaves_like 'stateable converter' end @@ -232,7 +232,7 @@ describe Gitlab::Ci::Ansi2html do it 'prints light red' do text = "#{section_start}\e[91mHello\e[0m\n#{section_end}" header = %{<span class="term-fg-l-red section js-section-header section-header js-s-#{class_name(section_name)}">Hello</span>} - line_break = %{<span class="section js-section-header section-header js-s-#{class_name(section_name)}"></span><br/>} + line_break = %{<span class="section js-section-header section-header js-s-#{class_name(section_name)}"><br/></span>} line = %{<span class="section line s_#{class_name(section_name)}"></span>} empty_line = %{<span class="section js-s-#{class_name(section_name)}"></span>} html = "#{section_start_html}#{header}#{line_break}#{line}#{empty_line}#{section_end_html}" diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index 5ac5122e800..51e16c99688 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do let(:cluster) { create(:cluster, :group) } before do - allow(build.deployment).to receive(:cluster).and_return(cluster) + allow(build.deployment).to receive(:deployment_platform_cluster).and_return(cluster) end it { is_expected.to be_truthy } @@ -45,17 +45,11 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do it { is_expected.to be_truthy } end end - - context 'and cluster is project type' do - let(:cluster) { create(:cluster, :project) } - - it { is_expected.to be_falsey } - end end context 'and no cluster to deploy to' do before do - expect(deployment.cluster).to be_nil + expect(deployment.deployment_platform_cluster).to be_nil end it { is_expected.to be_falsey } @@ -73,7 +67,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do let(:cluster) { create(:cluster, :group) } before do - allow(build.deployment).to receive(:cluster).and_return(cluster) + allow(build.deployment).to receive(:deployment_platform_cluster).and_return(cluster) end it 'creates a kubernetes namespace' do @@ -90,7 +84,7 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do context 'completion is not required' do before do - expect(deployment.cluster).to be_nil + expect(deployment.deployment_platform_cluster).to be_nil end it 'does not create a namespace' do diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index fae8add6453..7991e2f48b5 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -153,76 +153,72 @@ describe Gitlab::Ci::Pipeline::Seed::Build do end end - context 'when keywords and pipeline source policy matches' do - possibilities = [%w[pushes push], - %w[web web], - %w[triggers trigger], - %w[schedules schedule], - %w[api api], - %w[external external]] - - context 'when using only' do - possibilities.each do |keyword, source| - context "when using keyword `#{keyword}` and source `#{source}`" do - let(:pipeline) do - build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) - end + context 'with source-keyword policy' do + using RSpec::Parameterized + + let(:pipeline) { build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) } + + context 'matches' do + where(:keyword, :source) do + [ + %w(pushes push), + %w(web web), + %w(triggers trigger), + %w(schedules schedule), + %w(api api), + %w(external external) + ] + end + with_them do + context 'using an only policy' do let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } } it { is_expected.to be_included } end - end - end - - context 'when using except' do - possibilities.each do |keyword, source| - context "when using keyword `#{keyword}` and source `#{source}`" do - let(:pipeline) do - build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) - end + context 'using an except policy' do let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } } it { is_expected.not_to be_included } end + + context 'using both only and except policies' do + let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } } + + it { is_expected.not_to be_included } + end end end - end - context 'when keywords and pipeline source does not match' do - possibilities = [%w[pushes web], - %w[web push], - %w[triggers schedule], - %w[schedules external], - %w[api trigger], - %w[external api]] - - context 'when using only' do - possibilities.each do |keyword, source| - context "when using keyword `#{keyword}` and source `#{source}`" do - let(:pipeline) do - build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) - end + context 'non-matches' do + where(:keyword, :source) do + %w(web trigger schedule api external).map { |source| ['pushes', source] } + + %w(push trigger schedule api external).map { |source| ['web', source] } + + %w(push web schedule api external).map { |source| ['triggers', source] } + + %w(push web trigger api external).map { |source| ['schedules', source] } + + %w(push web trigger schedule external).map { |source| ['api', source] } + + %w(push web trigger schedule api).map { |source| ['external', source] } + end + with_them do + context 'using an only policy' do let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } } it { is_expected.not_to be_included } end - end - end - - context 'when using except' do - possibilities.each do |keyword, source| - context "when using keyword `#{keyword}` and source `#{source}`" do - let(:pipeline) do - build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) - end + context 'using an except policy' do let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } } it { is_expected.to be_included } end + + context 'using both only and except policies' do + let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } } + + it { is_expected.not_to be_included } + end end end end diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index 0d03eef99c8..35250632e86 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -65,9 +65,9 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do result = stream.html expect(result).to eq( - "<span class=\"\">ヾ(´༎ຶД༎ຶ`)ノ</span><br/><span class=\"\"></span>"\ - "<span class=\"term-fg-green\">許功蓋</span><span class=\"\"></span><br/>"\ - "<span class=\"\"></span>") + "<span class=\"\">ヾ(´༎ຶД༎ຶ`)ノ<br/><span class=\"\"></span></span>"\ + "<span class=\"term-fg-green\">許功蓋</span><span class=\"\"><br/>"\ + "<span class=\"\"></span></span>") expect(result.encoding).to eq(Encoding.default_external) end end @@ -306,8 +306,8 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do shared_examples_for 'htmls' do it "returns html" do expect(stream.html).to eq( - "<span class=\"\">12</span><br/><span class=\"\">34</span><br/>"\ - "<span class=\"\">56</span>") + "<span class=\"\">12<br/><span class=\"\">34<br/>"\ + "<span class=\"\">56</span></span></span>") end it "returns html for last line only" do diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb index ed9a1e23529..1b5dd2538e0 100644 --- a/spec/lib/gitlab/data_builder/note_spec.rb +++ b/spec/lib/gitlab/data_builder/note_spec.rb @@ -38,9 +38,11 @@ describe Gitlab::DataBuilder::Note do end describe 'When asking for a note on issue' do + let(:label) { create(:label, project: project) } + let(:issue) do - create(:issue, created_at: fixed_time, updated_at: fixed_time, - project: project) + create(:labeled_issue, created_at: fixed_time, updated_at: fixed_time, + project: project, labels: [label]) end let(:note) do @@ -48,13 +50,16 @@ describe Gitlab::DataBuilder::Note do end it 'returns the note and issue-specific data' do + without_timestamps = lambda { |label| label.except('created_at', 'updated_at') } + hook_attrs = issue.reload.hook_attrs + expect(data).to have_key(:issue) - expect(data[:issue].except('updated_at')) - .to eq(issue.reload.hook_attrs.except('updated_at')) + expect(data[:issue].except('updated_at', 'labels')) + .to eq(hook_attrs.except('updated_at', 'labels')) expect(data[:issue]['updated_at']) - .to be >= issue.hook_attrs['updated_at'] - expect(data[:issue]['labels']) - .to eq(issue.hook_attrs['labels']) + .to be >= hook_attrs['updated_at'] + expect(data[:issue]['labels'].map(&without_timestamps)) + .to eq(hook_attrs['labels'].map(&without_timestamps)) end context 'with confidential issue' do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index e72fb9c6fbc..cceeae8afe6 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -2038,24 +2038,24 @@ describe Gitlab::Git::Repository, :seed_helper do end describe '#clean_stale_repository_files' do - let(:worktree_path) { File.join(repository_path, 'worktrees', 'delete-me') } + let(:worktree_id) { 'rebase-1' } + let(:gitlab_worktree_path) { File.join(repository_path, 'gitlab-worktree', worktree_id) } + let(:admin_dir) { File.join(repository_path, 'worktrees') } it 'cleans up the files' do - create_worktree = %W[git -C #{repository_path} worktree add --detach #{worktree_path} master] + create_worktree = %W[git -C #{repository_path} worktree add --detach #{gitlab_worktree_path} master] raise 'preparation failed' unless system(*create_worktree, err: '/dev/null') - FileUtils.touch(worktree_path, mtime: Time.now - 8.hours) + FileUtils.touch(gitlab_worktree_path, mtime: Time.now - 8.hours) # git rev-list --all will fail in git 2.16 if HEAD is pointing to a non-existent object, # but the HEAD must be 40 characters long or git will ignore it. - File.write(File.join(worktree_path, 'HEAD'), Gitlab::Git::BLANK_SHA) - - # git 2.16 fails with "fatal: bad object HEAD" - expect(rev_list_all).to be false + File.write(File.join(admin_dir, worktree_id, 'HEAD'), Gitlab::Git::BLANK_SHA) + expect(rev_list_all).to be(false) repository.clean_stale_repository_files - expect(rev_list_all).to be true - expect(File.exist?(worktree_path)).to be_falsey + expect(rev_list_all).to be(true) + expect(File.exist?(gitlab_worktree_path)).to be_falsey end def rev_list_all diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb index 6d6107ca3e7..ba6abba4e61 100644 --- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb @@ -223,6 +223,19 @@ describe Gitlab::GitalyClient::CommitService do end context 'when caching of the ref name is enabled' do + it 'caches negative entries' do + expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit).once.and_return(double(commit: nil)) + + commit = nil + 2.times do + ::Gitlab::GitalyClient.allow_ref_name_caching do + commit = described_class.new(repository).find_commit('master') + end + end + + expect(commit).to eq(nil) + end + it 'returns a cached commit' do expect_any_instance_of(Gitaly::CommitService::Stub).to receive(:find_commit).once.and_return(double(commit: commit_dbl)) diff --git a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb index 13cf52fd795..20842f55014 100644 --- a/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb +++ b/spec/lib/gitlab/graphql/authorize/authorize_resource_spec.rb @@ -34,12 +34,6 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do end end - describe '#authorized_find' do - it 'returns the object' do - expect(loading_resource.authorized_find).to eq(project) - end - end - describe '#authorized_find!' do it 'returns the object' do expect(loading_resource.authorized_find!).to eq(project) @@ -66,12 +60,6 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do end end - describe '#authorized_find' do - it 'returns `nil`' do - expect(loading_resource.authorized_find).to be_nil - end - end - describe '#authorized_find!' do it 'raises an error' do expect { loading_resource.authorize!(project) }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) @@ -101,6 +89,45 @@ describe Gitlab::Graphql::Authorize::AuthorizeResource do end end + context 'when the class does not define authorize' do + let(:fake_class) do + Class.new do + include Gitlab::Graphql::Authorize::AuthorizeResource + + attr_reader :user, :found_object + + def initialize(user, found_object) + @user, @found_object = user, found_object + end + + def find_object(*_args) + found_object + end + + def current_user + user + end + + def self.name + 'TestClass' + end + end + end + let(:error) { /#{fake_class.name} has no authorizations/ } + + describe '#authorized_find!' do + it 'raises a comprehensive error message' do + expect { loading_resource.authorized_find! }.to raise_error(error) + end + end + + describe '#authorized?' do + it 'raises a comprehensive error message' do + expect { loading_resource.authorized?(project) }.to raise_error(error) + end + end + end + describe '#authorize' do it 'adds permissions from subclasses to those of superclasses when used on classes' do base_class = Class.new do diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index f066c0e3813..b06d05c1c7f 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe Gitlab::HookData::IssueBuilder do - set(:issue) { create(:issue) } + set(:label) { create(:label) } + set(:issue) { create(:labeled_issue, labels: [label], project: label.project) } let(:builder) { described_class.new(issue) } describe '#build' do @@ -39,6 +40,7 @@ describe Gitlab::HookData::IssueBuilder do expect(data).to include(:human_time_estimate) expect(data).to include(:human_total_time_spent) expect(data).to include(:assignee_ids) + expect(data).to include('labels' => [label.hook_attrs]) end context 'when the issue has an image in the description' do diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb index c6a6042c65c..39cdd42088e 100644 --- a/spec/lib/gitlab/json_cache_spec.rb +++ b/spec/lib/gitlab/json_cache_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::JsonCache do let(:backend) { double('backend').as_null_object } let(:namespace) { 'geo' } let(:key) { 'foo' } - let(:expanded_key) { "#{namespace}:#{key}:#{Rails.version}" } + let(:expanded_key) { "#{namespace}:#{key}:#{Gitlab::VERSION}:#{Rails.version}" } set(:broadcast_message) { create(:broadcast_message) } subject(:cache) { described_class.new(namespace: namespace, backend: backend) } @@ -35,42 +35,68 @@ describe Gitlab::JsonCache do describe '#cache_key' do context 'when namespace is not defined' do - it 'expands out the key with Rails version' do - cache = described_class.new(cache_key_with_version: true) + context 'when cache_key_with_version is true' do + it 'expands out the key with GitLab, and Rails versions' do + cache = described_class.new(cache_key_with_version: true) - cache_key = cache.cache_key(key) + cache_key = cache.cache_key(key) - expect(cache_key).to eq("#{key}:#{Rails.version}") + expect(cache_key).to eq("#{key}:#{Gitlab::VERSION}:#{Rails.version}") + end end - end - context 'when cache_key_with_version is true' do - it 'expands out the key with namespace and Rails version' do - cache = described_class.new(namespace: namespace, cache_key_with_version: true) + context 'when cache_key_with_version is false' do + it 'returns the key' do + cache = described_class.new(namespace: nil, cache_key_with_version: false) - cache_key = cache.cache_key(key) + cache_key = cache.cache_key(key) - expect(cache_key).to eq("#{namespace}:#{key}:#{Rails.version}") + expect(cache_key).to eq(key) + end end end - context 'when cache_key_with_version is false' do - it 'expands out the key with namespace' do - cache = described_class.new(namespace: namespace, cache_key_with_version: false) + context 'when namespace is nil' do + context 'when cache_key_with_version is true' do + it 'expands out the key with GitLab, and Rails versions' do + cache = described_class.new(cache_key_with_version: true) + + cache_key = cache.cache_key(key) + + expect(cache_key).to eq("#{key}:#{Gitlab::VERSION}:#{Rails.version}") + end + end + + context 'when cache_key_with_version is false' do + it 'returns the key' do + cache = described_class.new(namespace: nil, cache_key_with_version: false) - cache_key = cache.cache_key(key) + cache_key = cache.cache_key(key) - expect(cache_key).to eq("#{namespace}:#{key}") + expect(cache_key).to eq(key) + end end end - context 'when namespace is nil, and cache_key_with_version is false' do - it 'returns the key' do - cache = described_class.new(namespace: nil, cache_key_with_version: false) + context 'when namespace is set' do + context 'when cache_key_with_version is true' do + it 'expands out the key with namespace and Rails version' do + cache = described_class.new(namespace: namespace, cache_key_with_version: true) - cache_key = cache.cache_key(key) + cache_key = cache.cache_key(key) - expect(cache_key).to eq(key) + expect(cache_key).to eq("#{namespace}:#{key}:#{Gitlab::VERSION}:#{Rails.version}") + end + end + + context 'when cache_key_with_version is false' do + it 'expands out the key with namespace' do + cache = described_class.new(namespace: namespace, cache_key_with_version: false) + + cache_key = cache.cache_key(key) + + expect(cache_key).to eq("#{namespace}:#{key}") + end end end end @@ -103,19 +129,52 @@ describe Gitlab::JsonCache do .with(expanded_key) .and_return(nil) + expect(ActiveSupport::JSON).not_to receive(:decode) expect(cache.read(key)).to be_nil end - context 'when the cached value is a boolean' do + context 'when the cached value is true' do it 'parses the cached value' do allow(backend).to receive(:read) .with(expanded_key) .and_return(true) + expect(ActiveSupport::JSON).to receive(:decode).with("true").and_call_original expect(cache.read(key, BroadcastMessage)).to eq(true) end end + context 'when the cached value is false' do + it 'parses the cached value' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return(false) + + expect(ActiveSupport::JSON).to receive(:decode).with("false").and_call_original + expect(cache.read(key, BroadcastMessage)).to eq(false) + end + end + + context 'when the cached value is a JSON true value' do + it 'parses the cached value' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return("true") + + expect(cache.read(key, BroadcastMessage)).to eq(true) + end + end + + context 'when the cached value is a JSON false value' do + it 'parses the cached value' do + allow(backend).to receive(:read) + .with(expanded_key) + .and_return("false") + + expect(cache.read(key, BroadcastMessage)).to eq(false) + end + end + context 'when the cached value is a hash' do it 'parses the cached value' do allow(backend).to receive(:read) diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb index 45369b91ed6..a7ea942960b 100644 --- a/spec/lib/gitlab/kubernetes_spec.rb +++ b/spec/lib/gitlab/kubernetes_spec.rb @@ -67,6 +67,30 @@ describe Gitlab::Kubernetes do end end + describe '#filter_by_legacy_label' do + let(:non_matching_pod) { kube_pod(environment_slug: 'production', project_slug: 'my-cool-app') } + + let(:non_matching_pod_2) do + kube_pod(environment_slug: 'production', project_slug: 'my-cool-app').tap do |pod| + pod['metadata']['labels']['app'] = 'production' + end + end + + let(:matching_pod) do + kube_pod.tap do |pod| + pod['metadata']['annotations'].delete('app.gitlab.com/env') + pod['metadata']['annotations'].delete('app.gitlab.com/app') + pod['metadata']['labels']['app'] = 'production' + end + end + + it 'returns matching labels' do + items = [non_matching_pod, non_matching_pod_2, matching_pod] + + expect(filter_by_legacy_label(items, 'my-cool-app', 'production')).to contain_exactly(matching_pod) + end + end + describe '#to_kubeconfig' do let(:token) { 'TOKEN' } let(:ca_pem) { 'PEM' } diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb index 5454d9c1af4..cbb862cb0c9 100644 --- a/spec/lib/gitlab/lets_encrypt/client_spec.rb +++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb @@ -116,42 +116,6 @@ describe ::Gitlab::LetsEncrypt::Client do end end - describe '#enabled?' do - subject { client.enabled? } - - context 'when terms of service are accepted' do - it { is_expected.to eq(true) } - - context "when private_key isn't present and database is read only" do - before do - allow(::Gitlab::Database).to receive(:read_only?).and_return(true) - end - - it 'returns false' do - expect(::Gitlab::CurrentSettings.lets_encrypt_private_key).to eq(nil) - - is_expected.to eq(false) - end - end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(pages_auto_ssl: false) - end - - it { is_expected.to eq(false) } - end - end - - context 'when terms of service are not accepted' do - before do - stub_application_setting(lets_encrypt_terms_of_service_accepted: false) - end - - it { is_expected.to eq(false) } - end - end - describe '#terms_of_service_url' do subject { client.terms_of_service_url } diff --git a/spec/lib/gitlab/lets_encrypt_spec.rb b/spec/lib/gitlab/lets_encrypt_spec.rb new file mode 100644 index 00000000000..674b114e9d3 --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ::Gitlab::LetsEncrypt do + include LetsEncryptHelpers + + before do + stub_lets_encrypt_settings + end + + describe '.enabled?' do + let(:project) { create(:project) } + let(:pages_domain) { create(:pages_domain, project: project) } + + subject { described_class.enabled?(pages_domain) } + + context 'when terms of service are accepted' do + it { is_expected.to eq(true) } + + context 'when feature flag is disabled' do + before do + stub_feature_flags(pages_auto_ssl: false) + end + + it { is_expected.to eq(false) } + end + end + + context 'when terms of service are not accepted' do + before do + stub_application_setting(lets_encrypt_terms_of_service_accepted: false) + end + + it { is_expected.to eq(false) } + end + + context 'when feature flag for project is disabled' do + before do + stub_feature_flags(pages_auto_ssl_for_project: false) + end + + it 'returns false' do + is_expected.to eq(false) + end + end + + context 'when domain has not project' do + let(:pages_domain) { create(:pages_domain) } + + it 'returns false' do + is_expected.to eq(false) + end + end + end +end diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb index 74157e5c67c..da263bc7523 100644 --- a/spec/lib/gitlab/search/found_blob_spec.rb +++ b/spec/lib/gitlab/search/found_blob_spec.rb @@ -3,14 +3,15 @@ require 'spec_helper' describe Gitlab::Search::FoundBlob do - describe 'parsing results' do - let(:project) { create(:project, :public, :repository) } + let(:project) { create(:project, :public, :repository) } + + describe 'parsing content results' do let(:results) { project.repository.search_files_by_content('feature', 'master') } let(:search_result) { results.first } subject { described_class.new(content_match: search_result, project: project) } - it "returns a valid FoundBlob" do + it 'returns a valid FoundBlob' do is_expected.to be_an described_class expect(subject.id).to be_nil expect(subject.path).to eq('CHANGELOG') @@ -21,13 +22,13 @@ describe Gitlab::Search::FoundBlob do expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") end - it "doesn't parses content if not needed" do + it 'does not parse content if not needed' do expect(subject).not_to receive(:parse_search_result) expect(subject.project_id).to eq(project.id) expect(subject.binary_filename).to eq('CHANGELOG') end - it "parses content only once when needed" do + it 'parses content only once when needed' do expect(subject).to receive(:parse_search_result).once.and_call_original expect(subject.filename).to eq('CHANGELOG') expect(subject.startline).to eq(188) @@ -119,7 +120,7 @@ describe Gitlab::Search::FoundBlob do end end - context "when filename has extension" do + context 'when filename has extension' do let(:search_result) { "master:CONTRIBUTE.md\x005\x00- [Contribute to GitLab](#contribute-to-gitlab)\n" } it { expect(subject.path).to eq('CONTRIBUTE.md') } @@ -127,7 +128,7 @@ describe Gitlab::Search::FoundBlob do it { expect(subject.basename).to eq('CONTRIBUTE') } end - context "when file under directory" do + context 'when file is under directory' do let(:search_result) { "master:a/b/c.md\x005\x00a b c\n" } it { expect(subject.path).to eq('a/b/c.md') } @@ -135,4 +136,28 @@ describe Gitlab::Search::FoundBlob do it { expect(subject.basename).to eq('a/b/c') } end end + + describe 'parsing title results' do + context 'when file is under directory' do + let(:path) { 'a/b/c.md' } + + subject { described_class.new(blob_filename: path, project: project, ref: 'master') } + + before do + allow(Gitlab::Git::Blob).to receive(:batch).and_return([ + Gitlab::Git::Blob.new(path: path) + ]) + end + + it { expect(subject.path).to eq('a/b/c.md') } + it { expect(subject.filename).to eq('a/b/c.md') } + it { expect(subject.basename).to eq('a/b/c') } + + context 'when filename has multiple extensions' do + let(:path) { 'a/b/c.whatever.md' } + + it { expect(subject.basename).to eq('a/b/c.whatever') } + end + end + end end diff --git a/spec/lib/gitlab/time_tracking_formatter_spec.rb b/spec/lib/gitlab/time_tracking_formatter_spec.rb new file mode 100644 index 00000000000..a85d418777f --- /dev/null +++ b/spec/lib/gitlab/time_tracking_formatter_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::TimeTrackingFormatter do + describe '#parse' do + subject { described_class.parse(duration_string) } + + context 'positive durations' do + let(:duration_string) { '3h 20m' } + + it { expect(subject).to eq(12_000) } + end + + context 'negative durations' do + let(:duration_string) { '-3h 20m' } + + it { expect(subject).to eq(-12_000) } + end + end + + describe '#output' do + let(:num_seconds) { 178_800 } + + subject { described_class.output(num_seconds) } + + context 'time_tracking_limit_to_hours setting is true' do + before do + stub_application_setting(time_tracking_limit_to_hours: true) + end + + it { expect(subject).to eq('49h 40m') } + end + + context 'time_tracking_limit_to_hours setting is false' do + before do + stub_application_setting(time_tracking_limit_to_hours: false) + end + + it { expect(subject).to eq('1w 1d 1h 40m') } + end + end +end diff --git a/spec/mailers/emails/pages_domains_spec.rb b/spec/mailers/emails/pages_domains_spec.rb index 2f594dbf9d1..eae83cd64d3 100644 --- a/spec/mailers/emails/pages_domains_spec.rb +++ b/spec/mailers/emails/pages_domains_spec.rb @@ -9,7 +9,7 @@ describe Emails::PagesDomains do set(:user) { project.creator } shared_examples 'a pages domain email' do - let(:test_recipient) { user } + let(:recipient) { user } it_behaves_like 'an email sent to a user' it_behaves_like 'an email sent from GitLab' diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 11af6837dab..fa1343fe759 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -45,7 +45,7 @@ describe Notify do context 'for a project' do shared_examples 'an assignee email' do - let(:test_recipient) { assignee } + let(:recipient) { assignee } it_behaves_like 'an email sent to a user' @@ -55,7 +55,7 @@ describe Notify do aggregate_failures do expect(sender.display_name).to eq(current_user.name) expect(sender.address).to eq(gitlab_sender) - expect(subject).to deliver_to(assignee.email) + expect(subject).to deliver_to(recipient.notification_email) end end end @@ -547,12 +547,13 @@ describe Notify do let(:host) { Gitlab.config.gitlab.host } context 'in discussion' do - set(:first_note) { create(:discussion_note_on_issue) } - set(:second_note) { create(:discussion_note_on_issue, in_reply_to: first_note) } - set(:third_note) { create(:discussion_note_on_issue, in_reply_to: second_note) } + set(:first_note) { create(:discussion_note_on_issue, project: project) } + set(:second_note) { create(:discussion_note_on_issue, in_reply_to: first_note, project: project) } + set(:third_note) { create(:discussion_note_on_issue, in_reply_to: second_note, project: project) } subject { described_class.note_issue_email(recipient.id, third_note.id) } + it_behaves_like 'an email sent to a user' it_behaves_like 'appearance header and footer enabled' it_behaves_like 'appearance header and footer not enabled' @@ -572,10 +573,11 @@ describe Notify do end context 'individual issue comments' do - set(:note) { create(:note_on_issue) } + set(:note) { create(:note_on_issue, project: project) } subject { described_class.note_issue_email(recipient.id, note.id) } + it_behaves_like 'an email sent to a user' it_behaves_like 'appearance header and footer enabled' it_behaves_like 'appearance header and footer not enabled' @@ -604,13 +606,13 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it 'has the correct subject and body' do - is_expected.to have_referable_subject(project_snippet, reply: true) + is_expected.to have_referable_subject(project_snippet, include_group: true, reply: true) is_expected.to have_body_text project_snippet_note.note end end describe 'project was moved' do - let(:test_recipient) { user } + let(:recipient) { user } subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } it_behaves_like 'an email sent to a user' @@ -811,7 +813,7 @@ describe Notify do it 'has the correct subject and body' do aggregate_failures do - is_expected.to have_subject("Re: #{project.name} | #{commit.title} (#{commit.short_id})") + is_expected.to have_subject("Re: #{project.name} | #{project.group.name} | #{commit.title} (#{commit.short_id})") is_expected.to have_body_text(commit.short_id) end end @@ -837,7 +839,7 @@ describe Notify do it 'has the correct subject and body' do aggregate_failures do - is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_referable_subject(merge_request, include_group: true, reply: true) is_expected.to have_body_text note_on_merge_request_path end end @@ -863,7 +865,7 @@ describe Notify do it 'has the correct subject and body' do aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_referable_subject(issue, include_group: true, reply: true) is_expected.to have_body_text(note_on_issue_path) end end @@ -929,7 +931,7 @@ describe Notify do it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject' do - is_expected.to have_subject "Re: #{project.name} | #{commit.title} (#{commit.short_id})" + is_expected.to have_subject "Re: #{project.name} | #{project.group.name} | #{commit.title} (#{commit.short_id})" end it 'contains a link to the commit' do @@ -957,7 +959,7 @@ describe Notify do it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject' do - is_expected.to have_referable_subject(merge_request, reply: true) + is_expected.to have_referable_subject(merge_request, include_group: true, reply: true) end it 'contains a link to the merge request note' do @@ -985,7 +987,7 @@ describe Notify do it_behaves_like 'appearance header and footer not enabled' it 'has the correct subject' do - is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_referable_subject(issue, include_group: true, reply: true) end it 'contains a link to the issue note' do diff --git a/spec/migrations/backport_enterprise_schema_spec.rb b/spec/migrations/backport_enterprise_schema_spec.rb new file mode 100644 index 00000000000..8d2d9d4953a --- /dev/null +++ b/spec/migrations/backport_enterprise_schema_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +require Rails.root.join('db', 'migrate', '20190402150158_backport_enterprise_schema.rb') + +describe BackportEnterpriseSchema, :migration, schema: 20190329085614 do + include MigrationsHelpers + + def drop_if_exists(table) + active_record_base.connection.drop_table(table) if active_record_base.connection.table_exists?(table) + end + + describe '#up' do + it 'creates new EE tables' do + migrate! + + expect(active_record_base.connection.table_exists?(:epics)).to be true + expect(active_record_base.connection.table_exists?(:geo_nodes)).to be true + end + + context 'missing EE columns' do + before do + drop_if_exists(:epics) + + active_record_base.connection.create_table "epics" do |t| + t.integer :group_id, null: false, index: true + t.integer :author_id, null: false, index: true + end + end + + after do + drop_if_exists(:epics) + end + + it 'flags an error' do + expect { migrate! }.to raise_error(/Your database is missing.*that is present for GitLab EE/) + end + end + end +end diff --git a/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb new file mode 100644 index 00000000000..3c880c6f5fd --- /dev/null +++ b/spec/migrations/enqueue_reset_merge_status_second_run_spec.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190620112608_enqueue_reset_merge_status_second_run.rb') + +describe EnqueueResetMergeStatusSecondRun, :migration, :sidekiq do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } + let(:merge_requests) { table(:merge_requests) } + + def create_merge_request(id, extra_params = {}) + params = { + id: id, + target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: "mr name#{id}" + }.merge(extra_params) + + merge_requests.create!(params) + end + + it 'correctly schedules background migrations' do + create_merge_request(1, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(2, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(3, state: 'opened', merge_status: 'can_be_merged') + create_merge_request(4, state: 'merged', merge_status: 'can_be_merged') + create_merge_request(5, state: 'opened', merge_status: 'unchecked') + + stub_const("#{described_class.name}::BATCH_SIZE", 2) + + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(5.minutes, 1, 2) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(10.minutes, 3, 4) + + expect(described_class::MIGRATION) + .to be_scheduled_delayed_migration(15.minutes, 5, 5) + + expect(BackgroundMigrationWorker.jobs.size).to eq(3) + end + end + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index f8dc1541dd3..ab6f6dfe720 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -354,36 +354,6 @@ describe ApplicationSetting do end end - describe 'setting Sentry DSNs' do - context 'server DSN' do - it 'strips leading and trailing whitespace' do - subject.update(sentry_dsn: ' http://test ') - - expect(subject.sentry_dsn).to eq('http://test') - end - - it 'handles nil values' do - subject.update(sentry_dsn: nil) - - expect(subject.sentry_dsn).to be_nil - end - end - - context 'client-side DSN' do - it 'strips leading and trailing whitespace' do - subject.update(clientside_sentry_dsn: ' http://test ') - - expect(subject.clientside_sentry_dsn).to eq('http://test') - end - - it 'handles nil values' do - subject.update(clientside_sentry_dsn: nil) - - expect(subject.clientside_sentry_dsn).to be_nil - end - end - end - describe '#disabled_oauth_sign_in_sources=' do before do allow(Devise).to receive(:omniauth_providers).and_return([:github]) diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 4d53e4aad8a..020ada3c47a 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -48,14 +48,14 @@ describe BroadcastMessage do expect(described_class.current).to be_empty end - it 'caches the output of the query' do + it 'caches the output of the query for two weeks' do create(:broadcast_message) - expect(described_class).to receive(:current_and_future_messages).and_call_original.once + expect(described_class).to receive(:current_and_future_messages).and_call_original.twice described_class.current - Timecop.travel(1.year) do + Timecop.travel(3.weeks) do described_class.current end end diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index d7b81caddf5..aee43025288 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -88,23 +88,8 @@ describe Ci::PipelineSchedule do describe '#set_next_run_at' do let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) } - let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) } - - let(:expected_next_run_at) do - Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name) - .next_time_from(ideal_next_run_at) - end - - let(:cron_worker_next_run_at) do - Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name) - .next_time_from(Time.zone.now) - end - - context 'when creates new pipeline schedule' do - it 'updates next_run_at automatically' do - expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at) - end - end + let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_from, Time.zone.now) } + let(:cron_worker_next_run_at) { pipeline_schedule.send(:cron_worker_next_run_from, Time.zone.now) } context 'when PipelineScheduleWorker runs at a specific interval' do before do @@ -129,7 +114,7 @@ describe Ci::PipelineSchedule do let(:pipeline_schedule) { create(:ci_pipeline_schedule, :every_minute) } it "updates next_run_at to the sidekiq worker's execution time" do - Timecop.freeze(2019, 06, 19, 12, 00) do + Timecop.freeze(Time.parse("2019-06-01 12:18:00+0000")) do expect(pipeline_schedule.next_run_at).to eq(cron_worker_next_run_at) end end @@ -157,9 +142,8 @@ describe Ci::PipelineSchedule do let(:new_cron) { '0 0 1 1 *' } it 'updates next_run_at automatically' do - pipeline_schedule.update!(cron: new_cron) - - expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at) + expect { pipeline_schedule.update!(cron: new_cron) } + .to change { pipeline_schedule.next_run_at } end end end @@ -167,21 +151,24 @@ describe Ci::PipelineSchedule do describe '#schedule_next_run!' do let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) } - context 'when reschedules after 10 days from now' do - let(:future_time) { 10.days.from_now } - let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_at) } + before do + pipeline_schedule.update_column(:next_run_at, nil) + end - let(:expected_next_run_at) do - Gitlab::Ci::CronParser.new(Settings.cron_jobs['pipeline_schedule_worker']['cron'], Time.zone.name) - .next_time_from(ideal_next_run_at) + it 'updates next_run_at' do + expect { pipeline_schedule.schedule_next_run! } + .to change { pipeline_schedule.next_run_at } + end + + context 'when record is invalid' do + before do + allow(pipeline_schedule).to receive(:save!) { raise ActiveRecord::RecordInvalid.new(pipeline_schedule) } end - it 'points to proper next_run_at' do - Timecop.freeze(future_time) do - pipeline_schedule.schedule_next_run! + it 'nullifies the next run at' do + pipeline_schedule.schedule_next_run! - expect(pipeline_schedule.next_run_at).to eq(expected_next_run_at) - end + expect(pipeline_schedule.next_run_at).to be_nil end end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 0fa5d031736..471769e4aab 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -2,13 +2,11 @@ require 'spec_helper' -describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do +describe Clusters::Platforms::Kubernetes do include KubernetesHelpers - include ReactiveCachingHelpers it { is_expected.to belong_to(:cluster) } it { is_expected.to be_kind_of(Gitlab::Kubernetes) } - it { is_expected.to be_kind_of(ReactiveCaching) } it { is_expected.to respond_to :ca_pem } it { is_expected.to validate_exclusion_of(:namespace).in_array(%w(gitlab-managed-apps)) } @@ -397,17 +395,16 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end describe '#terminals' do - subject { service.terminals(environment) } + subject { service.terminals(environment, pods: pods) } let!(:cluster) { create(:cluster, :project, platform_kubernetes: service) } let(:project) { cluster.project } let(:service) { create(:cluster_platform_kubernetes, :configured) } let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") } + let(:pods) { [{ "bad" => "pod" }] } context 'with invalid pods' do it 'returns no terminals' do - stub_reactive_cache(service, pods: [{ "bad" => "pod" }]) - is_expected.to be_empty end end @@ -416,13 +413,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(project), project_slug: project.full_path_slug) } let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") } let(:terminals) { kube_terminals(service, pod) } - - before do - stub_reactive_cache( - service, - pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] - ) - end + let(:pods) { [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] } it 'returns terminals' do is_expected.to eq(terminals + terminals) @@ -437,16 +428,18 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching end end - describe '#calculate_reactive_cache' do - subject { service.calculate_reactive_cache } - - let!(:cluster) { create(:cluster, :project, enabled: enabled, platform_kubernetes: service) } + describe '#calculate_reactive_cache_for' do let(:service) { create(:cluster_platform_kubernetes, :configured) } - let(:enabled) { true } - let(:namespace) { cluster.kubernetes_namespace_for(cluster.project) } + let(:pod) { kube_pod } + let(:namespace) { pod["metadata"]["namespace"] } + let(:environment) { instance_double(Environment, deployment_namespace: namespace) } - context 'when cluster is disabled' do - let(:enabled) { false } + subject { service.calculate_reactive_cache_for(environment) } + + context 'when the kubernetes integration is disabled' do + before do + allow(service).to receive(:enabled?).and_return(false) + end it { is_expected.to be_nil } end @@ -457,7 +450,7 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching stub_kubeclient_deployments(namespace) end - it { is_expected.to include(pods: [kube_pod]) } + it { is_expected.to include(pods: [pod]) } end context 'when kubernetes responds with 500s' do @@ -477,34 +470,5 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { is_expected.to include(pods: []) } end - - context 'when the cluster is not project level' do - let(:cluster) { create(:cluster, :group, platform_kubernetes: service) } - - it { is_expected.to include(pods: []) } - end - end - - describe '#update_kubernetes_namespace' do - let(:cluster) { create(:cluster, :provided_by_gcp) } - let(:platform) { cluster.platform } - - context 'when namespace is updated' do - it 'calls ConfigureWorker' do - expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once - - platform.namespace = 'new-namespace' - platform.save - end - end - - context 'when namespace is not updated' do - it 'does not call ConfigureWorker' do - expect(ClusterConfigureWorker).not_to receive(:perform_async) - - platform.username = "new-username" - platform.save - end - end end end diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index 1dceef3fc00..a433878f3bc 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -380,12 +380,12 @@ describe Deployment do end end - describe '#cluster' do + describe '#deployment_platform_cluster' do let(:deployment) { create(:deployment) } let(:project) { deployment.project } let(:environment) { deployment.environment } - subject { deployment.cluster } + subject { deployment.deployment_platform_cluster } before do expect(project).to receive(:deployment_platform) diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 379dda1f5c4..fe4d64818b4 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -2,10 +2,14 @@ require 'spec_helper' -describe Environment do +describe Environment, :use_clean_rails_memory_store_caching do + include ReactiveCachingHelpers + let(:project) { create(:project, :stubbed_repository) } subject(:environment) { create(:environment, project: project) } + it { is_expected.to be_kind_of(ReactiveCaching) } + it { is_expected.to belong_to(:project).required } it { is_expected.to have_many(:deployments) } @@ -573,32 +577,65 @@ describe Environment do describe '#terminals' do subject { environment.terminals } - context 'when the environment has terminals' do + before do + allow(environment).to receive(:deployment_platform).and_return(double) + end + + context 'reactive cache is empty' do before do - allow(environment).to receive(:has_terminals?).and_return(true) + stub_reactive_cache(environment, nil) end - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } + it { is_expected.to be_nil } + end + + context 'reactive cache has pod data' do + let(:cache_data) { Hash(pods: %w(pod1 pod2)) } + + before do + stub_reactive_cache(environment, cache_data) + end - it 'returns the terminals from the deployment service' do - expect(environment.deployment_platform) - .to receive(:terminals).with(environment) - .and_return(:fake_terminals) + it 'retrieves terminals from the deployment platform' do + expect(environment.deployment_platform) + .to receive(:terminals).with(environment, cache_data) + .and_return(:fake_terminals) - is_expected.to eq(:fake_terminals) - end + is_expected.to eq(:fake_terminals) end end + end + + describe '#calculate_reactive_cache' do + let(:cluster) { create(:cluster, :project, :provided_by_user) } + let(:project) { cluster.project } + let(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, :success, environment: environment) } + + subject { environment.calculate_reactive_cache } + + it 'returns cache data from the deployment platform' do + expect(environment.deployment_platform).to receive(:calculate_reactive_cache_for) + .with(environment).and_return(pods: %w(pod1 pod2)) + + is_expected.to eq(pods: %w(pod1 pod2)) + end - context 'when the environment does not have terminals' do + context 'environment does not have terminals available' do before do allow(environment).to receive(:has_terminals?).and_return(false) end it { is_expected.to be_nil } end + + context 'project is pending deletion' do + before do + allow(environment.project).to receive(:pending_delete?).and_return(true) + end + + it { is_expected.to be_nil } + end end describe '#has_metrics?' do diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index 806b4f61bd8..28630f7d3fe 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -158,7 +158,7 @@ describe InternalId do before do described_class.reset_column_information # Project factory will also call the current_version - expect(ActiveRecord::Migrator).to receive(:current_version).twice.and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) + expect(ActiveRecord::Migrator).to receive(:current_version).at_least(:once).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) end it 'does not reset any of the iids' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index a5c7e9db2a1..d5b016dc8f6 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -862,4 +862,13 @@ describe Issue do end end end + + describe "#labels_hook_attrs" do + let(:label) { create(:label) } + let(:issue) { create(:labeled_issue, labels: [label]) } + + it "returns a list of label hook attributes" do + expect(issue.labels_hook_attrs).to eq([label.hook_attrs]) + end + end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c6251326c22..a2547755510 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1996,57 +1996,6 @@ describe MergeRequest do end end - describe '#check_if_can_be_merged' do - let(:project) { create(:project, only_allow_merge_if_pipeline_succeeds: true) } - - shared_examples 'checking if can be merged' do - context 'when it is not broken and has no conflicts' do - before do - allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?).and_return(true) - end - - it 'is marked as mergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('can_be_merged') - end - end - - context 'when broken' do - before do - allow(subject).to receive(:broken?) { true } - allow(project.repository).to receive(:can_be_merged?).and_return(false) - end - - it 'becomes unmergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') - end - end - - context 'when it has conflicts' do - before do - allow(subject).to receive(:broken?) { false } - allow(project.repository).to receive(:can_be_merged?).and_return(false) - end - - it 'becomes unmergeable' do - expect { subject.check_if_can_be_merged }.to change { subject.merge_status }.to('cannot_be_merged') - end - end - end - - context 'when merge_status is unchecked' do - subject { create(:merge_request, source_project: project, merge_status: :unchecked) } - - it_behaves_like 'checking if can be merged' - end - - context 'when merge_status is unchecked' do - subject { create(:merge_request, source_project: project, merge_status: :cannot_be_merged_recheck) } - - it_behaves_like 'checking if can be merged' - end - end - describe '#mergeable?' do let(:project) { create(:project) } @@ -2060,7 +2009,7 @@ describe MergeRequest do it 'return true if #mergeable_state? is true and the MR #can_be_merged? is true' do allow(subject).to receive(:mergeable_state?) { true } - expect(subject).to receive(:check_if_can_be_merged) + expect(subject).to receive(:check_mergeability) expect(subject).to receive(:can_be_merged?) { true } expect(subject.mergeable?).to be_truthy @@ -2074,7 +2023,7 @@ describe MergeRequest do it 'checks if merge request can be merged' do allow(subject).to receive(:mergeable_ci_state?) { true } - expect(subject).to receive(:check_if_can_be_merged) + expect(subject).to receive(:check_mergeability) subject.mergeable? end @@ -2175,7 +2124,7 @@ describe MergeRequest do allow(subject).to receive(:head_pipeline) { nil } end - it { expect(subject.mergeable_ci_state?).to be_truthy } + it { expect(subject.mergeable_ci_state?).to be_falsey } end end @@ -3142,38 +3091,6 @@ describe MergeRequest do end end - describe '#mergeable_to_ref?' do - it 'returns true when merge request is mergeable' do - subject = create(:merge_request) - - expect(subject.mergeable_to_ref?).to be(true) - end - - it 'returns false when merge request is already merged' do - subject = create(:merge_request, :merged) - - expect(subject.mergeable_to_ref?).to be(false) - end - - it 'returns false when merge request is closed' do - subject = create(:merge_request, :closed) - - expect(subject.mergeable_to_ref?).to be(false) - end - - it 'returns false when merge request is work in progress' do - subject = create(:merge_request, title: 'WIP: The feature') - - expect(subject.mergeable_to_ref?).to be(false) - end - - it 'returns false when merge request has no commits' do - subject = create(:merge_request, source_branch: 'empty-branch', target_branch: 'master') - - expect(subject.mergeable_to_ref?).to be(false) - end - end - describe '#merge_participants' do it 'contains author' do expect(subject.merge_participants).to eq([subject.author]) diff --git a/spec/models/namespace/aggregation_schedule_spec.rb b/spec/models/namespace/aggregation_schedule_spec.rb new file mode 100644 index 00000000000..5ba7547ff4d --- /dev/null +++ b/spec/models/namespace/aggregation_schedule_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Namespace::AggregationSchedule, type: :model do + it { is_expected.to belong_to :namespace } +end diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb new file mode 100644 index 00000000000..f6fb5af5aae --- /dev/null +++ b/spec/models/namespace/root_storage_statistics_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Namespace::RootStorageStatistics, type: :model do + it { is_expected.to belong_to :namespace } + it { is_expected.to have_one(:route).through(:namespace) } + + it { is_expected.to delegate_method(:all_projects).to(:namespace) } +end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index d80183af33e..30e49cf204f 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -15,6 +15,8 @@ describe Namespace do it { is_expected.to have_many :project_statistics } it { is_expected.to belong_to :parent } it { is_expected.to have_many :children } + it { is_expected.to have_one :root_storage_statistics } + it { is_expected.to have_one :aggregation_schedule } end describe 'validations' do diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index 4fb7b71a3c7..661957cf08b 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -479,4 +479,30 @@ describe PagesDomain do end end end + + describe '.need_auto_ssl_renewal' do + subject { described_class.need_auto_ssl_renewal } + + let!(:domain_with_user_provided_certificate) { create(:pages_domain) } + let!(:domain_with_expired_user_provided_certificate) do + create(:pages_domain, :with_expired_certificate) + end + let!(:domain_with_user_provided_certificate_and_auto_ssl) do + create(:pages_domain, auto_ssl_enabled: true) + end + + let!(:domain_with_gitlab_provided_certificate) { create(:pages_domain, :letsencrypt) } + let!(:domain_with_expired_gitlab_provided_certificate) do + create(:pages_domain, :letsencrypt, :with_expired_certificate) + end + + it 'contains only domains needing verification' do + is_expected.to( + contain_exactly( + domain_with_user_provided_certificate_and_auto_ssl, + domain_with_expired_gitlab_provided_certificate + ) + ) + end + end end diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb index 1c434b25205..3d875bc49e7 100644 --- a/spec/models/project_services/teamcity_service_spec.rb +++ b/spec/models/project_services/teamcity_service_spec.rb @@ -7,10 +7,11 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do include StubRequests let(:teamcity_url) { 'http://gitlab.com/teamcity' } + let(:project) { create(:project) } subject(:service) do described_class.create( - project: create(:project), + project: project, properties: { teamcity_url: teamcity_url, username: 'mic', @@ -207,6 +208,97 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do end end + describe '#execute' do + context 'when push' do + let(:data) do + { + object_kind: 'push', + ref: 'refs/heads/dev-123_branch', + after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e', + total_commits_count: 1 + } + end + + it 'handles push request correctly' do + stub_post_to_build_queue(branch: 'dev-123_branch') + + expect(service.execute(data)).to include('Ok') + end + + it 'returns nil when ref is blank' do + data[:after] = Gitlab::Git::BLANK_SHA + + expect(service.execute(data)).to be_nil + end + + it 'returns nil when there is no content' do + data[:total_commits_count] = 0 + + expect(service.execute(data)).to be_nil + end + + it 'returns nil when a merge request is opened for the same ref' do + create(:merge_request, source_project: project, source_branch: 'dev-123_branch') + + expect(service.execute(data)).to be_nil + end + end + + context 'when merge_request' do + let(:data) do + { + object_kind: 'merge_request', + ref: 'refs/heads/dev-123_branch', + after: '0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e', + total_commits_count: 1, + object_attributes: { + state: 'opened', + source_branch: 'dev-123_branch', + merge_status: 'unchecked' + } + } + end + + it 'handles merge request correctly' do + stub_post_to_build_queue(branch: 'dev-123_branch') + + expect(service.execute(data)).to include('Ok') + end + + it 'returns nil when merge request is not opened' do + data[:object_attributes][:state] = 'closed' + + expect(service.execute(data)).to be_nil + end + + it 'returns nil unless merge request is marked as unchecked' do + data[:object_attributes][:merge_status] = 'can_be_merged' + + expect(service.execute(data)).to be_nil + end + end + + it 'returns nil when event is not supported' do + data = { object_kind: 'foo' } + + expect(service.execute(data)).to be_nil + end + end + + def stub_post_to_build_queue(branch:) + teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/buildQueue' + body ||= %Q(<build branchName=\"#{branch}\"><buildType id=\"foo\"/></build>) + auth = %w(mic password) + + stub_full_request(teamcity_full_url, method: :post).with( + basic_auth: auth, + body: body, + headers: { + 'Content-Type' => 'application/xml' + } + ).to_return(status: 200, body: 'Ok', headers: {}) + end + def stub_request(status: 200, body: nil, build_status: 'success') teamcity_full_url = 'http://gitlab.com/teamcity/httpAuth/app/rest/builds/branch:unspecified:any,revision:123' auth = %w(mic password) diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 8b503777443..f9c8b42afa8 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -65,7 +65,7 @@ describe API::Branches do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { get api(route, current_user) } end end @@ -175,7 +175,7 @@ describe API::Branches do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { get api(route, current_user) } end end @@ -337,7 +337,7 @@ describe API::Branches do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { put api(route, current_user) } end end @@ -471,7 +471,7 @@ describe API::Branches do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { put api(route, current_user) } end end @@ -547,7 +547,7 @@ describe API::Branches do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { post api(route, current_user) } end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index f104da6ebba..3df5d9412f8 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -736,7 +736,7 @@ describe API::Commits do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { get api(route, current_user) } end end @@ -825,7 +825,7 @@ describe API::Commits do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { get api(route, current_user) } end end @@ -968,7 +968,7 @@ describe API::Commits do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { get api(route, current_user) } end end @@ -1067,7 +1067,7 @@ describe API::Commits do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { get api(route, current_user) } end end @@ -1169,7 +1169,7 @@ describe API::Commits do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { post api(route, current_user), params: { branch: 'master' } } end end @@ -1324,7 +1324,7 @@ describe API::Commits do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { post api(route, current_user), params: { branch: branch } } end end @@ -1435,7 +1435,7 @@ describe API::Commits do context 'when repository is disabled' do include_context 'disabled repository' - it_behaves_like '403 response' do + it_behaves_like '404 response' do let(:request) { post api(route, current_user), params: { note: 'My comment' } } end end diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 8fc7fdc8632..745f3c55ac8 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -34,6 +34,47 @@ describe API::Environments do expect(json_response.first['project'].keys).to contain_exactly(*project_data_keys) expect(json_response.first).not_to have_key("last_deployment") end + + context 'when filtering' do + let!(:environment2) { create(:environment, project: project) } + + it 'returns environment by name' do + get api("/projects/#{project.id}/environments?name=#{environment.name}", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.first['name']).to eq(environment.name) + end + + it 'returns no environment by non-existent name' do + get api("/projects/#{project.id}/environments?name=test", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(0) + end + + it 'returns environments by name_like' do + get api("/projects/#{project.id}/environments?search=envir", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + end + + it 'returns no environment by non-existent name_like' do + get api("/projects/#{project.id}/environments?search=test", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(0) + end + end end context 'as non member' do diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index ed907841bd8..1c69f5dbb67 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -226,10 +226,8 @@ describe API::Helpers do allow_any_instance_of(self.class).to receive(:rack_response) allow(Gitlab::Sentry).to receive(:enabled?).and_return(true) - stub_application_setting( - sentry_enabled: true, - sentry_dsn: "dummy://12345:67890@sentry.localdomain/sentry/42" - ) + stub_sentry_settings + configure_sentry Raven.client.configuration.encoding = 'json' end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 9f9180bc8c9..76d093e0774 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -1546,52 +1546,80 @@ describe API::MergeRequests do end end - describe "PUT /projects/:id/merge_requests/:merge_request_iid/merge_to_ref" do - let(:pipeline) { create(:ci_pipeline_without_jobs) } + describe "GET /projects/:id/merge_requests/:merge_request_iid/merge_ref" do + before do + merge_request.mark_as_unchecked! + end + + let(:merge_request_iid) { merge_request.iid } + let(:url) do - "/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge_to_ref" + "/projects/#{project.id}/merge_requests/#{merge_request_iid}/merge_ref" end it 'returns the generated ID from the merge service in case of success' do - put api(url, user), params: { merge_commit_message: 'Custom message' } - - commit = project.commit(json_response['commit_id']) + get api(url, user) expect(response).to have_gitlab_http_status(200) - expect(json_response['commit_id']).to be_present - expect(commit.message).to eq('Custom message') + expect(json_response['commit_id']).to eq(merge_request.merge_ref_head.sha) end - it "returns 400 if branch can't be merged" do - merge_request.update!(state: 'merged') + context 'when merge-ref is not synced with merge status' do + before do + merge_request.update!(merge_status: 'cannot_be_merged') + end - put api(url, user) + it 'returns 200 if MR can be merged' do + get api(url, user) - expect(response).to have_gitlab_http_status(400) - expect(json_response['message']) - .to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}") + expect(response).to have_gitlab_http_status(200) + expect(json_response['commit_id']).to eq(merge_request.merge_ref_head.sha) + end + + it 'returns 400 if MR cannot be merged' do + expect_next_instance_of(MergeRequests::MergeToRefService) do |merge_request| + expect(merge_request).to receive(:execute) { { status: :failed } } + end + + get api(url, user) + + expect(response).to have_gitlab_http_status(400) + expect(json_response['message']).to eq('Merge request is not mergeable') + end end - it 'returns 403 if user has no permissions to merge to the ref' do - user2 = create(:user) - project.add_reporter(user2) + context 'when user has no access to the MR' do + let(:project) { create(:project, :private) } + let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - put api(url, user2) + it 'returns 404' do + project.add_guest(user) - expect(response).to have_gitlab_http_status(403) - expect(json_response['message']).to eq('403 Forbidden') + get api(url, user) + + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 Not found') + end end - it 'returns 404 for an invalid merge request IID' do - put api("/projects/#{project.id}/merge_requests/12345/merge_to_ref", user) + context 'when invalid merge request IID' do + let(:merge_request_iid) { '12345' } - expect(response).to have_gitlab_http_status(404) + it 'returns 404' do + get api(url, user) + + expect(response).to have_gitlab_http_status(404) + end end - it "returns 404 if the merge request id is used instead of iid" do - put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/merge", user) + context 'when merge request ID is used instead IID' do + let(:merge_request_iid) { merge_request.id } - expect(response).to have_gitlab_http_status(404) + it 'returns 404' do + get api(url, user) + + expect(response).to have_gitlab_http_status(404) + end end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index a170bb14144..ff4228c9b99 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -1,12 +1,12 @@ require 'spec_helper' -# user GET /u/:username/ -# user_groups GET /u/:username/groups(.:format) -# user_projects GET /u/:username/projects(.:format) -# user_contributed_projects GET /u/:username/contributed(.:format) -# user_snippets GET /u/:username/snippets(.:format) -# user_calendar GET /u/:username/calendar(.:format) -# user_calendar_activities GET /u/:username/calendar_activities(.:format) +# user GET /users/:username/ +# user_groups GET /users/:username/groups(.:format) +# user_projects GET /users/:username/projects(.:format) +# user_contributed_projects GET /users/:username/contributed(.:format) +# user_snippets GET /users/:username/snippets(.:format) +# user_calendar GET /users/:username/calendar(.:format) +# user_calendar_activities GET /users/:username/calendar_activities(.:format) describe UsersController, "routing" do it "to #show" do allow_any_instance_of(::Constraints::UserUrlConstrainer).to receive(:matches?).and_return(true) @@ -37,22 +37,6 @@ describe UsersController, "routing" do it "to #calendar_activities" do expect(get("/users/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User') end - - describe 'redirect alias routes' do - include RSpec::Rails::RequestExampleGroup - - it '/u/user1 redirects to /user1' do - expect(get("/u/user1")).to redirect_to('/user1') - end - - it '/u/user1/groups redirects to /user1/groups' do - expect(get("/u/user1/groups")).to redirect_to('/users/user1/groups') - end - - it '/u/user1/projects redirects to /user1/projects' do - expect(get("/u/user1/projects")).to redirect_to('/users/user1/projects') - end - end end # search GET /search(.:format) search#show diff --git a/spec/services/clusters/gcp/finalize_creation_service_spec.rb b/spec/services/clusters/gcp/finalize_creation_service_spec.rb index 2664649df47..5f91acb8e84 100644 --- a/spec/services/clusters/gcp/finalize_creation_service_spec.rb +++ b/spec/services/clusters/gcp/finalize_creation_service_spec.rb @@ -19,10 +19,6 @@ describe Clusters::Gcp::FinalizeCreationService, '#execute' do subject { described_class.new.execute(provider) } - before do - allow(ClusterConfigureWorker).to receive(:perform_async) - end - shared_examples 'success' do it 'configures provider and kubernetes' do subject @@ -42,12 +38,6 @@ describe Clusters::Gcp::FinalizeCreationService, '#execute' do expect(platform.password).to eq(password) expect(platform.token).to eq(token) end - - it 'calls ClusterConfigureWorker in a ascync fashion' do - expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id) - - subject - end end shared_examples 'error' do diff --git a/spec/services/clusters/update_service_spec.rb b/spec/services/clusters/update_service_spec.rb index 21b37f88fd8..3ee45375dca 100644 --- a/spec/services/clusters/update_service_spec.rb +++ b/spec/services/clusters/update_service_spec.rb @@ -39,7 +39,6 @@ describe Clusters::UpdateService do end before do - allow(ClusterConfigureWorker).to receive(:perform_async) stub_kubeclient_get_namespace('https://kubernetes.example.com', namespace: 'my-namespace') end diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb index 0ac23050caf..61f99f82a76 100644 --- a/spec/services/merge_requests/merge_to_ref_service_spec.rb +++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb @@ -72,10 +72,6 @@ describe MergeRequests::MergeToRefService do let(:merge_request) { create(:merge_request, :simple) } let(:project) { merge_request.project } - before do - project.add_maintainer(user) - end - describe '#execute' do let(:service) do described_class.new(project, user, commit_message: 'Awesome message', @@ -92,6 +88,12 @@ describe MergeRequests::MergeToRefService do it_behaves_like 'successfully evaluates pre-condition checks' context 'commit history comparison with regular MergeService' do + before do + # The merge service needs an authorized user while merge-to-ref + # doesn't. + project.add_maintainer(user) + end + let(:merge_ref_service) do described_class.new(project, user, {}) end @@ -104,12 +106,18 @@ describe MergeRequests::MergeToRefService do it_behaves_like 'MergeService for target ref' end - context 'when merge commit with squash', :quarantine do + context 'when merge commit with squash' do before do - merge_request.update!(squash: true, source_branch: 'master', target_branch: 'feature') + merge_request.update!(squash: true) end it_behaves_like 'MergeService for target ref' + + it 'does not squash before merging' do + expect(MergeRequests::SquashService).not_to receive(:new) + + process_merge_to_ref + end end end @@ -136,9 +144,9 @@ describe MergeRequests::MergeToRefService do let(:merge_method) { :merge } it 'returns error' do - allow(merge_request).to receive(:mergeable_to_ref?) { false } + allow(project).to receive_message_chain(:repository, :merge_to_ref) { nil } - error_message = "Merge request is not mergeable to #{merge_request.merge_ref_path}" + error_message = 'Conflicts detected during merge' result = service.execute(merge_request) @@ -170,28 +178,5 @@ describe MergeRequests::MergeToRefService do it { expect(todo).not_to be_done } end - - context 'when merge request is WIP state' do - it 'fails to merge' do - merge_request = create(:merge_request, title: 'WIP: The feature') - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq("Merge request is not mergeable to #{merge_request.merge_ref_path}") - end - end - - it 'returns error when user has no authorization to admin the merge request' do - unauthorized_user = create(:user) - project.add_reporter(unauthorized_user) - - service = described_class.new(project, unauthorized_user) - - result = service.execute(merge_request) - - expect(result[:status]).to eq(:error) - expect(result[:message]).to eq('You are not allowed to merge to this ref') - end end end diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb new file mode 100644 index 00000000000..6efece64092 --- /dev/null +++ b/spec/services/merge_requests/mergeability_check_service_spec.rb @@ -0,0 +1,285 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequests::MergeabilityCheckService do + shared_examples_for 'unmergeable merge request' do + it 'updates or keeps merge status as cannot_be_merged' do + subject + + expect(merge_request.merge_status).to eq('cannot_be_merged') + end + + it 'does not change the merge ref HEAD' do + expect { subject }.not_to change(merge_request, :merge_ref_head) + end + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result).to be_error + end + end + + shared_examples_for 'mergeable merge request' do + it 'updates or keeps merge status as can_be_merged' do + subject + + expect(merge_request.merge_status).to eq('can_be_merged') + end + + it 'updates the merge ref' do + expect { subject }.to change(merge_request, :merge_ref_head).from(nil) + end + + it 'returns ServiceResponse.success' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result).to be_success + end + + it 'ServiceResponse has merge_ref_head payload' do + result = subject + + expect(result.payload.keys).to contain_exactly(:merge_ref_head) + expect(result.payload[:merge_ref_head].keys) + .to contain_exactly(:commit_id, :target_id, :source_id) + end + end + + describe '#execute' do + let(:project) { create(:project, :repository) } + let(:merge_request) { create(:merge_request, merge_status: :unchecked, source_project: project, target_project: project) } + let(:repo) { project.repository } + + subject { described_class.new(merge_request).execute } + + before do + project.add_developer(merge_request.author) + end + + it_behaves_like 'mergeable merge request' + + context 'when multiple calls to the service' do + it 'returns success' do + subject + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.success?).to be(true) + end + + it 'second call does not change the merge-ref' do + expect { subject }.to change(merge_request, :merge_ref_head).from(nil) + expect { subject }.not_to change(merge_request, :merge_ref_head) + end + end + + context 'disabled merge ref sync feature flag' do + before do + stub_feature_flags(merge_ref_auto_sync: false) + end + + it 'returns error and no payload' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge ref is outdated due to disabled feature') + expect(result.payload).to be_empty + end + + it 'ignores merge-ref and updates merge status' do + expect { subject }.to change(merge_request, :merge_status).from('unchecked').to('can_be_merged') + end + end + + context 'when broken' do + before do + allow(merge_request).to receive(:broken?) { true } + allow(project.repository).to receive(:can_be_merged?) { false } + end + + it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end + end + + context 'when it has conflicts' do + before do + allow(merge_request).to receive(:broken?) { false } + allow(project.repository).to receive(:can_be_merged?) { false } + end + + it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end + end + + context 'when MR cannot be merged and has no merge ref' do + before do + merge_request.mark_as_unmergeable! + end + + it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end + end + + context 'when MR cannot be merged and has outdated merge ref' do + before do + MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request) + merge_request.mark_as_unmergeable! + end + + it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end + end + + context 'when merge request is not given' do + subject { described_class.new(nil).execute } + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.message).to eq('Invalid argument') + end + end + + context 'when read only DB' do + it 'returns ServiceResponse.error' do + allow(Gitlab::Database).to receive(:read_only?) { true } + + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.message).to eq('Unsupported operation') + end + end + + context 'when fails to update the merge-ref' do + before do + expect_next_instance_of(MergeRequests::MergeToRefService) do |merge_to_ref| + expect(merge_to_ref).to receive(:execute).and_return(status: :failed) + end + end + + it_behaves_like 'unmergeable merge request' + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge request is not mergeable') + end + end + + context 'recheck enforced' do + subject { described_class.new(merge_request).execute(recheck: true) } + + context 'when MR is mergeable and merge-ref auto-sync is disabled' do + before do + stub_feature_flags(merge_ref_auto_sync: false) + merge_request.mark_as_mergeable! + end + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge ref is outdated due to disabled feature') + expect(result.payload).to be_empty + end + + it 'merge status is not changed' do + subject + + expect(merge_request.merge_status).to eq('can_be_merged') + end + end + + context 'when MR is marked as mergeable, but repo is not mergeable and MR is not opened' do + before do + # Making sure that we don't touch the merge-status after + # the MR is not opened any longer. Source branch might + # have been removed, etc. + allow(merge_request).to receive(:broken?) { true } + merge_request.mark_as_mergeable! + merge_request.close! + end + + it 'returns ServiceResponse.error' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result.error?).to be(true) + expect(result.message).to eq('Merge ref cannot be updated') + expect(result.payload).to be_empty + end + + it 'does not change the merge status' do + expect { subject }.not_to change(merge_request, :merge_status).from('can_be_merged') + end + end + + context 'when MR is mergeable but merge-ref does not exists' do + before do + merge_request.mark_as_mergeable! + end + + it_behaves_like 'mergeable merge request' + end + + context 'when MR is mergeable but merge-ref is already updated' do + before do + MergeRequests::MergeToRefService.new(project, merge_request.author).execute(merge_request) + merge_request.mark_as_mergeable! + end + + it 'returns ServiceResponse.success' do + result = subject + + expect(result).to be_a(ServiceResponse) + expect(result).to be_success + expect(result.payload[:merge_ref_head]).to be_present + end + + it 'does not recreate the merge-ref' do + expect(MergeRequests::MergeToRefService).not_to receive(:new) + + subject + end + end + end + end +end diff --git a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb index d5f77f3354b..8d43ce4f662 100644 --- a/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb +++ b/spec/services/pages_domains/obtain_lets_encrypt_certificate_service_spec.rb @@ -34,8 +34,12 @@ describe PagesDomains::ObtainLetsEncryptCertificateService do end context 'when there is no acme order' do - it 'creates acme order' do + it 'creates acme order and schedules next step' do expect_to_create_acme_challenge + expect(PagesDomainSslRenewalWorker).to( + receive(:perform_in).with(described_class::CHALLENGE_PROCESSING_DELAY, pages_domain.id) + .and_return(nil).once + ) service.execute end @@ -82,8 +86,12 @@ describe PagesDomains::ObtainLetsEncryptCertificateService do stub_lets_encrypt_order(existing_order.url, 'ready') end - it 'request certificate' do + it 'request certificate and schedules next step' do expect(api_order).to receive(:request_certificate).and_call_original + expect(PagesDomainSslRenewalWorker).to( + receive(:perform_in).with(described_class::CERTIFICATE_PROCESSING_DELAY, pages_domain.id) + .and_return(nil).once + ) service.execute end diff --git a/spec/services/projects/propagate_service_template_spec.rb b/spec/services/projects/propagate_service_template_spec.rb index f93e5aae82a..2c3effec617 100644 --- a/spec/services/projects/propagate_service_template_spec.rb +++ b/spec/services/projects/propagate_service_template_spec.rb @@ -72,7 +72,7 @@ describe Projects::PropagateServiceTemplate do expect(project.pushover_service.properties).to eq(service_template.properties) end - describe 'bulk update' do + describe 'bulk update', :use_sql_query_cache do let(:project_total) { 5 } before do diff --git a/spec/services/service_response_spec.rb b/spec/services/service_response_spec.rb index 30bd4d6820b..e790d272e61 100644 --- a/spec/services/service_response_spec.rb +++ b/spec/services/service_response_spec.rb @@ -16,6 +16,13 @@ describe ServiceResponse do expect(response).to be_success expect(response.message).to eq('Good orange') end + + it 'creates a successful response with payload' do + response = described_class.success(payload: { good: 'orange' }) + + expect(response).to be_success + expect(response.payload).to eq(good: 'orange') + end end describe '.error' do @@ -33,6 +40,15 @@ describe ServiceResponse do expect(response.message).to eq('Bad apple') expect(response.http_status).to eq(400) end + + it 'creates a failed response with payload' do + response = described_class.error(message: 'Bad apple', + payload: { bad: 'apple' }) + + expect(response).to be_error + expect(response.message).to eq('Bad apple') + expect(response.payload).to eq(bad: 'apple') + end end describe '#success?' do diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 2420817e1f7..30a867fa7ba 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -946,6 +946,18 @@ describe SystemNoteService do expect(subject.note).to eq "changed time estimate to 1w 4d 5h" end + + context 'when time_tracking_limit_to_hours setting is true' do + before do + stub_application_setting(time_tracking_limit_to_hours: true) + end + + it 'sets the note text' do + noteable.update_attribute(:time_estimate, 277200) + + expect(subject.note).to eq "changed time estimate to 77h" + end + end end context 'without a time estimate' do @@ -1022,6 +1034,18 @@ describe SystemNoteService do end end + context 'when time_tracking_limit_to_hours setting is true' do + before do + stub_application_setting(time_tracking_limit_to_hours: true) + end + + it 'sets the note text' do + spend_time!(277200) + + expect(subject.note).to eq "added 77h of time spent" + end + end + def spend_time!(seconds) noteable.spend_time(duration: seconds, user_id: author.id) noteable.save! diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 390a869d93f..3bd2408dc72 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -218,6 +218,12 @@ RSpec.configure do |config| ActionController::Base.cache_store = caching_store end + config.around(:each, :use_sql_query_cache) do |example| + ActiveRecord::Base.cache do + example.run + end + end + # The :each scope runs "inside" the example, so this hook ensures the DB is in the # correct state before any examples' before hooks are called. This prevents a # problem where `ScheduleIssuesClosedAtTypeChange` (or any migration that depends diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb index ed049daba80..a7175491fa0 100644 --- a/spec/support/helpers/email_helpers.rb +++ b/spec/support/helpers/email_helpers.rb @@ -37,8 +37,19 @@ module EmailHelpers ActionMailer::Base.deliveries.find { |d| d.to.include?(user.notification_email) } end - def have_referable_subject(referable, include_project: true, reply: false) - prefix = (include_project && referable.project ? "#{referable.project.name} | " : '').freeze + def have_referable_subject(referable, include_project: true, include_group: false, reply: false) + context = [] + + context << referable.project.name if include_project && referable.project + context << referable.project.group.name if include_group && referable.project.group + + prefix = + if context.any? + context.join(' | ') + ' | ' + else + '' + end + prefix = "Re: #{prefix}" if reply suffix = "#{referable.title} (#{referable.to_reference})" diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb index f4d5343c4ed..477bbf1c2e0 100644 --- a/spec/support/helpers/jira_service_helper.rb +++ b/spec/support/helpers/jira_service_helper.rb @@ -25,7 +25,7 @@ module JiraServiceHelper \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"}, \"displayName\":\"GitLab\",\"active\":true}, - \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\", + \"body\":\"[Administrator|http://localhost:3000/root] mentioned JIRA-1 in Merge request of [gitlab-org/gitlab-test|http://localhost:3000/gitlab-org/gitlab-test/merge_requests/2].\", \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", @@ -40,7 +40,7 @@ module JiraServiceHelper \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", \"32x32\":\"http://0.0.0.0:4567/secure/useravatar?size=medium&avatarId=10122\", \"48x48\":\"http://0.0.0.0:4567/secure/useravatar?avatarId=10122\"},\"displayName\":\"GitLab\",\"active\":true}, - \"body\":\"[Administrator|http://localhost:3000/u/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\", + \"body\":\"[Administrator|http://localhost:3000/root] mentioned this issue in [a commit of h5bp/html5-boilerplate|http://localhost:3000/h5bp/html5-boilerplate/commit/2439f77897122fbeee3bfd9bb692d3608848433e].\", \"updateAuthor\":{\"self\":\"http://0.0.0.0:4567/rest/api/2/user?username=gitlab\",\"name\":\"gitlab\",\"emailAddress\":\"gitlab@example.com\", \"avatarUrls\":{\"16x16\":\"http://0.0.0.0:4567/secure/useravatar?size=xsmall&avatarId=10122\", \"24x24\":\"http://0.0.0.0:4567/secure/useravatar?size=small&avatarId=10122\", diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb index 87f825152cf..db662836013 100644 --- a/spec/support/helpers/prometheus_helpers.rb +++ b/spec/support/helpers/prometheus_helpers.rb @@ -70,6 +70,10 @@ module PrometheusHelpers WebMock.stub_request(:get, url).to_raise(exception_type) end + def stub_any_prometheus_request + WebMock.stub_request(:any, /prometheus.example.com/) + end + def stub_all_prometheus_requests(environment_slug, body: nil, status: 200) stub_prometheus_request( prometheus_query_with_time_url(prometheus_memory_query(environment_slug), Time.now.utc), diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb index f6c613ad5aa..0d591f038ce 100644 --- a/spec/support/helpers/stub_configuration.rb +++ b/spec/support/helpers/stub_configuration.rb @@ -81,6 +81,12 @@ module StubConfiguration allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages)) end + def stub_sentry_settings + allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) + allow(Gitlab.config.sentry).to receive(:dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/42') + allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('dummy://b44a0828b72421a6d8e99efd68d44fa8@example.com/43') + end + def stub_kerberos_setting(messages) allow(Gitlab.config.kerberos).to receive_messages(to_settings(messages)) end diff --git a/spec/support/inspect_squelch.rb b/spec/support/inspect_squelch.rb new file mode 100644 index 00000000000..8ee6732370b --- /dev/null +++ b/spec/support/inspect_squelch.rb @@ -0,0 +1,7 @@ +# This class can generate a lot of output if it fails, +# so squelch the instance variable output. +class ActiveSupport::Cache::NullStore + def inspect + "<#{self.class}>" + end +end diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb index 421303c97be..e7ec24c5b7e 100644 --- a/spec/support/shared_examples/application_setting_examples.rb +++ b/spec/support/shared_examples/application_setting_examples.rb @@ -249,43 +249,4 @@ RSpec.shared_examples 'application settings examples' do expect(setting.password_authentication_enabled_for_web?).to be_falsey end - - describe 'sentry settings' do - context 'when the sentry settings are not set in gitlab.yml' do - it 'fallbacks to the settings in the database' do - setting.sentry_enabled = true - setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' - setting.clientside_sentry_enabled = true - setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' - - allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) - allow(Gitlab.config.sentry).to receive(:dsn).and_return(nil) - allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return(nil) - - expect(setting.sentry_enabled).to eq true - expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' - expect(setting.clientside_sentry_enabled).to eq true - expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' - end - end - - context 'when the sentry settings are set in gitlab.yml' do - it 'does not fallback to the settings in the database' do - setting.sentry_enabled = false - setting.sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/40' - setting.clientside_sentry_enabled = false - setting.clientside_sentry_dsn = 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/41' - - allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) - allow(Gitlab.config.sentry).to receive(:dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42') - allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return('https://b44a0828b72421a6d8e99efd68d44fa8@example.com/43') - - expect(setting).not_to receive(:read_attribute) - expect(setting.sentry_enabled).to eq true - expect(setting.sentry_dsn).to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/42' - expect(setting.clientside_sentry_enabled).to eq true - expect(setting.clientside_sentry_dsn). to eq 'https://b44a0828b72421a6d8e99efd68d44fa8@example.com/43' - end - end - end end diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb index 7993b2870e5..f985b2dcbba 100644 --- a/spec/support/shared_examples/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/ci_trace_shared_examples.rb @@ -5,7 +5,7 @@ shared_examples_for 'common trace features' do end it "returns formatted html" do - expect(trace.html).to eq("<span class=\"\">12</span><br/><span class=\"\">34</span>") + expect(trace.html).to eq("<span class=\"\">12<br/><span class=\"\">34</span></span>") end it "returns last line of formatted html" do diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb index 6894a63ce42..e64c7e37a0c 100644 --- a/spec/support/shared_examples/notify_shared_examples.rb +++ b/spec/support/shared_examples/notify_shared_examples.rb @@ -45,18 +45,18 @@ shared_examples 'an email sent to a user' do let(:group_notification_email) { 'user+group@example.com' } it 'is sent to user\'s global notification email address' do - expect(subject).to deliver_to(test_recipient.notification_email) + expect(subject).to deliver_to(recipient.notification_email) end context 'that is part of a project\'s group' do it 'is sent to user\'s group notification email address when set' do - create(:notification_setting, user: test_recipient, source: project.group, notification_email: group_notification_email) + create(:notification_setting, user: recipient, source: project.group, notification_email: group_notification_email) expect(subject).to deliver_to(group_notification_email) end it 'is sent to user\'s global notification email address when no group email set' do - create(:notification_setting, user: test_recipient, source: project.group, notification_email: '') - expect(subject).to deliver_to(test_recipient.notification_email) + create(:notification_setting, user: recipient, source: project.group, notification_email: '') + expect(subject).to deliver_to(recipient.notification_email) end end @@ -67,17 +67,17 @@ shared_examples 'an email sent to a user' do it 'is sent to user\'s subgroup notification email address when set' do # Set top-level group notification email address to make sure it doesn't get selected - create(:notification_setting, user: test_recipient, source: group, notification_email: group_notification_email) + create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email) subgroup_notification_email = 'user+subgroup@example.com' - create(:notification_setting, user: test_recipient, source: subgroup, notification_email: subgroup_notification_email) + create(:notification_setting, user: recipient, source: subgroup, notification_email: subgroup_notification_email) expect(subject).to deliver_to(subgroup_notification_email) end it 'is sent to user\'s group notification email address when set and subgroup email address not set' do - create(:notification_setting, user: test_recipient, source: subgroup, notification_email: '') - expect(subject).to deliver_to(test_recipient.notification_email) + create(:notification_setting, user: recipient, source: subgroup, notification_email: '') + expect(subject).to deliver_to(recipient.notification_email) end end end diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb index 6c4e11910d3..d1a765f27b9 100644 --- a/spec/support/sidekiq.rb +++ b/spec/support/sidekiq.rb @@ -30,6 +30,8 @@ RSpec.configure do |config| end config.after(:each, :sidekiq, :redis) do - Sidekiq.redis { |redis| redis.flushdb } + Sidekiq.redis do |connection| + connection.redis.flushdb + end end end diff --git a/spec/workers/build_success_worker_spec.rb b/spec/workers/build_success_worker_spec.rb index ffe8796ded9..f4ff8cb15f8 100644 --- a/spec/workers/build_success_worker_spec.rb +++ b/spec/workers/build_success_worker_spec.rb @@ -6,51 +6,7 @@ describe BuildSuccessWorker do describe '#perform' do subject { described_class.new.perform(build.id) } - before do - allow_any_instance_of(Deployment).to receive(:create_ref) - end - context 'when build exists' do - context 'when deployment was not created with the build creation' do # An edge case during the transition period - let!(:build) { create(:ci_build, :deploy_to_production) } - - before do - allow(Deployments::FinishedWorker).to receive(:perform_async) - Deployment.delete_all - build.reload - end - - it 'creates a successful deployment' do - expect(build).not_to be_has_deployment - - subject - - build.reload - expect(build).to be_has_deployment - expect(build.deployment).to be_success - end - end - - context 'when deployment was created with the build creation' do # Counter part of the above edge case - let!(:build) { create(:ci_build, :deploy_to_production) } - - it 'does not create a new deployment' do - expect(build).to be_has_deployment - - expect { subject }.not_to change { Deployment.count } - end - end - - context 'when build is not associated with project' do - let!(:build) { create(:ci_build, project: nil) } - - it 'does not create deployment' do - subject - - expect(build.reload).not_to be_has_deployment - end - end - context 'when the build will stop an environment' do let!(:build) { create(:ci_build, :stop_review_app, environment: environment.name, project: environment.project) } let(:environment) { create(:environment, state: :available) } diff --git a/spec/workers/cluster_provision_worker_spec.rb b/spec/workers/cluster_provision_worker_spec.rb index 9cc2ad12bfc..3f69962f25d 100644 --- a/spec/workers/cluster_provision_worker_spec.rb +++ b/spec/workers/cluster_provision_worker_spec.rb @@ -23,18 +23,11 @@ describe ClusterProvisionWorker do described_class.new.perform(cluster.id) end - - it 'configures kubernetes platform' do - expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id) - - described_class.new.perform(cluster.id) - end end context 'when cluster does not exist' do it 'does not provision a cluster' do expect_any_instance_of(Clusters::Gcp::ProvisionService).not_to receive(:execute) - expect(ClusterConfigureWorker).not_to receive(:perform_async) described_class.new.perform(123) end diff --git a/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb new file mode 100644 index 00000000000..08a3511f70b --- /dev/null +++ b/spec/workers/pages_domain_ssl_renewal_cron_worker_spec.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomainSslRenewalCronWorker do + include LetsEncryptHelpers + + subject(:worker) { described_class.new } + + before do + stub_lets_encrypt_settings + end + + describe '#perform' do + let(:project) { create :project } + let!(:domain) { create(:pages_domain, project: project) } + let!(:domain_with_enabled_auto_ssl) { create(:pages_domain, project: project, auto_ssl_enabled: true) } + let!(:domain_with_obtained_letsencrypt) do + create(:pages_domain, :letsencrypt, project: project, auto_ssl_enabled: true) + end + let!(:domain_without_auto_certificate) do + create(:pages_domain, :without_certificate, :without_key, project: project, auto_ssl_enabled: true) + end + + let!(:domain_with_expired_auto_ssl) do + create(:pages_domain, :letsencrypt, :with_expired_certificate, project: project) + end + + it 'enqueues a PagesDomainSslRenewalWorker for domains needing renewal' do + [domain_without_auto_certificate, + domain_with_enabled_auto_ssl, + domain_with_expired_auto_ssl].each do |domain| + expect(PagesDomainSslRenewalWorker).to receive(:perform_async).with(domain.id) + end + + [domain, + domain_with_obtained_letsencrypt].each do |domain| + expect(PagesDomainVerificationWorker).not_to receive(:perform_async).with(domain.id) + end + + worker.perform + end + + shared_examples 'does nothing' do + it 'does nothing' do + expect(PagesDomainSslRenewalWorker).not_to receive(:perform_async) + + worker.perform + end + end + + context 'when letsencrypt integration is disabled' do + before do + stub_application_setting( + lets_encrypt_terms_of_service_accepted: false + ) + end + + include_examples 'does nothing' + end + end +end diff --git a/spec/workers/pages_domain_ssl_renewal_worker_spec.rb b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb new file mode 100644 index 00000000000..3552ff0823a --- /dev/null +++ b/spec/workers/pages_domain_ssl_renewal_worker_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe PagesDomainSslRenewalWorker do + include LetsEncryptHelpers + + subject(:worker) { described_class.new } + + let(:project) { create(:project) } + let(:domain) { create(:pages_domain, project: project) } + + before do + stub_lets_encrypt_settings + end + + describe '#perform' do + it 'delegates to ObtainLetsEncryptCertificateService' do + service = double(:service) + expect(::PagesDomains::ObtainLetsEncryptCertificateService).to receive(:new).with(domain).and_return(service) + expect(service).to receive(:execute) + + worker.perform(domain.id) + end + + shared_examples 'does nothing' do + it 'does nothing' do + expect(::PagesDomains::ObtainLetsEncryptCertificateService).not_to receive(:new) + end + end + + context 'when domain was deleted' do + before do + domain.destroy! + end + + include_examples 'does nothing' + end + + context 'when domain is disabled' do + let(:domain) { create(:pages_domain, :disabled) } + + include_examples 'does nothing' + end + end +end diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb index b8ca6063ccd..ca0e76fc19a 100644 --- a/spec/workers/reactive_caching_worker_spec.rb +++ b/spec/workers/reactive_caching_worker_spec.rb @@ -3,17 +3,16 @@ require 'spec_helper' describe ReactiveCachingWorker do - let(:service) { project.deployment_platform } - describe '#perform' do context 'when user configured kubernetes from CI/CD > Clusters' do let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:project) { cluster.project } + let!(:environment) { create(:environment, project: project) } it 'calls #exclusively_update_reactive_cache!' do - expect_any_instance_of(Clusters::Platforms::Kubernetes).to receive(:exclusively_update_reactive_cache!) + expect_any_instance_of(Environment).to receive(:exclusively_update_reactive_cache!) - described_class.new.perform("Clusters::Platforms::Kubernetes", service.id) + described_class.new.perform("Environment", environment.id) end end end diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml index 0fbf36b39cc..2aadd3dbe1e 100644 --- a/vendor/jupyter/values.yaml +++ b/vendor/jupyter/values.yaml @@ -46,7 +46,7 @@ singleuser: - "-c" - > git clone https://gitlab.com/gitlab-org/nurtch-demo.git DevOps-Runbook-Demo || true; - echo "https://${GITLAB_USER_LOGIN}:${GITLAB_ACCESS_TOKEN}@${GITLAB_HOST}" > ~/.git-credentials; + echo "https://oauth2:${GITLAB_ACCESS_TOKEN}@${GITLAB_HOST}" > ~/.git-credentials; git config --global credential.helper store; git config --global user.email "${GITLAB_USER_EMAIL}"; git config --global user.name "${GITLAB_USER_NAME}"; diff --git a/yarn.lock b/yarn.lock index 5b725f07c93..1f1b35eed0a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1686,13 +1686,13 @@ axios-mock-adapter@^1.15.0: dependencies: deep-equal "^1.0.1" -axios@^0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d" - integrity sha1-LY4+XQvb1zJ/kbyBT1xXZg+Bgk0= +axios@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" + integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ== dependencies: - follow-redirects "^1.2.5" - is-buffer "^1.1.5" + follow-redirects "1.5.10" + is-buffer "^2.0.2" babel-code-frame@^6.26.0: version "6.26.0" @@ -2231,6 +2231,14 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== +camel-case@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -2460,6 +2468,13 @@ classlist-polyfill@^1.2.0: resolved "https://registry.yarnpkg.com/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz#935bc2dfd9458a876b279617514638bcaa964a2e" integrity sha1-k1vC39lFiodrJ5YXUUY4vKqWSi4= +clean-css@^4.1.6, clean-css@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" + integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== + dependencies: + source-map "~0.6.0" + cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" @@ -2953,6 +2968,11 @@ crypto-random-string@^1.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4= +css-b64-images@~0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/css-b64-images/-/css-b64-images-0.2.5.tgz#42005d83204b2b4a5d93b6b1a5644133b5927a02" + integrity sha1-QgBdgyBLK0pdk7axpWRBM7WSegI= + css-loader@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-1.0.1.tgz#6885bb5233b35ec47b006057da01cc640b6b79fe" @@ -3410,6 +3430,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: dependencies: ms "2.0.0" +debug@=3.1.0, debug@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -3424,13 +3451,6 @@ debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" -debug@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - decamelize-keys@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -4731,12 +4751,12 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" -follow-redirects@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.2.6.tgz#4dcdc7e4ab3dd6765a97ff89c3b4c258117c79bf" - integrity sha512-FrMqZ/FONtHnbqO651UPpfRUVukIEwJhXMfdr/JWAmrDbeYBu773b1J6gdWDyRIj4hvvzQEHoEOTrdR8o6KLYA== +follow-redirects@1.5.10: + version "1.5.10" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" + integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== dependencies: - debug "^3.1.0" + debug "=3.1.0" for-in@^1.0.2: version "1.0.2" @@ -4829,10 +4849,10 @@ fsevents@^1.2.2, fsevents@^1.2.7: nan "^2.12.1" node-pre-gyp "^0.12.0" -fstream@^1.0.0, fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= +fstream@^1.0.0, fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== dependencies: graceful-fs "^4.1.2" inherits "~2.0.0" @@ -5370,6 +5390,19 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2" integrity sha1-QZSMr4XOgv7Tbk5qDtNxpmZDeeI= +html-minifier@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56" + integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig== + dependencies: + camel-case "^3.0.0" + clean-css "^4.2.1" + commander "^2.19.0" + he "^1.2.0" + param-case "^2.1.1" + relateurl "^0.2.7" + uglify-js "^3.5.1" + html-tags@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" @@ -5734,7 +5767,7 @@ is-buffer@^1.1.5, is-buffer@~1.1.1: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@^2.0.0: +is-buffer@^2.0.0, is-buffer@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== @@ -6566,10 +6599,10 @@ jquery.waitforimages@^2.2.0: resolved "https://registry.yarnpkg.com/jquery.waitforimages/-/jquery.waitforimages-2.2.0.tgz#63f23131055a1b060dc913e6d874bcc9b9e6b16b" integrity sha1-Y/IxMQVaGwYNyRPm2HS8ybnmsWs= -"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" - integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg== +"jquery@>= 1.9.1", jquery@>=1.8.0, jquery@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.4.1.tgz#714f1f8d9dde4bdfa55764ba37ef214630d80ef2" + integrity sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw== js-base64@^2.1.8: version "2.5.1" @@ -7141,6 +7174,11 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= + lowercase-keys@1.0.0, lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" @@ -7384,10 +7422,10 @@ merge2@^1.2.3: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5" integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA== -mermaid@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.0.0.tgz#8f6c75017e788a8c3997e20c5e5046c2b88d1a8f" - integrity sha512-vUQRykev0A6RtxIVqQT3a9TDxcSbdZbQF5JDyKgidnYuJy8BE8jp6LM+HKDSQuroKm6buu4NlpMO+qhxIP/cTg== +mermaid@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.1.0.tgz#f9f4c02cf98d2d9fae230d5ce28f531e605e9b72" + integrity sha512-fsCN8bOukYHZT6FlA0eIeLs/O3H2+CWcHnxRrS86Ci1cpJes5/qvoye0xjhe8lbXJCFLM8sXWVg57aMHPtnAaw== dependencies: d3 "^5.7.0" dagre-d3-renderer "^0.5.8" @@ -7395,7 +7433,8 @@ mermaid@^8.0.0: graphlibrary "^2.2.0" he "^1.2.0" lodash "^4.17.11" - moment "^2.23.0" + minify "^4.1.1" + moment-mini "^2.22.1" scope-css "^1.2.1" methods@~1.1.2: @@ -7467,6 +7506,19 @@ mimic-response@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" integrity sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4= +minify@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/minify/-/minify-4.1.2.tgz#88755f4faa5f7ab6d0c64fdd659aa34ea658f180" + integrity sha512-YY6b6VzV7AY2MTMt1GjoFqKthGWvAr2L7MrzmFyiEsvPX+XAvidHcKqu36LlDT1V4I80ncbV5bsdTnIJq4/Sdw== + dependencies: + clean-css "^4.1.6" + css-b64-images "~0.2.5" + debug "^4.1.0" + html-minifier "^4.0.0" + terser "^4.0.0" + try-catch "^2.0.0" + try-to-catch "^1.0.2" + minimalistic-assert@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3" @@ -7553,7 +7605,12 @@ mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp dependencies: minimist "0.0.8" -moment@2.x, moment@^2.10.2, moment@^2.23.0: +moment-mini@^2.22.1: + version "2.22.1" + resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.22.1.tgz#bc32d73e43a4505070be6b53494b17623183420d" + integrity sha512-OUCkHOz7ehtNMYuZjNciXUfwTuz8vmF1MTbAy59ebf+ZBYZO5/tZKuChVWCX+uDo+4idJBpGltNfV8st+HwsGw== + +moment@2.x, moment@^2.10.2: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -7666,6 +7723,13 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + node-ensure@^0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" @@ -8259,6 +8323,13 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" +param-case@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= + dependencies: + no-case "^2.2.0" + parse-asn1@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" @@ -9305,6 +9376,11 @@ regjsparser@^0.6.0: dependencies: jsesc "~0.5.0" +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + remark-parse@^6.0.0: version "6.0.3" resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-6.0.3.tgz#c99131052809da482108413f87b0ee7f52180a3a" @@ -10027,7 +10103,7 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.6, source-map-support@~0.5.6: +source-map-support@^0.5.6, source-map-support@~0.5.10, source-map-support@~0.5.6: version "0.5.12" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599" integrity sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ== @@ -10057,7 +10133,7 @@ source-map@^0.5.0, source-map@^0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -10537,12 +10613,12 @@ tapable@^1.0.0, tapable@^1.1.0: integrity sha512-IlqtmLVaZA2qab8epUXbVWRn3aB1imbDMJtjB3nu4X0NqPkcY/JH9ZtCBWKHWPxs8Svi9tyo8w2dBoi07qZbBA== tar@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" - integrity sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE= + version "2.2.2" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" + integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== dependencies: block-stream "*" - fstream "^1.0.2" + fstream "^1.0.12" inherits "2" tar@^4: @@ -10588,6 +10664,15 @@ terser@^3.8.1: source-map "~0.6.1" source-map-support "~0.5.6" +terser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.0.0.tgz#ef356f6f359a963e2cc675517f21c1c382877374" + integrity sha512-dOapGTU0hETFl1tCo4t56FN+2jffoKyER9qBGoUFyZ6y7WLoKT0bF+lAYi6B6YsILcGF3q1C2FBh8QcKSCgkgA== + dependencies: + commander "^2.19.0" + source-map "~0.6.1" + source-map-support "~0.5.10" + test-exclude@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.0.0.tgz#cdce7cece785e0e829cd5c2b27baf18bc583cfb7" @@ -10842,6 +10927,16 @@ trough@^1.0.0: dependencies: glob "^7.1.2" +try-catch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/try-catch/-/try-catch-2.0.0.tgz#a491141d597f8b72b46757fe1c47059341a16aed" + integrity sha512-RPXpVjsbtWgymwGq5F/OWDFsjEzdvzwHFaMjWWW6f/p6+uk/N7YSKJHQfIfGqITfj8qH4cBqCLMnhKZBaKk7Kg== + +try-to-catch@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/try-to-catch/-/try-to-catch-1.1.1.tgz#770162dd13b9a0e55da04db5b7f888956072038a" + integrity sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA== + tryer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.0.tgz#027b69fa823225e551cace3ef03b11f6ab37c1d7" @@ -10928,10 +11023,10 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg== -uglify-js@^3.1.4: - version "3.5.15" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.5.15.tgz#fe2b5378fd0b09e116864041437bff889105ce24" - integrity sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg== +uglify-js@^3.1.4, uglify-js@^3.5.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5" + integrity sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg== dependencies: commander "~2.20.0" source-map "~0.6.1" @@ -11128,6 +11223,11 @@ update-notifier@^2.5.0: semver-diff "^2.0.0" xdg-basedir "^3.0.0" +upper-case@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" |